about summary refs log tree commit diff
path: root/tvix
diff options
context:
space:
mode:
Diffstat (limited to 'tvix')
-rw-r--r--tvix/.envrc10
-rw-r--r--tvix/.gitignore3
-rw-r--r--tvix/.vscode/extensions.json2
-rw-r--r--tvix/Cargo.lock5089
-rw-r--r--tvix/Cargo.nix17630
-rw-r--r--tvix/Cargo.toml38
-rw-r--r--tvix/OWNERS12
-rw-r--r--tvix/README.md111
-rw-r--r--tvix/boot/README.md136
-rw-r--r--tvix/boot/default.nix113
-rw-r--r--tvix/boot/tests/default.nix138
-rw-r--r--tvix/boot/tvix-init.go138
-rw-r--r--tvix/build-go/LICENSE (renamed from tvix/proto/LICENSE)2
-rw-r--r--tvix/build-go/README.md10
-rw-r--r--tvix/build-go/build.pb.go670
-rw-r--r--tvix/build-go/default.nix31
-rw-r--r--tvix/build-go/go.mod19
-rw-r--r--tvix/build-go/go.sum88
-rw-r--r--tvix/build-go/rpc_build.pb.go80
-rw-r--r--tvix/build-go/rpc_build_grpc.pb.go112
-rw-r--r--tvix/build/Cargo.toml33
-rw-r--r--tvix/build/build.rs38
-rw-r--r--tvix/build/default.nix5
-rw-r--r--tvix/build/protos/LICENSE21
-rw-r--r--tvix/build/protos/build.proto163
-rw-r--r--tvix/build/protos/default.nix56
-rw-r--r--tvix/build/protos/rpc_build.proto13
-rw-r--r--tvix/build/src/bin/tvix-build.rs132
-rw-r--r--tvix/build/src/buildservice/dummy.rs19
-rw-r--r--tvix/build/src/buildservice/from_addr.rs90
-rw-r--r--tvix/build/src/buildservice/grpc.rs28
-rw-r--r--tvix/build/src/buildservice/mod.rs16
-rw-r--r--tvix/build/src/lib.rs2
-rw-r--r--tvix/build/src/proto/grpc_buildservice_wrapper.rs35
-rw-r--r--tvix/build/src/proto/mod.rs264
-rw-r--r--tvix/buildkite.yml10
-rw-r--r--tvix/castore-go/LICENSE21
-rw-r--r--tvix/castore-go/README.md10
-rw-r--r--tvix/castore-go/castore.go212
-rw-r--r--tvix/castore-go/castore.pb.go580
-rw-r--r--tvix/castore-go/castore_test.go298
-rw-r--r--tvix/castore-go/default.nix31
-rw-r--r--tvix/castore-go/go.mod22
-rw-r--r--tvix/castore-go/go.sum99
-rw-r--r--tvix/castore-go/rename_node.go38
-rw-r--r--tvix/castore-go/rpc_blobstore.pb.go538
-rw-r--r--tvix/castore-go/rpc_blobstore_grpc.pb.go288
-rw-r--r--tvix/castore-go/rpc_directory.pb.go272
-rw-r--r--tvix/castore-go/rpc_directory_grpc.pb.go248
-rw-r--r--tvix/castore/Cargo.toml118
-rw-r--r--tvix/castore/build.rs39
-rw-r--r--tvix/castore/default.nix23
-rw-r--r--tvix/castore/docs/blobstore-chunking.md147
-rw-r--r--tvix/castore/docs/blobstore-protocol.md104
-rw-r--r--tvix/castore/docs/data-model.md50
-rw-r--r--tvix/castore/docs/why-not-git-trees.md57
-rw-r--r--tvix/castore/protos/LICENSE21
-rw-r--r--tvix/castore/protos/castore.proto71
-rw-r--r--tvix/castore/protos/default.nix54
-rw-r--r--tvix/castore/protos/rpc_blobstore.proto85
-rw-r--r--tvix/castore/protos/rpc_directory.proto53
-rw-r--r--tvix/castore/src/blobservice/chunked_reader.rs496
-rw-r--r--tvix/castore/src/blobservice/combinator.rs132
-rw-r--r--tvix/castore/src/blobservice/from_addr.rs152
-rw-r--r--tvix/castore/src/blobservice/grpc.rs349
-rw-r--r--tvix/castore/src/blobservice/memory.rs137
-rw-r--r--tvix/castore/src/blobservice/mod.rs105
-rw-r--r--tvix/castore/src/blobservice/naive_seeker.rs265
-rw-r--r--tvix/castore/src/blobservice/object_store.rs546
-rw-r--r--tvix/castore/src/blobservice/sled.rs150
-rw-r--r--tvix/castore/src/blobservice/tests/mod.rs254
-rw-r--r--tvix/castore/src/blobservice/tests/utils.rs41
-rw-r--r--tvix/castore/src/digests.rs86
-rw-r--r--tvix/castore/src/directoryservice/bigtable.rs357
-rw-r--r--tvix/castore/src/directoryservice/closure_validator.rs268
-rw-r--r--tvix/castore/src/directoryservice/from_addr.rs174
-rw-r--r--tvix/castore/src/directoryservice/grpc.rs345
-rw-r--r--tvix/castore/src/directoryservice/memory.rs86
-rw-r--r--tvix/castore/src/directoryservice/mod.rs122
-rw-r--r--tvix/castore/src/directoryservice/simple_putter.rs75
-rw-r--r--tvix/castore/src/directoryservice/sled.rs168
-rw-r--r--tvix/castore/src/directoryservice/tests/mod.rs226
-rw-r--r--tvix/castore/src/directoryservice/tests/utils.rs46
-rw-r--r--tvix/castore/src/directoryservice/traverse.rs197
-rw-r--r--tvix/castore/src/directoryservice/utils.rs82
-rw-r--r--tvix/castore/src/errors.rs61
-rw-r--r--tvix/castore/src/fixtures.rs88
-rw-r--r--tvix/castore/src/fs/file_attr.rs29
-rw-r--r--tvix/castore/src/fs/fuse.rs120
-rw-r--r--tvix/castore/src/fs/inode_tracker.rs207
-rw-r--r--tvix/castore/src/fs/inodes.rs96
-rw-r--r--tvix/castore/src/fs/mod.rs877
-rw-r--r--tvix/castore/src/fs/root_nodes.rs37
-rw-r--r--tvix/castore/src/fs/tests.rs1244
-rw-r--r--tvix/castore/src/fs/virtiofs.rs238
-rw-r--r--tvix/castore/src/hashing_reader.rs89
-rw-r--r--tvix/castore/src/import/archive.rs458
-rw-r--r--tvix/castore/src/import/error.rs20
-rw-r--r--tvix/castore/src/import/fs.rs185
-rw-r--r--tvix/castore/src/import/mod.rs340
-rw-r--r--tvix/castore/src/lib.rs30
-rw-r--r--tvix/castore/src/path.rs446
-rw-r--r--tvix/castore/src/proto/grpc_blobservice_wrapper.rs175
-rw-r--r--tvix/castore/src/proto/grpc_directoryservice_wrapper.rs114
-rw-r--r--tvix/castore/src/proto/mod.rs471
-rw-r--r--tvix/castore/src/proto/tests/directory.rs452
-rw-r--r--tvix/castore/src/proto/tests/directory_nodes_iterator.rs78
-rw-r--r--tvix/castore/src/proto/tests/mod.rs2
-rw-r--r--tvix/castore/src/tests/import.rs129
-rw-r--r--tvix/castore/src/tests/mod.rs1
-rw-r--r--tvix/castore/src/tonic.rs122
-rw-r--r--tvix/cli/Cargo.toml27
-rw-r--r--tvix/cli/default.nix95
-rw-r--r--tvix/cli/src/main.rs337
-rw-r--r--tvix/clippy.toml6
-rw-r--r--tvix/crate-hashes.json4
-rw-r--r--tvix/default.nix238
-rw-r--r--tvix/docs/.gitignore4
-rw-r--r--tvix/docs/Makefile12
-rw-r--r--tvix/docs/book.toml11
-rw-r--r--tvix/docs/components.md114
-rw-r--r--tvix/docs/default.nix42
-rw-r--r--tvix/docs/src/SUMMARY.md10
-rw-r--r--tvix/docs/src/TODO.md129
-rw-r--r--tvix/docs/src/architecture.md147
-rw-r--r--tvix/docs/src/figures/component-flow.puml (renamed from tvix/docs/component-flow.puml)20
-rw-r--r--tvix/docs/src/lang-version.md62
-rw-r--r--tvix/docs/src/language-spec.md (renamed from tvix/docs/language-spec.md)13
-rw-r--r--tvix/docs/src/value-pointer-equality.md338
-rw-r--r--tvix/docs/theme/highlight.js590
-rw-r--r--tvix/eval/.gitignore1
-rw-r--r--tvix/eval/.skip-subtree2
-rw-r--r--tvix/eval/Cargo.lock106
-rw-r--r--tvix/eval/Cargo.toml56
-rw-r--r--tvix/eval/README.md78
-rw-r--r--tvix/eval/benches/eval.rs36
-rw-r--r--tvix/eval/build.rs9
-rw-r--r--tvix/eval/builtin-macros/.gitignore1
-rw-r--r--tvix/eval/builtin-macros/Cargo.toml16
-rw-r--r--tvix/eval/builtin-macros/src/lib.rs357
-rw-r--r--tvix/eval/builtin-macros/tests/tests.rs45
-rw-r--r--tvix/eval/default.nix10
-rw-r--r--tvix/eval/docs/abandoned/thread-local-vm.md233
-rw-r--r--tvix/eval/docs/bindings.md133
-rw-r--r--tvix/eval/docs/build-references.md254
-rw-r--r--tvix/eval/docs/builtins.md138
-rw-r--r--tvix/eval/docs/catchable-errors.md131
-rw-r--r--tvix/eval/docs/known-optimisation-potential.md162
-rw-r--r--tvix/eval/docs/language-issues.md46
-rw-r--r--tvix/eval/docs/recursive-attrs.md68
-rw-r--r--tvix/eval/docs/vm-loop.md315
-rw-r--r--tvix/eval/proptest-regressions/value/mod.txt10
-rw-r--r--tvix/eval/src/builtins/hash.rs29
-rw-r--r--tvix/eval/src/builtins/impure.rs110
-rw-r--r--tvix/eval/src/builtins/mod.rs1728
-rw-r--r--tvix/eval/src/builtins/to_xml.rs154
-rw-r--r--tvix/eval/src/builtins/versions.rs163
-rw-r--r--tvix/eval/src/chunk.rs273
-rw-r--r--tvix/eval/src/compiler.rs267
-rw-r--r--tvix/eval/src/compiler/bindings.rs826
-rw-r--r--tvix/eval/src/compiler/import.rs120
-rw-r--r--tvix/eval/src/compiler/mod.rs1684
-rw-r--r--tvix/eval/src/compiler/optimiser.rs125
-rw-r--r--tvix/eval/src/compiler/scope.rs378
-rw-r--r--tvix/eval/src/errors.rs1096
-rw-r--r--tvix/eval/src/eval.rs20
-rw-r--r--tvix/eval/src/io.rs164
-rw-r--r--tvix/eval/src/lib.rs394
-rw-r--r--tvix/eval/src/main.rs54
-rw-r--r--tvix/eval/src/nix_search_path.rs256
-rw-r--r--tvix/eval/src/observer.rs318
-rw-r--r--tvix/eval/src/opcode.rs270
-rw-r--r--tvix/eval/src/pretty_ast.rs468
-rw-r--r--tvix/eval/src/properties.rs164
-rw-r--r--tvix/eval/src/source.rs65
-rw-r--r--tvix/eval/src/spans.rs109
-rw-r--r--tvix/eval/src/systems.rs351
-rw-r--r--tvix/eval/src/test_utils.rs8
-rw-r--r--tvix/eval/src/tests/mod.rs203
-rw-r--r--tvix/eval/src/tests/nix_tests/README.md8
-rw-r--r--tvix/eval/src/tests/nix_tests/binary-databin0 -> 1024 bytes
-rw-r--r--tvix/eval/src/tests/nix_tests/data1
-rw-r--r--tvix/eval/src/tests/nix_tests/dir1/a.nix1
-rw-r--r--tvix/eval/src/tests/nix_tests/dir2/a.nix1
-rw-r--r--tvix/eval/src/tests/nix_tests/dir2/b.nix1
-rw-r--r--tvix/eval/src/tests/nix_tests/dir3/a.nix1
-rw-r--r--tvix/eval/src/tests/nix_tests/dir3/b.nix1
-rw-r--r--tvix/eval/src/tests/nix_tests/dir3/c.nix1
-rw-r--r--tvix/eval/src/tests/nix_tests/dir4/a.nix1
-rw-r--r--tvix/eval/src/tests/nix_tests/dir4/c.nix1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-fail-abort.nix1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-fail-assert.nix5
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-fail-bad-antiquote-1.nix1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-fail-bad-antiquote-3.nix1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-fail-blackhole.nix5
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-fail-deepseq.nix1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-fail-foldlStrict-strict-op-application.nix5
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-fail-hashfile-missing.nix5
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-fail-missing-arg.nix1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-fail-path-slash.nix6
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-fail-remove.nix5
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-fail-seq.nix1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-fail-substring.nix1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-fail-to-path.nix1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-any-all.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-any-all.nix11
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-arithmetic.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-arithmetic.nix59
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-attrnames.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-attrnames.nix11
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-attrs.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-attrs.nix5
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-attrs2.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-attrs2.nix10
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-attrs3.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-attrs3.nix22
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-attrs4.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-attrs4.nix7
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-attrs5.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-attrs5.nix21
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-backslash-newline-1.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-backslash-newline-1.nix2
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-backslash-newline-2.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-backslash-newline-2.nix2
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-builtins-add.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-builtins-add.nix8
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-builtins.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-builtins.nix12
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-callable-attrs.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-callable-attrs.nix1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-catattrs.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-catattrs.nix1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-closure.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-closure.exp.xml343
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-closure.nix13
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-comments.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-comments.nix59
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-concat.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-concat.nix1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-concatmap.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-concatmap.nix5
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-concatstringssep.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-concatstringssep.nix8
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-deepseq.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-deepseq.nix1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-delayed-with-inherit.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-delayed-with-inherit.nix24
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-dynamic-attrs-2.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-dynamic-attrs-2.nix1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-dynamic-attrs-bare.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-dynamic-attrs-bare.nix17
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-dynamic-attrs.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-dynamic-attrs.nix17
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-elem.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-elem.nix6
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-empty-args.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-empty-args.nix1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-eq.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-eq.nix3
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-filter.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-filter.nix5
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-flatten.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-flatten.nix8
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-float.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-float.nix6
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-foldlStrict-lazy-elements.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-foldlStrict-lazy-elements.nix9
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-foldlStrict-lazy-initial-accumulator.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-foldlStrict-lazy-initial-accumulator.nix6
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-foldlStrict.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-foldlStrict.nix3
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-fromTOML.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-fromTOML.nix208
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-fromjson-escapes.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-fromjson-escapes.nix3
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-fromjson.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-fromjson.nix35
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-functionargs.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-functionargs.exp.xml15
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-functionargs.nix80
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-getenv.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-getenv.nix1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-groupBy.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-groupBy.nix5
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-hash.exp0
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-hashfile.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-hashfile.nix4
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-hashstring.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-hashstring.nix4
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-if.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-if.nix1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-ind-string.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-ind-string.nix128
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-intersectAttrs.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-intersectAttrs.nix50
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-let.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-let.nix5
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-list.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-list.nix7
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-listtoattrs.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-listtoattrs.nix11
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-logic.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-logic.nix1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-map.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-map.nix3
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-mapattrs.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-mapattrs.nix3
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-nested-with.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-nested-with.nix3
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-new-let.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-new-let.nix14
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-null-dynamic-attrs.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-null-dynamic-attrs.nix1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-partition.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-partition.nix5
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-path.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-pathexists.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-pathexists.nix5
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-patterns.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-patterns.nix16
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-readfile.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-readfile.nix1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-redefine-builtin.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-redefine-builtin.nix3
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-regex-match.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-regex-match.nix29
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-regex-split.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-regex-split.nix48
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-regression-20220122.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-regression-20220122.nix1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-regression-20220125.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-regression-20220125.nix2
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-remove.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-remove.nix5
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-scope-1.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-scope-1.nix6
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-scope-2.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-scope-2.nix6
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-scope-3.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-scope-3.nix6
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-scope-4.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-scope-4.nix10
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-scope-6.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-scope-6.nix7
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-scope-7.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-scope-7.nix6
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-seq.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-seq.nix1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-sort.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-sort.nix20
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-splitversion.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-splitversion.nix1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-string.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-string.nix12
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-strings-as-attrs-names.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-strings-as-attrs-names.nix20
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-substring.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-substring.nix21
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-tail-call-1.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-tail-call-1.nix3
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-tojson.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-tojson.nix13
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-toxml.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-toxml.nix3
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-toxml2.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-toxml2.nix1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-tryeval.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-tryeval.nix5
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-types.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-types.nix37
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-versions.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-versions.nix43
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-with.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-with.nix19
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-xml.exp.xml52
-rw-r--r--tvix/eval/src/tests/nix_tests/eval-okay-xml.nix21
-rw-r--r--tvix/eval/src/tests/nix_tests/imported.nix3
-rw-r--r--tvix/eval/src/tests/nix_tests/imported2.nix1
-rw-r--r--tvix/eval/src/tests/nix_tests/lib.nix61
-rw-r--r--tvix/eval/src/tests/nix_tests/notyetpassing/eval-fail-bad-antiquote-2.nix1
-rw-r--r--tvix/eval/src/tests/nix_tests/notyetpassing/eval-fail-fromTOML-timestamps.nix130
-rw-r--r--tvix/eval/src/tests/nix_tests/notyetpassing/eval-fail-nonexist-path.nix4
-rw-r--r--tvix/eval/src/tests/nix_tests/notyetpassing/eval-fail-scope-5.nix10
-rw-r--r--tvix/eval/src/tests/nix_tests/notyetpassing/eval-fail-undeclared-arg.nix1
-rw-r--r--tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-attrs6.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-attrs6.nix4
-rw-r--r--tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-autoargs.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-autoargs.flags1
-rw-r--r--tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-autoargs.nix15
-rw-r--r--tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-context-introspection.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-context-introspection.nix41
-rw-r--r--tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-context.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-context.nix6
-rw-r--r--tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-curpos.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-curpos.nix5
-rw-r--r--tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-delayed-with.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-delayed-with.nix29
-rw-r--r--tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-eq-derivations.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-eq-derivations.nix10
-rw-r--r--tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-floor-ceil.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-floor-ceil.nix9
-rw-r--r--tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-fromTOML-timestamps.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-fromTOML-timestamps.flags1
-rw-r--r--tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-fromTOML-timestamps.nix130
-rw-r--r--tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-getattrpos-functionargs.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-getattrpos-functionargs.nix4
-rw-r--r--tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-getattrpos-undefined.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-getattrpos-undefined.nix1
-rw-r--r--tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-getattrpos.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-getattrpos.nix6
-rw-r--r--tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-import.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-import.nix11
-rw-r--r--tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-overrides.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-overrides.nix9
-rw-r--r--tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-path-antiquotation.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-path-antiquotation.nix12
-rw-r--r--tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-path.nix7
-rw-r--r--tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-readDir.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-readDir.nix.disabled1
-rw-r--r--tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-readFileType.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-readFileType.nix6
-rw-r--r--tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-replacestrings.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-replacestrings.nix12
-rw-r--r--tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-search-path.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-search-path.flags1
-rw-r--r--tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-search-path.nix10
-rw-r--r--tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-zipAttrsWith.exp1
-rw-r--r--tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-zipAttrsWith.nix9
-rw-r--r--tvix/eval/src/tests/nix_tests/notyetpassing/readDir/bar0
-rw-r--r--tvix/eval/src/tests/nix_tests/notyetpassing/readDir/foo/git-hates-directories0
l---------tvix/eval/src/tests/nix_tests/notyetpassing/readDir/ldir1
l---------tvix/eval/src/tests/nix_tests/notyetpassing/readDir/linked1
-rw-r--r--tvix/eval/src/tests/nix_tests/parse-fail-dup-attrs-1.nix4
-rw-r--r--tvix/eval/src/tests/nix_tests/parse-fail-dup-attrs-2.nix13
-rw-r--r--tvix/eval/src/tests/nix_tests/parse-fail-dup-attrs-3.nix13
-rw-r--r--tvix/eval/src/tests/nix_tests/parse-fail-dup-attrs-4.nix4
-rw-r--r--tvix/eval/src/tests/nix_tests/parse-fail-dup-attrs-7.nix9
-rw-r--r--tvix/eval/src/tests/nix_tests/parse-fail-dup-formals.nix1
-rw-r--r--tvix/eval/src/tests/nix_tests/parse-fail-eof-in-string.nix3
-rw-r--r--tvix/eval/src/tests/nix_tests/parse-fail-mixed-nested-attrs1.nix4
-rw-r--r--tvix/eval/src/tests/nix_tests/parse-fail-mixed-nested-attrs2.nix4
-rw-r--r--tvix/eval/src/tests/nix_tests/parse-fail-patterns-1.nix1
-rw-r--r--tvix/eval/src/tests/nix_tests/parse-fail-regression-20060610.nix11
-rw-r--r--tvix/eval/src/tests/nix_tests/parse-fail-uft8.nix1
-rw-r--r--tvix/eval/src/tests/nix_tests/parse-fail-undef-var-2.nix7
-rw-r--r--tvix/eval/src/tests/nix_tests/parse-fail-undef-var.nix1
-rw-r--r--tvix/eval/src/tests/nix_tests/parse-okay-1.nix1
-rw-r--r--tvix/eval/src/tests/nix_tests/parse-okay-crlf.nix17
-rw-r--r--tvix/eval/src/tests/nix_tests/parse-okay-dup-attrs-5.nix4
-rw-r--r--tvix/eval/src/tests/nix_tests/parse-okay-dup-attrs-6.nix4
-rw-r--r--tvix/eval/src/tests/nix_tests/parse-okay-mixed-nested-attrs-1.nix4
-rw-r--r--tvix/eval/src/tests/nix_tests/parse-okay-mixed-nested-attrs-2.nix4
-rw-r--r--tvix/eval/src/tests/nix_tests/parse-okay-mixed-nested-attrs-3.nix7
-rw-r--r--tvix/eval/src/tests/nix_tests/parse-okay-regression-20041027.nix11
-rw-r--r--tvix/eval/src/tests/nix_tests/parse-okay-regression-751.nix2
-rw-r--r--tvix/eval/src/tests/nix_tests/parse-okay-subversion.nix43
-rw-r--r--tvix/eval/src/tests/nix_tests/parse-okay-url.nix8
-rw-r--r--tvix/eval/src/tests/one_offs.rs36
-rw-r--r--tvix/eval/src/tests/tvix_tests/README.md19
-rw-r--r--tvix/eval/src/tests/tvix_tests/directory/default.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-fail-builtins-substring-negative-start.nix3
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-fail-builtins-thunk-error.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-fail-builtins-tojson-tostring-notcallable.nix5
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-fail-builtins-tojson-tostring-strong.nix6
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-fail-closed-formals.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-fail-deep-forced-thunk-error.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-fail-deepseq.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-fail-division-by-zero-float.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-fail-division-by-zero-int.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-fail-foldlStrict-strict-op-application.nix4
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-fail-force-before-value-pointer-equality.nix5
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-fail-function-formals-typecheck.nix2
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-fail-getEnv-coercion.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-fail-infinite-recursion.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-fail-outer-value-never-pointer-equal.nix7
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-fail-parsedrvname-coerce.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-fail-remove.nix5
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-fail-seq.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-fail-throw-abort-cannot-be-caught.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-abort-throw-can-be-caught.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-abort-throw-can-be-caught.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-access-strange-identifier.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-access-strange-identifier.nix9
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-add-paths.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-add-paths.nix9
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-arithmetic-float.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-arithmetic-float.nix6
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-arithmetic-int.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-arithmetic-int.nix6
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-assert-thunk-condition.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-assert-thunk-condition.nix7
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-attempt-to-call-catchable.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-attempt-to-call-catchable.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-attr-key-catchable.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-attr-key-catchable.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-attrs-inherit-literal.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-attrs-inherit-literal.nix2
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-attrs-simple-inherit.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-attrs-simple-inherit.nix4
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-attrs-update-empty-lhs.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-attrs-update-empty-lhs.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-attrs-update-empty-rhs.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-attrs-update-empty-rhs.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-attrs-update-kv-lhs.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-attrs-update-kv-lhs.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-attrs-update.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-attrs-update.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-basenameof-propagate-catchables.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-basenameof-propagate-catchables.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-basenameof.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-basenameof.nix10
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-add.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-add.nix8
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-all-propagate-catchable.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-all-propagate-catchable.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-all.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-all.nix15
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-any-propagate-catchable.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-any-propagate-catchable.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-any.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-any.nix15
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-attrnames.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-attrnames.nix13
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-attrvalues-propagate-catchable.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-attrvalues-propagate-catchable.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-attrvalues.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-attrvalues.nix4
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-bitand.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-bitand.nix10
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-bitor.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-bitor.nix10
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-bitxor.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-bitxor.nix12
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-builtins.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-builtins.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-catAttrs.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-catAttrs.nix10
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-catattrs-propagate-catchable.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-catattrs-propagate-catchable.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-compareVersions.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-compareVersions.nix46
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-concat-lists-propagate-catchable.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-concat-lists-propagate-catchable.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-concat-lists.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-concat-lists.nix6
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-concat-map-propagate-catchable.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-concat-map-propagate-catchable.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-concat-strings-sep-propagate-catchable.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-concat-strings-sep-propagate-catchable.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-div.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-div.nix34
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-elemAt-catchable.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-elemAt-catchable.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-elemat.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-elemat.nix5
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-filter-catchable.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-filter-catchable.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-filter-propagate-catchable.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-filter-propagate-catchable.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-filter.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-filter.nix13
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-foldl-propagate-catchable.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-foldl-propagate-catchable.nix8
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-from-json-propagate-catchable.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-from-json-propagate-catchable.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-function-args-propagate-catchable.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-function-args-propagate-catchable.nix4
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-gen-list-propagate-catchable.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-gen-list-propagate-catchable.nix5
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-genList-function-strictness.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-genList-function-strictness.nix8
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-genericClosure-pointer-equality.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-genericClosure-pointer-equality.nix15
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-genericClosure-propagate-catchable.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-genericClosure-propagate-catchable.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-getAttr-catchable.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-getAttr-catchable.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-getContext-propagate-catchable.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-getContext-propagate-catchable.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-getattr.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-getattr.nix6
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-group-by-propagate-catchable.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-group-by-propagate-catchable.nix5
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-groupby-thunk.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-groupby-thunk.nix5
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-hasContext-propagate-catchable.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-hasContext-propagate-catchable.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-hasattr.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-hasattr.nix9
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-hashString.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-hashString.nix6
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-head-propagate-catchable.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-head-propagate-catchable.nix4
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-head.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-head.nix4
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-isType-propagate-catchable.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-isType-propagate-catchable.nix14
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-length-catchable.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-length-catchable.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-length.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-length.nix5
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-lessThan.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-lessThan.nix15
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-list-to-attrs-propagate-catchable.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-list-to-attrs-propagate-catchable.nix7
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-map-function-strictness.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-map-function-strictness.nix8
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-map-propagate-catchable.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-map-propagate-catchable.nix5
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-map.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-map.nix19
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-mapAttrs-function-strictness.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-mapAttrs-function-strictness.nix8
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-match-propagate-catchables.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-match-propagate-catchables.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-mul.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-mul.nix7
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-parse-drv-name-propagate-catchable.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-parse-drv-name-propagate-catchable.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-partition-propagate-catchable.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-partition-propagate-catchable.nix5
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-partition.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-partition.nix13
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-remove-attrs-propagate-catchable.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-remove-attrs-propagate-catchable.nix6
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-replace-strings-propagate-catchable.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-replace-strings-propagate-catchable.nix16
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-replaceStrings.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-replaceStrings.nix6
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-sort-propagate-catchable.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-sort-propagate-catchable.nix6
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-split-propagate-catchable.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-split-propagate-catchable.nix4
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-splitVersion.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-splitVersion.nix13
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-splitversion-catchable.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-splitversion-catchable.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-string-length-propagate-catchable.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-string-length-propagate-catchable.nix4
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-string-length.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-string-length.nix10
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-sub.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-sub.nix8
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-substring-coerce.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-substring-coerce.nix5
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-substring-negative-length.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-substring-negative-length.nix5
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-substring.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-substring.nix18
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-tail-propagate-catchable.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-tail-propagate-catchable.nix6
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-tail.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-tail.nix4
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-thunked-function-calls.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-thunked-function-calls.nix31
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-to-json-propagate-catchable.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-to-json-propagate-catchable.nix14
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-to-path-propagate-catchable.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-to-path-propagate-catchable.nix5
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-to-string-propagate-catchable.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-to-string-propagate-catchable.nix7
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-to-xml-propagate-catchable.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-to-xml-propagate-catchable.nix15
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-toString.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-toString.nix28
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-tojson-literals.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-tojson-literals.nix11
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-tojson-outpath-nested.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-tojson-outpath-nested.nix8
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-tojson-outpath.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-tojson-outpath.nix5
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-tojson-thunks.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-tojson-thunks.nix9
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-tojson-tostring.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-tojson-tostring.nix8
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-type-of-propagate-catchable.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-type-of-propagate-catchable.nix9
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-type-of.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-type-of.nix22
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-type-predicates.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-type-predicates.nix34
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-unsafe-discard-string-context-propagate-catchable.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-unsafe-discard-string-context-propagate-catchable.nix4
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-catchable-double-throw.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-catchable-double-throw.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-catchable-in-attrNames.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-catchable-in-attrNames.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-catchable-in-inequality.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-catchable-in-inequality.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-catchable-in-intersectattrs.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-catchable-in-intersectattrs.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-catchable-in-string-interpolation.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-catchable-in-string-interpolation.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-catchable-in-update-attrs.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-catchable-in-update-attrs.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-catchable-passed-to-function-with-formals.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-catchable-passed-to-function-with-formals.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-ceil.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-ceil.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-closure-pointer-compare.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-closure-pointer-compare.nix14
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-closure-self.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-closure-self.nix5
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-closure-with-shadowing.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-closure-with-shadowing.nix14
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-cmp-float-false.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-cmp-float-false.nix8
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-cmp-float-true.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-cmp-float-true.nix8
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-cmp-int-false.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-cmp-int-false.nix8
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-cmp-int-true.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-cmp-int-true.nix8
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-cmp-num-false.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-cmp-num-false.nix8
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-cmp-num-true.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-cmp-num-true.nix8
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-cmp-str-false.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-cmp-str-false.nix8
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-cmp-str-true.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-cmp-str-true.nix8
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-coerce-opadd.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-coerce-opadd.nix7
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-compare-lists.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-compare-lists.nix17
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-compare-ordering-catchable.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-compare-ordering-catchable.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-concat-lists.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-concat-lists.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-concat-strings.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-concat-strings.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-concatmap.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-concatmap.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-concatstringssep.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-concatstringssep.nix9
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-contains-nested-non-set.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-contains-nested-non-set.nix3
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-contains-non-set.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-contains-non-set.nix3
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-deeply-nested-attrs.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-deeply-nested-attrs.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-deeply-nested-with-closure.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-deeply-nested-with-closure.nix21
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-deeply-nested-with.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-deeply-nested-with.nix6
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-deepseq.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-deepseq.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-deferred-unary-formals.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-deferred-unary-formals.nix6
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-deferred-with.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-deferred-with.nix9
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-dirof.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-dirof.nix10
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-elem.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-elem.nix6
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-empty-rec-inherit.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-empty-rec-inherit.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-eq-float.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-eq-float.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-eq-int.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-eq-int.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-eq-nested-list.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-eq-nested-list.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-equality-tolerate-catchable-in-type-field.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-equality-tolerate-catchable-in-type-field.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-escape-string-correct-char-boundaries.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-escape-string-correct-char-boundaries.nix6
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-escapify-integer-keys.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-escapify-integer-keys.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-fib.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-fib.nix9
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-fix.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-fix.nix8
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-float-repr.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-float-repr.nix2
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-floor.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-floor.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-foldlStrict-lazy-elements.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-foldlStrict-lazy-elements.nix8
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-foldlStrict-lazy-initial-accumulator.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-foldlStrict-lazy-initial-accumulator.nix4
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-foldlStrict.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-foldlStrict.nix5
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-formals-miscompilation-b-261-regression.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-formals-miscompilation-b-261-regression.nix20
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-fromjson-escapes.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-fromjson-escapes.nix3
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-fromjson.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-fromjson.nix24
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-functionargs.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-functionargs.nix83
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-functor-call.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-functor-call.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-genlist.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-genlist.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-hasattr-catchable.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-hasattr-catchable.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-identifier-formatting.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-identifier-formatting.nix42
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-import-display.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-import-display.nix2
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-import.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-import.nix4
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-inherit-string-ident.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-inherit-string-ident.nix8
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-internal-formals-deferred.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-internal-formals-deferred.nix3
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-internal-formals.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-internal-formals.nix3
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-intersectattrs.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-intersectattrs.nix3
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-lambda-identity.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-lambda-identity.nix2
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-late-binding-closure.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-late-binding-closure.nix5
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-late-binding.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-late-binding.nix5
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-lazy-assert.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-lazy-assert.nix8
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-lazy-equality.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-lazy-equality.nix16
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-lazy-with-nested.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-lazy-with-nested.nix5
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-lazy-with.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-lazy-with.nix6
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-legacy-let-fix.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-legacy-let-fix.nix9
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-legacy-let-in-with.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-legacy-let-in-with.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-legacy-let.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-legacy-let.nix4
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-let-identifiers.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-let-identifiers.nix6
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-let-inherit-from-later-bound.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-let-inherit-from-later-bound.nix13
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-let-inherit.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-let-inherit.nix13
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-let-sibling-access.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-let-sibling-access.nix6
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-let-useful-plain-inherit-mixed.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-let-useful-plain-inherit-mixed.nix20
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-let-useful-plain-inherit.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-let-useful-plain-inherit.nix9
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-list-comparison.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-list-comparison.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-listtoattrs.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-listtoattrs.nix16
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-logical-and-catchable.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-logical-and-catchable.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-logical-or-catchable.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-logical-or-catchable.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-manual-rec.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-manual-rec.nix10
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-merge-nested-attrs.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-merge-nested-attrs.nix9
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-merge-nested-rec-attrs.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-merge-nested-rec-attrs.nix12
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-multiline-string.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-multiline-string.nix2
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-multiple-nested-attrs.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-multiple-nested-attrs.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-mutually-recursive-let-binding.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-mutually-recursive-let-binding.nix14
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-ne-int.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-ne-int.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-ne-string.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-ne-string.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-nested-assertions.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-nested-assertions.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-nested-closure.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-nested-closure.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-nested-deferred-upvalue.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-nested-deferred-upvalue.nix10
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-nested-has-attrs.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-nested-has-attrs.nix26
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-nested-keys-let.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-nested-keys-let.nix5
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-nested-keys-rec.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-nested-keys-rec.nix4
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-nested-let-slots.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-nested-let-slots.nix22
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-nested-let.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-nested-let.nix10
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-nested-poisoning.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-nested-poisoning.nix5
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-nested-set-thunks.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-nested-set-thunks.nix5
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-nested-siblings.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-nested-siblings.nix7
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-nested-thunks.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-nested-thunks.nix8
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-nested-with.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-nested-with.nix5
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-nix-version-cmp.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-nix-version-cmp.nix5
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-observable-eval-cache.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-observable-eval-cache.nix7
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-observe-infinite-attrs.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-observe-infinite-attrs.nix4
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-optimised-bools.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-optimised-bools.nix21
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-or-operator-default.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-or-operator-default.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-or-operator-nested-default.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-or-operator-nested-default.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-or-operator-nested.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-or-operator-nested.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-or-operator-non-set.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-or-operator-non-set.nix2
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-or-operator.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-or-operator.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-overlapping-nested-attrs.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-overlapping-nested-attrs.nix4
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-parsedrvname.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-parsedrvname.nix11
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-pathexists.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-pathexists.nix2
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-poisoned-scopes.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-poisoned-scopes.nix6
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-readDir.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-readDir.nix.disabled1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-readfile.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-readfile.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-rec-dynamic-keys.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-rec-dynamic-keys.nix5
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-rec-nested-access.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-rec-nested-access.nix4
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-recursive-attrs-all-features.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-recursive-attrs-all-features.nix13
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-regex-match.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-regex-match.nix29
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-remove.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-remove.nix5
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-repeated-list-to-attrs.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-repeated-list-to-attrs.nix14
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-seq.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-seq.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-simple-closure.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-simple-closure.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-simple-interpol.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-simple-interpol.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-simple-let.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-simple-let.nix5
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-simple-nested-attrs.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-simple-nested-attrs.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-simple-recursive-attrs.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-simple-recursive-attrs.nix4
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-simple-with.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-simple-with.nix6
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-stable-sort.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-stable-sort.nix7
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-substring-propagate-catchables.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-substring-propagate-catchables.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-test-catchables-in-default-args.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-test-catchables-in-default-args.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-test-catchables-in-implications.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-test-catchables-in-implications.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-thunked-functor.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-thunked-functor.nix8
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-thunked-if.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-thunked-if.nix8
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-thunked-string-interpolation.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-thunked-string-interpolation.nix7
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-thunked-with.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-thunked-with.nix8
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-toplevel-finaliser.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-toplevel-finaliser.nix11
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-tryeval-thunk-twice.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-tryeval-thunk-twice.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-tryeval.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-tryeval.nix7
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-unpoison-scope.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-unpoison-scope.nix10
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-useless-inherit-with.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-useless-inherit-with.nix16
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-value-display.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-value-display.nix16
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-value-pointer-compare.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-value-pointer-compare.nix6
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-value-pointer-equality.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-value-pointer-equality.nix46
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-with-closure.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-with-closure.nix5
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-with-in-dynamic-key.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-with-in-dynamic-key.nix13
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-with-in-list.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-with-in-list.nix14
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-xml.exp.xml41
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-xml.nix7
-rw-r--r--tvix/eval/src/tests/tvix_tests/identity-bool-false.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/identity-bool-true.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/identity-dollar-escape.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/identity-empty-attrs.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/identity-empty-list.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/identity-flat-attrs.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/identity-float.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/identity-heterogeneous-list.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/identity-homogeneous-float-list.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/identity-homogeneous-int-list.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/identity-homogeneous-string-list.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/identity-int.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/identity-kv-attrs.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/identity-nested-attrs.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/identity-null.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/identity-quoted-attrname-assert.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/identity-quoted-attrname-else.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/identity-quoted-attrname-if.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/identity-quoted-attrname-in.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/identity-quoted-attrname-inherit.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/identity-quoted-attrname-let.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/identity-quoted-attrname-rec.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/identity-quoted-attrname-then.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/identity-quoted-attrname-with.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/identity-signed-float.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/identity-signed-int.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/identity-string.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/lib.nix64
-rw-r--r--tvix/eval/src/tests/tvix_tests/notyetpassing/eval-fail-builtins-genericClosure-uncomparable-keys.nix9
-rw-r--r--tvix/eval/src/tests/tvix_tests/notyetpassing/eval-fail-builtins-genericClosure-uncomparable-keys2.nix12
-rw-r--r--tvix/eval/src/tests/tvix_tests/notyetpassing/eval-okay-builtins-set-pointer-equality.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/notyetpassing/eval-okay-builtins-set-pointer-equality.nix25
-rw-r--r--tvix/eval/src/tests/tvix_tests/notyetpassing/eval-okay-cycle-display-cpp-nix-2.13.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/notyetpassing/eval-okay-cycle-display-cpp-nix-2.13.nix34
-rw-r--r--tvix/eval/src/tests/tvix_tests/notyetpassing/eval-okay-minimal-2.3-builtins.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/notyetpassing/eval-okay-minimal-2.3-builtins.nix122
-rw-r--r--tvix/eval/src/tests/tvix_tests/notyetpassing/eval-okay-non-identifier-pointer-inequality.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/notyetpassing/eval-okay-non-identifier-pointer-inequality.nix28
-rw-r--r--tvix/eval/src/tests/tvix_tests/observable-eval-cache1.nix1
l---------tvix/eval/src/tests/tvix_tests/observable-eval-cache2.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/observable-eval-cache3.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/readDir/bar0
-rw-r--r--tvix/eval/src/tests/tvix_tests/readDir/foo/.keep0
-rw-r--r--tvix/eval/src/upvalues.rs86
-rw-r--r--tvix/eval/src/value/arbitrary.rs106
-rw-r--r--tvix/eval/src/value/attrs.rs622
-rw-r--r--tvix/eval/src/value/attrs/tests.rs106
-rw-r--r--tvix/eval/src/value/builtin.rs137
-rw-r--r--tvix/eval/src/value/function.rs112
-rw-r--r--tvix/eval/src/value/json.rs154
-rw-r--r--tvix/eval/src/value/list.rs98
-rw-r--r--tvix/eval/src/value/mod.rs1051
-rw-r--r--tvix/eval/src/value/path.rs14
-rw-r--r--tvix/eval/src/value/string.rs872
-rw-r--r--tvix/eval/src/value/thunk.rs434
-rw-r--r--tvix/eval/src/vm.rs385
-rw-r--r--tvix/eval/src/vm/generators.rs809
-rw-r--r--tvix/eval/src/vm/macros.rs93
-rw-r--r--tvix/eval/src/vm/mod.rs1368
-rw-r--r--tvix/eval/src/warnings.rs152
-rw-r--r--tvix/eval/tests/nix_oracle.rs171
-rw-r--r--tvix/glue/Cargo.toml53
-rw-r--r--tvix/glue/benches/eval.rs77
-rw-r--r--tvix/glue/default.nix8
-rw-r--r--tvix/glue/src/.skip-subtree1
-rw-r--r--tvix/glue/src/builtins/derivation.nix36
-rw-r--r--tvix/glue/src/builtins/derivation.rs552
-rw-r--r--tvix/glue/src/builtins/errors.rs76
-rw-r--r--tvix/glue/src/builtins/fetchers.rs186
-rw-r--r--tvix/glue/src/builtins/import.rs287
-rw-r--r--tvix/glue/src/builtins/mod.rs797
-rw-r--r--tvix/glue/src/builtins/utils.rs36
-rw-r--r--tvix/glue/src/fetchers/decompression.rs222
-rw-r--r--tvix/glue/src/fetchers/mod.rs445
-rw-r--r--tvix/glue/src/fetchurl.nix53
-rw-r--r--tvix/glue/src/known_paths.rs289
-rw-r--r--tvix/glue/src/lib.rs23
-rw-r--r--tvix/glue/src/refscan.rs115
-rw-r--r--tvix/glue/src/tests/0hm2f1psjpcwg8fijsmr4wwxrx59s092-bar.drv1
-rw-r--r--tvix/glue/src/tests/blob.tar.bz2bin0 -> 116 bytes
-rw-r--r--tvix/glue/src/tests/blob.tar.gzbin0 -> 116 bytes
-rw-r--r--tvix/glue/src/tests/blob.tar.xzbin0 -> 172 bytes
-rw-r--r--tvix/glue/src/tests/ch49594n9avinrf8ip0aslidkc4lxkqv-foo.drv1
-rw-r--r--tvix/glue/src/tests/mod.rs153
-rw-r--r--tvix/glue/src/tests/nix_tests/eval-okay-context-introspection.exp1
-rw-r--r--tvix/glue/src/tests/nix_tests/eval-okay-context-introspection.nix42
-rw-r--r--tvix/glue/src/tests/nix_tests/eval-okay-context.exp1
-rw-r--r--tvix/glue/src/tests/nix_tests/eval-okay-context.nix6
-rw-r--r--tvix/glue/src/tests/ss2p4wmxijn652haqyd7dckxwl4c7hxx-bar.drv1
-rw-r--r--tvix/glue/src/tests/tvix_tests/eval-okay-context-introspection.exp1
-rw-r--r--tvix/glue/src/tests/tvix_tests/eval-okay-context-introspection.nix83
-rw-r--r--tvix/glue/src/tests/tvix_tests/eval-okay-context-propagation.exp1
-rw-r--r--tvix/glue/src/tests/tvix_tests/eval-okay-context-propagation.nix119
-rw-r--r--tvix/glue/src/tests/tvix_tests/eval-okay-fetchtarball.exp1
-rw-r--r--tvix/glue/src/tests/tvix_tests/eval-okay-fetchtarball.nix42
-rw-r--r--tvix/glue/src/tests/tvix_tests/eval-okay-fetchurl.exp1
-rw-r--r--tvix/glue/src/tests/tvix_tests/eval-okay-fetchurl.nix25
-rw-r--r--tvix/glue/src/tvix_build.rs439
-rw-r--r--tvix/glue/src/tvix_io.rs66
-rw-r--r--tvix/glue/src/tvix_store_io.rs715
-rw-r--r--tvix/logo.webpbin0 -> 82366 bytes
-rw-r--r--tvix/nar-bridge/.gitignore2
-rw-r--r--tvix/nar-bridge/README.md7
-rw-r--r--tvix/nar-bridge/cmd/nar-bridge-http/main.go93
-rw-r--r--tvix/nar-bridge/cmd/nar-bridge-http/otel.go87
-rw-r--r--tvix/nar-bridge/default.nix10
-rw-r--r--tvix/nar-bridge/go.mod54
-rw-r--r--tvix/nar-bridge/go.sum120
-rw-r--r--tvix/nar-bridge/pkg/http/nar_get.go197
-rw-r--r--tvix/nar-bridge/pkg/http/nar_put.go141
-rw-r--r--tvix/nar-bridge/pkg/http/narinfo.go51
-rw-r--r--tvix/nar-bridge/pkg/http/narinfo_get.go137
-rw-r--r--tvix/nar-bridge/pkg/http/narinfo_put.go103
-rw-r--r--tvix/nar-bridge/pkg/http/server.go119
-rw-r--r--tvix/nar-bridge/pkg/http/util.go24
-rw-r--r--tvix/nar-bridge/pkg/importer/blob_upload.go71
-rw-r--r--tvix/nar-bridge/pkg/importer/counting_writer.go21
-rw-r--r--tvix/nar-bridge/pkg/importer/directory_upload.go88
-rw-r--r--tvix/nar-bridge/pkg/importer/gen_pathinfo.go62
-rw-r--r--tvix/nar-bridge/pkg/importer/importer.go303
-rw-r--r--tvix/nar-bridge/pkg/importer/importer_test.go537
-rw-r--r--tvix/nar-bridge/pkg/importer/roundtrip_test.go85
-rw-r--r--tvix/nar-bridge/pkg/importer/util_test.go34
-rw-r--r--tvix/nar-bridge/testdata/emptydirectory.narbin0 -> 96 bytes
-rw-r--r--tvix/nar-bridge/testdata/nar_1094wph9z4nwlgvsd53abfz8i117ykiv5dwnq9nnhz846s7xqd7d.narbin0 -> 464152 bytes
-rw-r--r--tvix/nar-bridge/testdata/onebyteexecutable.narbin0 -> 152 bytes
-rw-r--r--tvix/nar-bridge/testdata/onebyteregular.narbin0 -> 120 bytes
-rw-r--r--tvix/nar-bridge/testdata/popdirectories.narbin0 -> 600 bytes
-rw-r--r--tvix/nar-bridge/testdata/symlink.narbin0 -> 136 bytes
-rw-r--r--tvix/nix-compat/Cargo.toml56
-rw-r--r--tvix/nix-compat/benches/derivation_parse_aterm.rs31
-rw-r--r--tvix/nix-compat/benches/narinfo_parse.rs69
-rw-r--r--tvix/nix-compat/default.nix7
-rw-r--r--tvix/nix-compat/src/aterm/escape.rs28
-rw-r--r--tvix/nix-compat/src/aterm/mod.rs7
-rw-r--r--tvix/nix-compat/src/aterm/parser.rs125
-rw-r--r--tvix/nix-compat/src/bin/drvfmt.rs42
-rw-r--r--tvix/nix-compat/src/derivation/errors.rs60
-rw-r--r--tvix/nix-compat/src/derivation/mod.rs305
-rw-r--r--tvix/nix-compat/src/derivation/output.rs189
-rw-r--r--tvix/nix-compat/src/derivation/parse_error.rs87
-rw-r--r--tvix/nix-compat/src/derivation/parser.rs585
-rw-r--r--tvix/nix-compat/src/derivation/tests/derivation_tests/duplicate.drv1
-rw-r--r--tvix/nix-compat/src/derivation/tests/derivation_tests/ok/0hm2f1psjpcwg8fijsmr4wwxrx59s092-bar.drv1
-rw-r--r--tvix/nix-compat/src/derivation/tests/derivation_tests/ok/0hm2f1psjpcwg8fijsmr4wwxrx59s092-bar.drv.json23
-rw-r--r--tvix/nix-compat/src/derivation/tests/derivation_tests/ok/292w8yzv5nn7nhdpxcs8b7vby2p27s09-nested-json.drv1
-rw-r--r--tvix/nix-compat/src/derivation/tests/derivation_tests/ok/292w8yzv5nn7nhdpxcs8b7vby2p27s09-nested-json.drv.json19
-rw-r--r--tvix/nix-compat/src/derivation/tests/derivation_tests/ok/4wvvbi4jwn0prsdxb7vs673qa5h9gr7x-foo.drv1
-rw-r--r--tvix/nix-compat/src/derivation/tests/derivation_tests/ok/4wvvbi4jwn0prsdxb7vs673qa5h9gr7x-foo.drv.json23
-rw-r--r--tvix/nix-compat/src/derivation/tests/derivation_tests/ok/52a9id8hx688hvlnz4d1n25ml1jdykz0-unicode.drv1
-rw-r--r--tvix/nix-compat/src/derivation/tests/derivation_tests/ok/52a9id8hx688hvlnz4d1n25ml1jdykz0-unicode.drv.json19
-rw-r--r--tvix/nix-compat/src/derivation/tests/derivation_tests/ok/9lj1lkjm2ag622mh4h9rpy6j607an8g2-structured-attrs.drv1
-rw-r--r--tvix/nix-compat/src/derivation/tests/derivation_tests/ok/9lj1lkjm2ag622mh4h9rpy6j607an8g2-structured-attrs.drv.json16
-rw-r--r--tvix/nix-compat/src/derivation/tests/derivation_tests/ok/ch49594n9avinrf8ip0aslidkc4lxkqv-foo.drv1
-rw-r--r--tvix/nix-compat/src/derivation/tests/derivation_tests/ok/ch49594n9avinrf8ip0aslidkc4lxkqv-foo.drv.json23
-rw-r--r--tvix/nix-compat/src/derivation/tests/derivation_tests/ok/h32dahq0bx5rp1krcdx3a53asj21jvhk-has-multi-out.drv1
-rw-r--r--tvix/nix-compat/src/derivation/tests/derivation_tests/ok/h32dahq0bx5rp1krcdx3a53asj21jvhk-has-multi-out.drv.json23
-rw-r--r--tvix/nix-compat/src/derivation/tests/derivation_tests/ok/m1vfixn8iprlf0v9abmlrz7mjw1xj8kp-cp1252.drv1
-rw-r--r--tvix/nix-compat/src/derivation/tests/derivation_tests/ok/m1vfixn8iprlf0v9abmlrz7mjw1xj8kp-cp1252.drv.json21
-rw-r--r--tvix/nix-compat/src/derivation/tests/derivation_tests/ok/ss2p4wmxijn652haqyd7dckxwl4c7hxx-bar.drv1
-rw-r--r--tvix/nix-compat/src/derivation/tests/derivation_tests/ok/ss2p4wmxijn652haqyd7dckxwl4c7hxx-bar.drv.json23
-rw-r--r--tvix/nix-compat/src/derivation/tests/derivation_tests/ok/x6p0hg79i3wg0kkv7699935f7rrj9jf3-latin1.drv1
-rw-r--r--tvix/nix-compat/src/derivation/tests/derivation_tests/ok/x6p0hg79i3wg0kkv7699935f7rrj9jf3-latin1.drv.json21
-rw-r--r--tvix/nix-compat/src/derivation/tests/mod.rs436
-rw-r--r--tvix/nix-compat/src/derivation/validate.rs141
-rw-r--r--tvix/nix-compat/src/derivation/write.rs257
-rw-r--r--tvix/nix-compat/src/lib.rs18
-rw-r--r--tvix/nix-compat/src/nar/mod.rs4
-rw-r--r--tvix/nix-compat/src/nar/reader/async/mod.rs166
-rw-r--r--tvix/nix-compat/src/nar/reader/async/read.rs69
-rw-r--r--tvix/nix-compat/src/nar/reader/async/test.rs310
-rw-r--r--tvix/nix-compat/src/nar/reader/mod.rs482
-rw-r--r--tvix/nix-compat/src/nar/reader/read.rs109
-rw-r--r--tvix/nix-compat/src/nar/reader/test.rs278
-rw-r--r--tvix/nix-compat/src/nar/tests/complicated.narbin0 -> 840 bytes
-rw-r--r--tvix/nix-compat/src/nar/tests/helloworld.narbin0 -> 128 bytes
-rw-r--r--tvix/nix-compat/src/nar/tests/symlink.narbin0 -> 136 bytes
-rw-r--r--tvix/nix-compat/src/nar/wire/mod.rs150
-rw-r--r--tvix/nix-compat/src/nar/wire/tag.rs166
-rw-r--r--tvix/nix-compat/src/nar/writer/async.rs235
-rw-r--r--tvix/nix-compat/src/nar/writer/mod.rs9
-rw-r--r--tvix/nix-compat/src/nar/writer/sync.rs224
-rw-r--r--tvix/nix-compat/src/nar/writer/test.rs128
-rw-r--r--tvix/nix-compat/src/narinfo/fingerprint.rs50
-rw-r--r--tvix/nix-compat/src/narinfo/mod.rs527
-rw-r--r--tvix/nix-compat/src/narinfo/public_keys.rs152
-rw-r--r--tvix/nix-compat/src/narinfo/signature.rs184
-rw-r--r--tvix/nix-compat/src/nix_daemon/mod.rs4
-rw-r--r--tvix/nix-compat/src/nix_daemon/protocol_version.rs123
-rw-r--r--tvix/nix-compat/src/nix_daemon/worker_protocol.rs434
-rw-r--r--tvix/nix-compat/src/nixbase32.rs206
-rw-r--r--tvix/nix-compat/src/nixhash/algos.rs75
-rw-r--r--tvix/nix-compat/src/nixhash/ca_hash.rs343
-rw-r--r--tvix/nix-compat/src/nixhash/mod.rs602
-rw-r--r--tvix/nix-compat/src/path_info.rs121
-rw-r--r--tvix/nix-compat/src/store_path/mod.rs635
-rw-r--r--tvix/nix-compat/src/store_path/utils.rs293
-rw-r--r--tvix/nix-compat/src/wire/bytes/mod.rs229
-rw-r--r--tvix/nix-compat/src/wire/bytes/reader/mod.rs447
-rw-r--r--tvix/nix-compat/src/wire/bytes/reader/trailer.rs198
-rw-r--r--tvix/nix-compat/src/wire/bytes/writer.rs538
-rw-r--r--tvix/nix-compat/src/wire/mod.rs5
-rw-r--r--tvix/nix-compat/testdata/narinfo.zstbin0 -> 975945 bytes
-rw-r--r--tvix/nix-lang-test-suite/README.md140
-rw-r--r--tvix/nix_cli/Cargo.lock248
-rw-r--r--tvix/nix_cli/Cargo.toml14
-rw-r--r--tvix/nix_cli/default.nix2
-rw-r--r--tvix/nix_cli/shell.nix11
-rw-r--r--tvix/nix_cli/src/bin/nix-store.rs104
-rw-r--r--tvix/nix_cli/src/main.rs3
-rw-r--r--tvix/proto/default.nix9
-rw-r--r--tvix/proto/evaluator.proto144
-rwxr-xr-xtvix/scripts/bench-windtunnel.sh27
-rw-r--r--tvix/serde/.skip-subtree1
-rw-r--r--tvix/serde/Cargo.toml9
-rw-r--r--tvix/serde/default.nix5
-rw-r--r--tvix/serde/examples/cfg-demo.rs35
-rw-r--r--tvix/serde/examples/foods.nix22
-rw-r--r--tvix/serde/examples/nixpkgs.rs34
-rw-r--r--tvix/serde/src/de.rs475
-rw-r--r--tvix/serde/src/de_tests.rs245
-rw-r--r--tvix/serde/src/error.rs99
-rw-r--r--tvix/serde/src/lib.rs12
-rw-r--r--tvix/shell.nix62
-rw-r--r--tvix/store-go/LICENSE21
-rw-r--r--tvix/store-go/README.md10
-rw-r--r--tvix/store-go/default.nix31
-rw-r--r--tvix/store-go/export.go273
-rw-r--r--tvix/store-go/export_test.go134
-rw-r--r--tvix/store-go/go.mod25
-rw-r--r--tvix/store-go/go.sum47
-rw-r--r--tvix/store-go/pathinfo.go99
-rw-r--r--tvix/store-go/pathinfo.pb.go657
-rw-r--r--tvix/store-go/pathinfo_test.go149
-rw-r--r--tvix/store-go/pick_next_node_test.go51
-rw-r--r--tvix/store-go/rpc_pathinfo.pb.go347
-rw-r--r--tvix/store-go/rpc_pathinfo_grpc.pb.go308
-rw-r--r--tvix/store-go/testdata/emptydirectory.narbin0 -> 96 bytes
-rw-r--r--tvix/store-go/testdata/onebyteregular.narbin0 -> 120 bytes
-rw-r--r--tvix/store-go/testdata/symlink.narbin0 -> 136 bytes
-rw-r--r--tvix/store/Cargo.toml79
-rw-r--r--tvix/store/README.md63
-rw-r--r--tvix/store/build.rs38
-rw-r--r--tvix/store/default.nix52
-rw-r--r--tvix/store/docs/api.md288
-rw-r--r--tvix/store/protos/LICENSE21
-rw-r--r--tvix/store/protos/default.nix55
-rw-r--r--tvix/store/protos/pathinfo.proto128
-rw-r--r--tvix/store/protos/rpc_pathinfo.proto76
-rw-r--r--tvix/store/src/bin/tvix-store.rs563
-rw-r--r--tvix/store/src/import.rs180
-rw-r--r--tvix/store/src/lib.rs14
-rw-r--r--tvix/store/src/nar/import.rs228
-rw-r--r--tvix/store/src/nar/mod.rs26
-rw-r--r--tvix/store/src/nar/renderer.rs187
-rw-r--r--tvix/store/src/pathinfoservice/bigtable.rs401
-rw-r--r--tvix/store/src/pathinfoservice/from_addr.rs236
-rw-r--r--tvix/store/src/pathinfoservice/fs/mod.rs81
-rw-r--r--tvix/store/src/pathinfoservice/grpc.rs216
-rw-r--r--tvix/store/src/pathinfoservice/memory.rs84
-rw-r--r--tvix/store/src/pathinfoservice/mod.rs85
-rw-r--r--tvix/store/src/pathinfoservice/nix_http.rs278
-rw-r--r--tvix/store/src/pathinfoservice/sled.rs125
-rw-r--r--tvix/store/src/pathinfoservice/tests/mod.rs113
-rw-r--r--tvix/store/src/pathinfoservice/tests/utils.rs60
-rw-r--r--tvix/store/src/proto/grpc_pathinfoservice_wrapper.rs121
-rw-r--r--tvix/store/src/proto/mod.rs374
-rw-r--r--tvix/store/src/proto/tests/mod.rs1
-rw-r--r--tvix/store/src/proto/tests/pathinfo.rs431
-rw-r--r--tvix/store/src/tests/fixtures.rs142
-rw-r--r--tvix/store/src/tests/mod.rs2
-rw-r--r--tvix/store/src/tests/nar_renderer.rs228
-rw-r--r--tvix/store/src/utils.rs65
-rw-r--r--tvix/tools/crunch-v2/.gitignore1
-rw-r--r--tvix/tools/crunch-v2/Cargo.lock3214
-rw-r--r--tvix/tools/crunch-v2/Cargo.nix11881
-rw-r--r--tvix/tools/crunch-v2/Cargo.toml39
-rw-r--r--tvix/tools/crunch-v2/OWNERS1
-rw-r--r--tvix/tools/crunch-v2/build.rs6
-rw-r--r--tvix/tools/crunch-v2/default.nix15
-rw-r--r--tvix/tools/crunch-v2/protos/flatstore.proto38
-rw-r--r--tvix/tools/crunch-v2/src/bin/extract.rs155
-rw-r--r--tvix/tools/crunch-v2/src/lib.rs3
-rw-r--r--tvix/tools/crunch-v2/src/main.rs309
-rw-r--r--tvix/tools/crunch-v2/src/remote.rs211
-rw-r--r--tvix/tools/narinfo2parquet/Cargo.lock2144
-rw-r--r--tvix/tools/narinfo2parquet/Cargo.nix8528
-rw-r--r--tvix/tools/narinfo2parquet/Cargo.toml25
-rw-r--r--tvix/tools/narinfo2parquet/OWNERS1
-rw-r--r--tvix/tools/narinfo2parquet/default.nix3
-rw-r--r--tvix/tools/narinfo2parquet/src/main.rs264
-rw-r--r--tvix/tools/turbofetch/Cargo.lock1715
-rw-r--r--tvix/tools/turbofetch/Cargo.nix6466
-rw-r--r--tvix/tools/turbofetch/Cargo.toml28
-rw-r--r--tvix/tools/turbofetch/OWNERS1
-rw-r--r--tvix/tools/turbofetch/default.nix12
-rwxr-xr-xtvix/tools/turbofetch/deploy.sh5
-rw-r--r--tvix/tools/turbofetch/src/buffer.rs83
-rw-r--r--tvix/tools/turbofetch/src/lib.rs103
-rw-r--r--tvix/tools/turbofetch/src/main.rs220
-rw-r--r--tvix/tools/weave/Cargo.lock2179
-rw-r--r--tvix/tools/weave/Cargo.nix8809
-rw-r--r--tvix/tools/weave/Cargo.toml20
-rw-r--r--tvix/tools/weave/OWNERS1
-rw-r--r--tvix/tools/weave/default.nix3
-rw-r--r--tvix/tools/weave/src/bin/swizzle.rs114
-rw-r--r--tvix/tools/weave/src/bytes.rs27
-rw-r--r--tvix/tools/weave/src/lib.rs106
-rw-r--r--tvix/tools/weave/src/main.rs216
-rw-r--r--tvix/verify-lang-tests/default.nix226
-rw-r--r--tvix/website/default.nix46
-rw-r--r--tvix/website/landing-en.md42
1301 files changed, 137929 insertions, 1702 deletions
diff --git a/tvix/.envrc b/tvix/.envrc
deleted file mode 100644
index ea1ec94e43..0000000000
--- a/tvix/.envrc
+++ /dev/null
@@ -1,10 +0,0 @@
-source_env ../.envrc
-
-if type lorri &>/dev/null; then
-    echo "direnv: using lorri from PATH ($(type -p lorri))"
-    eval "$(lorri direnv)"
-else
-    # fall back to using direnv's builtin nix support
-    # to prevent bootstrapping problems.
-    use nix
-fi
diff --git a/tvix/.gitignore b/tvix/.gitignore
index f807dfa42c..e047e8af40 100644
--- a/tvix/.gitignore
+++ b/tvix/.gitignore
@@ -1,3 +1,6 @@
 /target
 /result-*
 /result
+target
+
+/*.sled
diff --git a/tvix/.vscode/extensions.json b/tvix/.vscode/extensions.json
index dd7012c107..0740550041 100644
--- a/tvix/.vscode/extensions.json
+++ b/tvix/.vscode/extensions.json
@@ -1,6 +1,6 @@
 {
     "recommendations": [
-        "matklad.rust-analyzer"
+        "rust-lang.rust-analyzer"
     ],
     "unwantedRecommendations": [
         "rust-lang.rust"
diff --git a/tvix/Cargo.lock b/tvix/Cargo.lock
new file mode 100644
index 0000000000..8385528e8f
--- /dev/null
+++ b/tvix/Cargo.lock
@@ -0,0 +1,5089 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "addr2line"
+version = "0.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb"
+dependencies = [
+ "gimli",
+]
+
+[[package]]
+name = "adler"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "android-tzdata"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
+
+[[package]]
+name = "android_system_properties"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "anes"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
+
+[[package]]
+name = "anstream"
+version = "0.6.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5"
+dependencies = [
+ "anstyle",
+ "anstyle-parse",
+ "anstyle-query",
+ "anstyle-wincon",
+ "colorchoice",
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87"
+
+[[package]]
+name = "anstyle-parse"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
+dependencies = [
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle-query"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648"
+dependencies = [
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "anstyle-wincon"
+version = "3.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
+dependencies = [
+ "anstyle",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.79"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca"
+
+[[package]]
+name = "arc-swap"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6"
+
+[[package]]
+name = "arrayref"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545"
+
+[[package]]
+name = "arrayvec"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
+
+[[package]]
+name = "async-channel"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f28243a43d821d11341ab73c80bed182dc015c514b951616cf79bd4af39af0c3"
+dependencies = [
+ "concurrent-queue",
+ "event-listener 5.2.0",
+ "event-listener-strategy 0.5.0",
+ "futures-core",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "async-compression"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4e9eabd7a98fe442131a17c316bd9349c43695e49e730c3c8e12cfb5f4da2693"
+dependencies = [
+ "bzip2",
+ "flate2",
+ "futures-core",
+ "memchr",
+ "pin-project-lite",
+ "tokio",
+ "xz2",
+ "zstd",
+ "zstd-safe",
+]
+
+[[package]]
+name = "async-io"
+version = "2.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dcccb0f599cfa2f8ace422d3555572f47424da5648a4382a9dd0310ff8210884"
+dependencies = [
+ "async-lock 3.3.0",
+ "cfg-if",
+ "concurrent-queue",
+ "futures-io",
+ "futures-lite",
+ "parking",
+ "polling",
+ "rustix",
+ "slab",
+ "tracing",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "async-lock"
+version = "2.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b"
+dependencies = [
+ "event-listener 2.5.3",
+]
+
+[[package]]
+name = "async-lock"
+version = "3.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d034b430882f8381900d3fe6f0aaa3ad94f2cb4ac519b429692a1bc2dda4ae7b"
+dependencies = [
+ "event-listener 4.0.3",
+ "event-listener-strategy 0.4.0",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "async-process"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "451e3cf68011bd56771c79db04a9e333095ab6349f7e47592b788e9b98720cc8"
+dependencies = [
+ "async-channel",
+ "async-io",
+ "async-lock 3.3.0",
+ "async-signal",
+ "blocking",
+ "cfg-if",
+ "event-listener 5.2.0",
+ "futures-lite",
+ "rustix",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "async-signal"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e47d90f65a225c4527103a8d747001fc56e375203592b25ad103e1ca13124c5"
+dependencies = [
+ "async-io",
+ "async-lock 2.8.0",
+ "atomic-waker",
+ "cfg-if",
+ "futures-core",
+ "futures-io",
+ "rustix",
+ "signal-hook-registry",
+ "slab",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "async-stream"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51"
+dependencies = [
+ "async-stream-impl",
+ "futures-core",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "async-stream-impl"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "async-task"
+version = "4.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fbb36e985947064623dbd357f727af08ffd077f93d696782f3c56365fa2e2799"
+
+[[package]]
+name = "async-tempfile"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b37d4bb113c47e4f263d4b0221912ff5aa840a51bc9b7b47b024e1cf1926fd9b"
+dependencies = [
+ "tokio",
+ "uuid",
+]
+
+[[package]]
+name = "async-trait"
+version = "0.1.77"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "atomic-waker"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
+
+[[package]]
+name = "autocfg"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+
+[[package]]
+name = "axum"
+version = "0.6.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf"
+dependencies = [
+ "async-trait",
+ "axum-core 0.3.4",
+ "bitflags 1.3.2",
+ "bytes",
+ "futures-util",
+ "http 0.2.11",
+ "http-body 0.4.6",
+ "hyper 0.14.28",
+ "itoa",
+ "matchit",
+ "memchr",
+ "mime",
+ "percent-encoding",
+ "pin-project-lite",
+ "rustversion",
+ "serde",
+ "sync_wrapper",
+ "tower",
+ "tower-layer",
+ "tower-service",
+]
+
+[[package]]
+name = "axum"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1236b4b292f6c4d6dc34604bb5120d85c3fe1d1aa596bd5cc52ca054d13e7b9e"
+dependencies = [
+ "async-trait",
+ "axum-core 0.4.3",
+ "bytes",
+ "futures-util",
+ "http 1.1.0",
+ "http-body 1.0.0",
+ "http-body-util",
+ "hyper 1.2.0",
+ "hyper-util",
+ "itoa",
+ "matchit",
+ "memchr",
+ "mime",
+ "percent-encoding",
+ "pin-project-lite",
+ "rustversion",
+ "serde",
+ "serde_json",
+ "serde_path_to_error",
+ "serde_urlencoded",
+ "sync_wrapper",
+ "tokio",
+ "tower",
+ "tower-layer",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "axum-core"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c"
+dependencies = [
+ "async-trait",
+ "bytes",
+ "futures-util",
+ "http 0.2.11",
+ "http-body 0.4.6",
+ "mime",
+ "rustversion",
+ "tower-layer",
+ "tower-service",
+]
+
+[[package]]
+name = "axum-core"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a15c63fd72d41492dc4f497196f5da1fb04fb7529e631d73630d1b491e47a2e3"
+dependencies = [
+ "async-trait",
+ "bytes",
+ "futures-util",
+ "http 1.1.0",
+ "http-body 1.0.0",
+ "http-body-util",
+ "mime",
+ "pin-project-lite",
+ "rustversion",
+ "sync_wrapper",
+ "tower-layer",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "backtrace"
+version = "0.3.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837"
+dependencies = [
+ "addr2line",
+ "cc",
+ "cfg-if",
+ "libc",
+ "miniz_oxide",
+ "object",
+ "rustc-demangle",
+]
+
+[[package]]
+name = "base64"
+version = "0.21.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
+
+[[package]]
+name = "base64ct"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
+
+[[package]]
+name = "bigtable_rs"
+version = "0.2.9"
+source = "git+https://github.com/flokli/bigtable_rs?rev=0af404741dfc40eb9fa99cf4d4140a09c5c20df7#0af404741dfc40eb9fa99cf4d4140a09c5c20df7"
+dependencies = [
+ "gcp_auth",
+ "http 0.2.11",
+ "log",
+ "prost 0.12.3",
+ "prost-build",
+ "prost-types",
+ "prost-wkt",
+ "prost-wkt-build",
+ "prost-wkt-types",
+ "serde",
+ "serde_with",
+ "thiserror",
+ "tokio",
+ "tonic 0.11.0",
+ "tonic-build",
+ "tower",
+]
+
+[[package]]
+name = "bit-set"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1"
+dependencies = [
+ "bit-vec",
+]
+
+[[package]]
+name = "bit-vec"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "bitflags"
+version = "2.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
+
+[[package]]
+name = "bitmaps"
+version = "3.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "703642b98a00b3b90513279a8ede3fcfa479c126c5fb46e78f3051522f021403"
+
+[[package]]
+name = "blake3"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0231f06152bf547e9c2b5194f247cd97aacf6dcd8b15d8e5ec0663f64580da87"
+dependencies = [
+ "arrayref",
+ "arrayvec",
+ "cc",
+ "cfg-if",
+ "constant_time_eq",
+ "digest",
+ "rayon",
+]
+
+[[package]]
+name = "block-buffer"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "blocking"
+version = "1.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a37913e8dc4ddcc604f0c6d3bf2887c995153af3611de9e23c352b44c1b9118"
+dependencies = [
+ "async-channel",
+ "async-lock 3.3.0",
+ "async-task",
+ "fastrand",
+ "futures-io",
+ "futures-lite",
+ "piper",
+ "tracing",
+]
+
+[[package]]
+name = "bstr"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c48f0051a4b4c5e0b6d365cd04af53aeaa209e3cc15ec2cdb69e73cc87fbd0dc"
+dependencies = [
+ "memchr",
+ "regex-automata 0.4.3",
+ "serde",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"
+
+[[package]]
+name = "byteorder"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
+[[package]]
+name = "bytes"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
+
+[[package]]
+name = "bzip2"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8"
+dependencies = [
+ "bzip2-sys",
+ "libc",
+]
+
+[[package]]
+name = "bzip2-sys"
+version = "0.1.11+1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+]
+
+[[package]]
+name = "caps"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "190baaad529bcfbde9e1a19022c42781bdb6ff9de25721abdb8fd98c0807730b"
+dependencies = [
+ "libc",
+ "thiserror",
+]
+
+[[package]]
+name = "cast"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
+
+[[package]]
+name = "cc"
+version = "1.0.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
+dependencies = [
+ "jobserver",
+ "libc",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "chrono"
+version = "0.4.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5bc015644b92d5890fab7489e49d21f879d5c990186827d42ec511919404f38b"
+dependencies = [
+ "android-tzdata",
+ "iana-time-zone",
+ "js-sys",
+ "num-traits",
+ "serde",
+ "wasm-bindgen",
+ "windows-targets 0.52.0",
+]
+
+[[package]]
+name = "ciborium"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "effd91f6c78e5a4ace8a5d3c0b6bfaec9e2baaef55f3efc00e45fb2e477ee926"
+dependencies = [
+ "ciborium-io",
+ "ciborium-ll",
+ "serde",
+]
+
+[[package]]
+name = "ciborium-io"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdf919175532b369853f5d5e20b26b43112613fd6fe7aee757e35f7a44642656"
+
+[[package]]
+name = "ciborium-ll"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b"
+dependencies = [
+ "ciborium-io",
+ "half",
+]
+
+[[package]]
+name = "clap"
+version = "4.4.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e578d6ec4194633722ccf9544794b71b1385c3c027efe0c55db226fc880865c"
+dependencies = [
+ "clap_builder",
+ "clap_derive",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.4.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4df4df40ec50c46000231c914968278b1eb05098cf8f1b3a518a95030e71d1c7"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "clap_lex",
+ "strsim",
+]
+
+[[package]]
+name = "clap_derive"
+version = "4.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "clap_lex"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1"
+
+[[package]]
+name = "clipboard-win"
+version = "4.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7191c27c2357d9b7ef96baac1773290d4ca63b24205b82a3fd8a0637afcf0362"
+dependencies = [
+ "error-code",
+ "str-buf",
+ "winapi",
+]
+
+[[package]]
+name = "codemap"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9e769b5c8c8283982a987c6e948e540254f1058d5a74b8794914d4ef5fc2a24"
+
+[[package]]
+name = "codemap-diagnostic"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc20770be05b566a963bf91505e60412c4a2d016d1ef95c5512823bb085a8122"
+dependencies = [
+ "codemap",
+ "termcolor",
+]
+
+[[package]]
+name = "colorchoice"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
+
+[[package]]
+name = "concurrent-queue"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d16048cd947b08fa32c24458a22f5dc5e835264f689f4f5653210c69fd107363"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "const-oid"
+version = "0.9.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
+
+[[package]]
+name = "constant_time_eq"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2"
+
+[[package]]
+name = "core-foundation"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
+
+[[package]]
+name = "count-write"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ced507ab50aa0123e2c54db8b5f44fdfee04b1c93744d69e924307945fe57a85"
+
+[[package]]
+name = "countme"
+version = "3.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7704b5fdd17b18ae31c4c1da5a2e0305a2bf17b5249300a9ee9ed7b72114c636"
+
+[[package]]
+name = "cpufeatures"
+version = "0.2.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crc32fast"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "criterion"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f"
+dependencies = [
+ "anes",
+ "cast",
+ "ciborium",
+ "clap",
+ "criterion-plot",
+ "is-terminal",
+ "itertools 0.10.5",
+ "num-traits",
+ "once_cell",
+ "oorandom",
+ "plotters",
+ "rayon",
+ "regex",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "tinytemplate",
+ "walkdir",
+]
+
+[[package]]
+name = "criterion-plot"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1"
+dependencies = [
+ "cast",
+ "itertools 0.10.5",
+]
+
+[[package]]
+name = "crossbeam-channel"
+version = "0.5.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "176dc175b78f56c0f321911d9c8eb2b77a78a4860b9c19db83835fea1a46649b"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-deque"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d"
+dependencies = [
+ "crossbeam-epoch",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.9.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
+
+[[package]]
+name = "crypto-common"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
+dependencies = [
+ "generic-array",
+ "typenum",
+]
+
+[[package]]
+name = "curve25519-dalek"
+version = "4.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e89b8c6a2e4b1f45971ad09761aafb85514a84744b67a95e32c3cc1352d1f65c"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "curve25519-dalek-derive",
+ "digest",
+ "fiat-crypto",
+ "platforms",
+ "rustc_version",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "curve25519-dalek-derive"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "darling"
+version = "0.20.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391"
+dependencies = [
+ "darling_core",
+ "darling_macro",
+]
+
+[[package]]
+name = "darling_core"
+version = "0.20.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f"
+dependencies = [
+ "fnv",
+ "ident_case",
+ "proc-macro2",
+ "quote",
+ "strsim",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "darling_macro"
+version = "0.20.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f"
+dependencies = [
+ "darling_core",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "data-encoding"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5"
+
+[[package]]
+name = "der"
+version = "0.7.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c"
+dependencies = [
+ "const-oid",
+ "zeroize",
+]
+
+[[package]]
+name = "deranged"
+version = "0.3.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
+dependencies = [
+ "powerfmt",
+ "serde",
+]
+
+[[package]]
+name = "diff"
+version = "0.1.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
+
+[[package]]
+name = "digest"
+version = "0.10.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
+dependencies = [
+ "block-buffer",
+ "crypto-common",
+ "subtle",
+]
+
+[[package]]
+name = "dirs"
+version = "4.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059"
+dependencies = [
+ "dirs-sys",
+]
+
+[[package]]
+name = "dirs-next"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
+dependencies = [
+ "cfg-if",
+ "dirs-sys-next",
+]
+
+[[package]]
+name = "dirs-sys"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6"
+dependencies = [
+ "libc",
+ "redox_users",
+ "winapi",
+]
+
+[[package]]
+name = "dirs-sys-next"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
+dependencies = [
+ "libc",
+ "redox_users",
+ "winapi",
+]
+
+[[package]]
+name = "doc-comment"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
+
+[[package]]
+name = "document-features"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef5282ad69563b5fc40319526ba27e0e7363d552a896f0297d54f767717f9b95"
+dependencies = [
+ "litrs",
+]
+
+[[package]]
+name = "ed25519"
+version = "2.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53"
+dependencies = [
+ "pkcs8",
+ "signature",
+]
+
+[[package]]
+name = "ed25519-dalek"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f628eaec48bfd21b865dc2950cfa014450c01d2fa2b69a86c2fd5844ec523c0"
+dependencies = [
+ "curve25519-dalek",
+ "ed25519",
+ "serde",
+ "sha2",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "either"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
+
+[[package]]
+name = "encoding_rs"
+version = "0.8.33"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "endian-type"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d"
+
+[[package]]
+name = "enum-primitive-derive"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba7795da175654fe16979af73f81f26a8ea27638d8d9823d317016888a63dc4c"
+dependencies = [
+ "num-traits",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "equivalent"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
+
+[[package]]
+name = "erased-serde"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b73807008a3c7f171cc40312f37d95ef0396e048b5848d775f54b1a4dd4a0d3"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "errno"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
+dependencies = [
+ "libc",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "error-code"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64f18991e7bf11e7ffee451b5318b5c1a73c52d0d0ada6e5a3017c8c1ced6a21"
+dependencies = [
+ "libc",
+ "str-buf",
+]
+
+[[package]]
+name = "event-listener"
+version = "2.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
+
+[[package]]
+name = "event-listener"
+version = "4.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67b215c49b2b248c855fb73579eb1f4f26c38ffdc12973e20e07b91d78d5646e"
+dependencies = [
+ "concurrent-queue",
+ "parking",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "event-listener"
+version = "5.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b5fb89194fa3cad959b833185b3063ba881dbfc7030680b314250779fb4cc91"
+dependencies = [
+ "concurrent-queue",
+ "parking",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "event-listener-strategy"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3"
+dependencies = [
+ "event-listener 4.0.3",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "event-listener-strategy"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "feedafcaa9b749175d5ac357452a9d41ea2911da598fde46ce1fe02c37751291"
+dependencies = [
+ "event-listener 5.2.0",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "fastcdc"
+version = "3.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a71061d097bfa9a5a4d2efdec57990d9a88745020b365191d37e48541a1628f2"
+dependencies = [
+ "async-stream",
+ "tokio",
+ "tokio-stream",
+]
+
+[[package]]
+name = "fastrand"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
+
+[[package]]
+name = "fd-lock"
+version = "3.0.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef033ed5e9bad94e55838ca0ca906db0e043f517adda0c8b79c7a8c66c93c1b5"
+dependencies = [
+ "cfg-if",
+ "rustix",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "fiat-crypto"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "27573eac26f4dd11e2b1916c3fe1baa56407c83c71a773a8ba17ec0bca03b6b7"
+
+[[package]]
+name = "filetime"
+version = "0.2.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall 0.4.1",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "fixedbitset"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
+
+[[package]]
+name = "flate2"
+version = "1.0.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e"
+dependencies = [
+ "crc32fast",
+ "miniz_oxide",
+]
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "form_urlencoded"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
+dependencies = [
+ "percent-encoding",
+]
+
+[[package]]
+name = "fs2"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "fuse-backend-rs"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e5a63a89f40ec26a0a1434e89de3f4ee939a920eae15d641053ee09ee6ed44b"
+dependencies = [
+ "arc-swap",
+ "bitflags 1.3.2",
+ "caps",
+ "core-foundation-sys",
+ "lazy_static",
+ "libc",
+ "log",
+ "mio",
+ "nix 0.24.3",
+ "vhost",
+ "virtio-queue",
+ "vm-memory",
+ "vmm-sys-util",
+]
+
+[[package]]
+name = "futures"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-io",
+ "futures-sink",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
+
+[[package]]
+name = "futures-executor"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-io"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
+
+[[package]]
+name = "futures-lite"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5"
+dependencies = [
+ "fastrand",
+ "futures-core",
+ "futures-io",
+ "parking",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "futures-macro"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "futures-sink"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5"
+
+[[package]]
+name = "futures-task"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
+
+[[package]]
+name = "futures-timer"
+version = "3.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c"
+
+[[package]]
+name = "futures-util"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-macro",
+ "futures-sink",
+ "futures-task",
+ "memchr",
+ "pin-project-lite",
+ "pin-utils",
+ "slab",
+]
+
+[[package]]
+name = "fxhash"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
+dependencies = [
+ "byteorder",
+]
+
+[[package]]
+name = "gcp_auth"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de2c71ea685b88a1aa50e9fb66fe0e1cb29d755f58cca41fb8c91ef604d4f4d4"
+dependencies = [
+ "async-trait",
+ "base64",
+ "chrono",
+ "home",
+ "hyper 0.14.28",
+ "hyper-rustls",
+ "ring",
+ "rustls 0.21.10",
+ "rustls-pemfile 1.0.4",
+ "serde",
+ "serde_json",
+ "thiserror",
+ "tokio",
+ "tracing",
+ "tracing-futures",
+ "url",
+ "which 5.0.0",
+]
+
+[[package]]
+name = "genawaiter"
+version = "0.99.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c86bd0361bcbde39b13475e6e36cb24c329964aa2611be285289d1e4b751c1a0"
+dependencies = [
+ "genawaiter-macro",
+]
+
+[[package]]
+name = "genawaiter-macro"
+version = "0.99.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b32dfe1fdfc0bbde1f22a5da25355514b5e450c33a6af6770884c8750aedfbc"
+
+[[package]]
+name = "generic-array"
+version = "0.14.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "gimli"
+version = "0.28.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
+
+[[package]]
+name = "glob"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
+
+[[package]]
+name = "h2"
+version = "0.3.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9"
+dependencies = [
+ "bytes",
+ "fnv",
+ "futures-core",
+ "futures-sink",
+ "futures-util",
+ "http 0.2.11",
+ "indexmap 2.1.0",
+ "slab",
+ "tokio",
+ "tokio-util",
+ "tracing",
+]
+
+[[package]]
+name = "h2"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51ee2dd2e4f378392eeff5d51618cd9a63166a2513846bbc55f21cfacd9199d4"
+dependencies = [
+ "bytes",
+ "fnv",
+ "futures-core",
+ "futures-sink",
+ "futures-util",
+ "http 1.1.0",
+ "indexmap 2.1.0",
+ "slab",
+ "tokio",
+ "tokio-util",
+ "tracing",
+]
+
+[[package]]
+name = "half"
+version = "1.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7"
+
+[[package]]
+name = "hashbrown"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
+
+[[package]]
+name = "hashbrown"
+version = "0.14.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
+
+[[package]]
+name = "heck"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
+
+[[package]]
+name = "hermit-abi"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d3d0e0f38255e7fa3cf31335b3a56f05febd18025f4db5ef7a0cfb4f8da651f"
+
+[[package]]
+name = "hex"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+
+[[package]]
+name = "hex-literal"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46"
+
+[[package]]
+name = "home"
+version = "0.5.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5"
+dependencies = [
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "http"
+version = "0.2.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb"
+dependencies = [
+ "bytes",
+ "fnv",
+ "itoa",
+]
+
+[[package]]
+name = "http"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258"
+dependencies = [
+ "bytes",
+ "fnv",
+ "itoa",
+]
+
+[[package]]
+name = "http-body"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2"
+dependencies = [
+ "bytes",
+ "http 0.2.11",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "http-body"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643"
+dependencies = [
+ "bytes",
+ "http 1.1.0",
+]
+
+[[package]]
+name = "http-body-util"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "http 1.1.0",
+ "http-body 1.0.0",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "httparse"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
+
+[[package]]
+name = "httpdate"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
+
+[[package]]
+name = "humantime"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
+
+[[package]]
+name = "hyper"
+version = "0.14.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80"
+dependencies = [
+ "bytes",
+ "futures-channel",
+ "futures-core",
+ "futures-util",
+ "h2 0.3.24",
+ "http 0.2.11",
+ "http-body 0.4.6",
+ "httparse",
+ "httpdate",
+ "itoa",
+ "pin-project-lite",
+ "socket2",
+ "tokio",
+ "tower-service",
+ "tracing",
+ "want",
+]
+
+[[package]]
+name = "hyper"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "186548d73ac615b32a73aafe38fb4f56c0d340e110e5a200bcadbaf2e199263a"
+dependencies = [
+ "bytes",
+ "futures-channel",
+ "futures-util",
+ "h2 0.4.3",
+ "http 1.1.0",
+ "http-body 1.0.0",
+ "httparse",
+ "httpdate",
+ "itoa",
+ "pin-project-lite",
+ "smallvec",
+ "tokio",
+]
+
+[[package]]
+name = "hyper-rustls"
+version = "0.24.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590"
+dependencies = [
+ "futures-util",
+ "http 0.2.11",
+ "hyper 0.14.28",
+ "rustls 0.21.10",
+ "rustls-native-certs 0.6.3",
+ "tokio",
+ "tokio-rustls 0.24.1",
+]
+
+[[package]]
+name = "hyper-timeout"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1"
+dependencies = [
+ "hyper 0.14.28",
+ "pin-project-lite",
+ "tokio",
+ "tokio-io-timeout",
+]
+
+[[package]]
+name = "hyper-util"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa"
+dependencies = [
+ "bytes",
+ "futures-util",
+ "http 1.1.0",
+ "http-body 1.0.0",
+ "hyper 1.2.0",
+ "pin-project-lite",
+ "socket2",
+ "tokio",
+]
+
+[[package]]
+name = "iana-time-zone"
+version = "0.1.60"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "iana-time-zone-haiku",
+ "js-sys",
+ "wasm-bindgen",
+ "windows-core",
+]
+
+[[package]]
+name = "iana-time-zone-haiku"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "ident_case"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
+
+[[package]]
+name = "idna"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
+dependencies = [
+ "unicode-bidi",
+ "unicode-normalization",
+]
+
+[[package]]
+name = "imbl"
+version = "2.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "978d142c8028edf52095703af2fad11d6f611af1246685725d6b850634647085"
+dependencies = [
+ "bitmaps",
+ "imbl-sized-chunks",
+ "proptest",
+ "rand_core",
+ "rand_xoshiro",
+ "serde",
+ "version_check",
+]
+
+[[package]]
+name = "imbl-sized-chunks"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "144006fb58ed787dcae3f54575ff4349755b00ccc99f4b4873860b654be1ed63"
+dependencies = [
+ "bitmaps",
+]
+
+[[package]]
+name = "indexmap"
+version = "1.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
+dependencies = [
+ "autocfg",
+ "hashbrown 0.12.3",
+ "serde",
+]
+
+[[package]]
+name = "indexmap"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f"
+dependencies = [
+ "equivalent",
+ "hashbrown 0.14.3",
+ "serde",
+]
+
+[[package]]
+name = "instant"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "inventory"
+version = "0.3.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f958d3d68f4167080a18141e10381e7634563984a537f2a49a30fd8e53ac5767"
+
+[[package]]
+name = "ipnet"
+version = "2.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3"
+
+[[package]]
+name = "is-terminal"
+version = "0.4.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0bad00257d07be169d870ab665980b06cdb366d792ad690bf2e76876dc503455"
+dependencies = [
+ "hermit-abi",
+ "rustix",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "itertools"
+version = "0.10.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itertools"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itertools"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "25db6b064527c5d482d0423354fcd07a89a2dfe07b67892e62411946db7f07b0"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
+
+[[package]]
+name = "jobserver"
+version = "0.1.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "js-sys"
+version = "0.3.67"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a1d36f1235bc969acba30b7f5990b864423a6068a10f7c90ae8f0112e3a59d1"
+dependencies = [
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "lexical-core"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2cde5de06e8d4c2faabc400238f9ae1c74d5412d03a7bd067645ccbc47070e46"
+dependencies = [
+ "lexical-parse-float",
+ "lexical-parse-integer",
+ "lexical-util",
+ "lexical-write-float",
+ "lexical-write-integer",
+]
+
+[[package]]
+name = "lexical-parse-float"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "683b3a5ebd0130b8fb52ba0bdc718cc56815b6a097e28ae5a6997d0ad17dc05f"
+dependencies = [
+ "lexical-parse-integer",
+ "lexical-util",
+ "static_assertions",
+]
+
+[[package]]
+name = "lexical-parse-integer"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d0994485ed0c312f6d965766754ea177d07f9c00c9b82a5ee62ed5b47945ee9"
+dependencies = [
+ "lexical-util",
+ "static_assertions",
+]
+
+[[package]]
+name = "lexical-util"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5255b9ff16ff898710eb9eb63cb39248ea8a5bb036bea8085b1a767ff6c4e3fc"
+dependencies = [
+ "static_assertions",
+]
+
+[[package]]
+name = "lexical-write-float"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "accabaa1c4581f05a3923d1b4cfd124c329352288b7b9da09e766b0668116862"
+dependencies = [
+ "lexical-util",
+ "lexical-write-integer",
+ "static_assertions",
+]
+
+[[package]]
+name = "lexical-write-integer"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1b6f3d1f4422866b68192d62f77bc5c700bee84f3069f2469d7bc8c77852446"
+dependencies = [
+ "lexical-util",
+ "static_assertions",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.152"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7"
+
+[[package]]
+name = "libm"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058"
+
+[[package]]
+name = "libredox"
+version = "0.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8"
+dependencies = [
+ "bitflags 2.4.2",
+ "libc",
+ "redox_syscall 0.4.1",
+]
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.4.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
+
+[[package]]
+name = "litrs"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5"
+
+[[package]]
+name = "lock_api"
+version = "0.4.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45"
+dependencies = [
+ "autocfg",
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
+
+[[package]]
+name = "lzma-sys"
+version = "0.1.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+]
+
+[[package]]
+name = "magic"
+version = "0.16.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a200ae03df8c3dce7a963f6eeaac8feb41bf9001cb7e5ab22e3205aec2f0373d"
+dependencies = [
+ "bitflags 2.4.2",
+ "libc",
+ "magic-sys",
+ "thiserror",
+]
+
+[[package]]
+name = "magic-sys"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eff86ae08895140d628119d407d568f3b657145ee8c265878064f717534bb3bc"
+dependencies = [
+ "libc",
+ "vcpkg",
+]
+
+[[package]]
+name = "matchers"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
+dependencies = [
+ "regex-automata 0.1.10",
+]
+
+[[package]]
+name = "matchit"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94"
+
+[[package]]
+name = "md-5"
+version = "0.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf"
+dependencies = [
+ "cfg-if",
+ "digest",
+]
+
+[[package]]
+name = "memchr"
+version = "2.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
+
+[[package]]
+name = "memoffset"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "memoffset"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "mime"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
+
+[[package]]
+name = "minimal-lexical"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
+
+[[package]]
+name = "miniz_oxide"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
+dependencies = [
+ "adler",
+]
+
+[[package]]
+name = "mio"
+version = "0.8.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09"
+dependencies = [
+ "libc",
+ "log",
+ "wasi",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "multimap"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a"
+
+[[package]]
+name = "nibble_vec"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43"
+dependencies = [
+ "smallvec",
+]
+
+[[package]]
+name = "nix"
+version = "0.24.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069"
+dependencies = [
+ "bitflags 1.3.2",
+ "cfg-if",
+ "libc",
+ "memoffset 0.6.5",
+]
+
+[[package]]
+name = "nix"
+version = "0.25.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4"
+dependencies = [
+ "autocfg",
+ "bitflags 1.3.2",
+ "cfg-if",
+ "libc",
+]
+
+[[package]]
+name = "nix"
+version = "0.26.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b"
+dependencies = [
+ "bitflags 1.3.2",
+ "cfg-if",
+ "libc",
+]
+
+[[package]]
+name = "nix"
+version = "0.27.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053"
+dependencies = [
+ "bitflags 2.4.2",
+ "cfg-if",
+ "libc",
+]
+
+[[package]]
+name = "nix-compat"
+version = "0.1.0"
+dependencies = [
+ "bitflags 2.4.2",
+ "bstr",
+ "criterion",
+ "data-encoding",
+ "ed25519",
+ "ed25519-dalek",
+ "enum-primitive-derive",
+ "futures",
+ "glob",
+ "hex-literal",
+ "lazy_static",
+ "nom",
+ "num-traits",
+ "pin-project-lite",
+ "pretty_assertions",
+ "rstest",
+ "serde",
+ "serde_json",
+ "sha2",
+ "thiserror",
+ "tokio",
+ "tokio-test",
+ "zstd",
+]
+
+[[package]]
+name = "nom"
+version = "7.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
+dependencies = [
+ "memchr",
+ "minimal-lexical",
+]
+
+[[package]]
+name = "nom8"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae01545c9c7fc4486ab7debaf2aad7003ac19431791868fb2e8066df97fad2f8"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "nu-ansi-term"
+version = "0.46.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
+dependencies = [
+ "overload",
+ "winapi",
+]
+
+[[package]]
+name = "num-conv"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
+
+[[package]]
+name = "num-traits"
+version = "0.2.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a"
+dependencies = [
+ "autocfg",
+ "libm",
+]
+
+[[package]]
+name = "num_cpus"
+version = "1.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
+dependencies = [
+ "hermit-abi",
+ "libc",
+]
+
+[[package]]
+name = "object"
+version = "0.32.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "object_store"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8718f8b65fdf67a45108d1548347d4af7d71fb81ce727bbf9e3b2535e079db3"
+dependencies = [
+ "async-trait",
+ "base64",
+ "bytes",
+ "chrono",
+ "futures",
+ "humantime",
+ "hyper 0.14.28",
+ "itertools 0.12.0",
+ "md-5",
+ "parking_lot 0.12.1",
+ "percent-encoding",
+ "quick-xml",
+ "rand",
+ "reqwest",
+ "ring",
+ "rustls-pemfile 2.1.0",
+ "serde",
+ "serde_json",
+ "snafu",
+ "tokio",
+ "tracing",
+ "url",
+ "walkdir",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
+
+[[package]]
+name = "oorandom"
+version = "11.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
+
+[[package]]
+name = "openssl-probe"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
+
+[[package]]
+name = "opentelemetry"
+version = "0.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e32339a5dc40459130b3bd269e9892439f55b33e772d2a9d402a789baaf4e8a"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+ "indexmap 2.1.0",
+ "js-sys",
+ "once_cell",
+ "pin-project-lite",
+ "thiserror",
+ "urlencoding",
+]
+
+[[package]]
+name = "opentelemetry-otlp"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f24cda83b20ed2433c68241f918d0f6fdec8b1d43b7a9590ab4420c5095ca930"
+dependencies = [
+ "async-trait",
+ "futures-core",
+ "http 0.2.11",
+ "opentelemetry",
+ "opentelemetry-proto",
+ "opentelemetry-semantic-conventions",
+ "opentelemetry_sdk",
+ "prost 0.11.9",
+ "thiserror",
+ "tokio",
+ "tonic 0.9.2",
+]
+
+[[package]]
+name = "opentelemetry-proto"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2e155ce5cc812ea3d1dffbd1539aed653de4bf4882d60e6e04dcf0901d674e1"
+dependencies = [
+ "opentelemetry",
+ "opentelemetry_sdk",
+ "prost 0.11.9",
+ "tonic 0.9.2",
+]
+
+[[package]]
+name = "opentelemetry-semantic-conventions"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f5774f1ef1f982ef2a447f6ee04ec383981a3ab99c8e77a1a7b30182e65bbc84"
+dependencies = [
+ "opentelemetry",
+]
+
+[[package]]
+name = "opentelemetry_sdk"
+version = "0.21.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2f16aec8a98a457a52664d69e0091bac3a0abd18ead9b641cb00202ba4e0efe4"
+dependencies = [
+ "async-trait",
+ "crossbeam-channel",
+ "futures-channel",
+ "futures-executor",
+ "futures-util",
+ "glob",
+ "once_cell",
+ "opentelemetry",
+ "ordered-float",
+ "percent-encoding",
+ "rand",
+ "thiserror",
+ "tokio",
+ "tokio-stream",
+]
+
+[[package]]
+name = "ordered-float"
+version = "4.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a76df7075c7d4d01fdcb46c912dd17fba5b60c78ea480b475f2b6ab6f666584e"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "os_str_bytes"
+version = "6.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "overload"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
+
+[[package]]
+name = "parking"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae"
+
+[[package]]
+name = "parking_lot"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
+dependencies = [
+ "instant",
+ "lock_api",
+ "parking_lot_core 0.8.6",
+]
+
+[[package]]
+name = "parking_lot"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
+dependencies = [
+ "lock_api",
+ "parking_lot_core 0.9.9",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc"
+dependencies = [
+ "cfg-if",
+ "instant",
+ "libc",
+ "redox_syscall 0.2.16",
+ "smallvec",
+ "winapi",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall 0.4.1",
+ "smallvec",
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "path-clean"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ecba01bf2678719532c5e3059e0b5f0811273d94b397088b82e3bd0a78c78fdd"
+
+[[package]]
+name = "percent-encoding"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
+
+[[package]]
+name = "petgraph"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9"
+dependencies = [
+ "fixedbitset",
+ "indexmap 2.1.0",
+]
+
+[[package]]
+name = "pin-project"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422"
+dependencies = [
+ "pin-project-internal",
+]
+
+[[package]]
+name = "pin-project-internal"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
+[[package]]
+name = "piper"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "668d31b1c4eba19242f2088b2bf3316b82ca31082a8335764db4e083db7485d4"
+dependencies = [
+ "atomic-waker",
+ "fastrand",
+ "futures-io",
+]
+
+[[package]]
+name = "pkcs8"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
+dependencies = [
+ "der",
+ "spki",
+]
+
+[[package]]
+name = "pkg-config"
+version = "0.3.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2900ede94e305130c13ddd391e0ab7cbaeb783945ae07a279c268cb05109c6cb"
+
+[[package]]
+name = "platforms"
+version = "3.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "626dec3cac7cc0e1577a2ec3fc496277ec2baa084bebad95bb6fdbfae235f84c"
+
+[[package]]
+name = "plotters"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45"
+dependencies = [
+ "num-traits",
+ "plotters-backend",
+ "plotters-svg",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "plotters-backend"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609"
+
+[[package]]
+name = "plotters-svg"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab"
+dependencies = [
+ "plotters-backend",
+]
+
+[[package]]
+name = "polling"
+version = "3.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30054e72317ab98eddd8561db0f6524df3367636884b7b21b703e4b280a84a14"
+dependencies = [
+ "cfg-if",
+ "concurrent-queue",
+ "pin-project-lite",
+ "rustix",
+ "tracing",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "powerfmt"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
+
+[[package]]
+name = "pretty_assertions"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66"
+dependencies = [
+ "diff",
+ "yansi",
+]
+
+[[package]]
+name = "prettyplease"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a41cf62165e97c7f814d2221421dbb9afcbcdb0a88068e5ea206e19951c2cbb5"
+dependencies = [
+ "proc-macro2",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.76"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "proptest"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "31b476131c3c86cb68032fdc5cb6d5a1045e3e42d96b69fa599fd77701e1f5bf"
+dependencies = [
+ "bit-set",
+ "bit-vec",
+ "bitflags 2.4.2",
+ "lazy_static",
+ "num-traits",
+ "rand",
+ "rand_chacha",
+ "rand_xorshift",
+ "regex-syntax 0.8.2",
+ "rusty-fork",
+ "tempfile",
+ "unarray",
+]
+
+[[package]]
+name = "prost"
+version = "0.11.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd"
+dependencies = [
+ "bytes",
+ "prost-derive 0.11.9",
+]
+
+[[package]]
+name = "prost"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "146c289cda302b98a28d40c8b3b90498d6e526dd24ac2ecea73e4e491685b94a"
+dependencies = [
+ "bytes",
+ "prost-derive 0.12.3",
+]
+
+[[package]]
+name = "prost-build"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c55e02e35260070b6f716a2423c2ff1c3bb1642ddca6f99e1f26d06268a0e2d2"
+dependencies = [
+ "bytes",
+ "heck",
+ "itertools 0.11.0",
+ "log",
+ "multimap",
+ "once_cell",
+ "petgraph",
+ "prettyplease",
+ "prost 0.12.3",
+ "prost-types",
+ "pulldown-cmark",
+ "pulldown-cmark-to-cmark",
+ "regex",
+ "syn 2.0.48",
+ "tempfile",
+ "which 4.4.2",
+]
+
+[[package]]
+name = "prost-derive"
+version = "0.11.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4"
+dependencies = [
+ "anyhow",
+ "itertools 0.10.5",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "prost-derive"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e"
+dependencies = [
+ "anyhow",
+ "itertools 0.11.0",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "prost-types"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "193898f59edcf43c26227dcd4c8427f00d99d61e95dcde58dabd49fa291d470e"
+dependencies = [
+ "prost 0.12.3",
+]
+
+[[package]]
+name = "prost-wkt"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4d8ef9c3f0f1dab910d2b7e2c24a8e4322e122eba6d7a1921eeebcebbc046c40"
+dependencies = [
+ "chrono",
+ "inventory",
+ "prost 0.12.3",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "typetag",
+]
+
+[[package]]
+name = "prost-wkt-build"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b31cae9a54ca84fee1504740a82eebf2479532905e106f63ca0c3bc8d780321"
+dependencies = [
+ "heck",
+ "prost 0.12.3",
+ "prost-build",
+ "prost-types",
+ "quote",
+]
+
+[[package]]
+name = "prost-wkt-types"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "435be4a8704091b4c5fb1d79799de7f2dbff53af05edf29385237f8cf7ab37ee"
+dependencies = [
+ "chrono",
+ "prost 0.12.3",
+ "prost-build",
+ "prost-types",
+ "prost-wkt",
+ "prost-wkt-build",
+ "regex",
+ "serde",
+ "serde_derive",
+ "serde_json",
+]
+
+[[package]]
+name = "pulldown-cmark"
+version = "0.9.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57206b407293d2bcd3af849ce869d52068623f19e1b5ff8e8778e3309439682b"
+dependencies = [
+ "bitflags 2.4.2",
+ "memchr",
+ "unicase",
+]
+
+[[package]]
+name = "pulldown-cmark-to-cmark"
+version = "10.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0194e6e1966c23cc5fd988714f85b18d548d773e81965413555d96569931833d"
+dependencies = [
+ "pulldown-cmark",
+]
+
+[[package]]
+name = "quick-error"
+version = "1.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
+
+[[package]]
+name = "quick-xml"
+version = "0.31.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33"
+dependencies = [
+ "memchr",
+ "serde",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "radix_trie"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd"
+dependencies = [
+ "endian-type",
+ "nibble_vec",
+]
+
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "libc",
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "rand_xorshift"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f"
+dependencies = [
+ "rand_core",
+]
+
+[[package]]
+name = "rand_xoshiro"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa"
+dependencies = [
+ "rand_core",
+]
+
+[[package]]
+name = "rayon"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa7237101a77a10773db45d62004a272517633fbcc3df19d96455ede1122e051"
+dependencies = [
+ "either",
+ "rayon-core",
+]
+
+[[package]]
+name = "rayon-core"
+version = "1.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
+dependencies = [
+ "crossbeam-deque",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
+dependencies = [
+ "bitflags 1.3.2",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
+dependencies = [
+ "bitflags 1.3.2",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
+dependencies = [
+ "bitflags 1.3.2",
+]
+
+[[package]]
+name = "redox_users"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4"
+dependencies = [
+ "getrandom",
+ "libredox",
+ "thiserror",
+]
+
+[[package]]
+name = "regex"
+version = "1.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata 0.4.3",
+ "regex-syntax 0.8.2",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
+dependencies = [
+ "regex-syntax 0.6.29",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax 0.8.2",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.6.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
+
+[[package]]
+name = "relative-path"
+version = "1.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e898588f33fdd5b9420719948f9f2a32c922a246964576f71ba7f24f80610fbc"
+
+[[package]]
+name = "reqwest"
+version = "0.11.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37b1ae8d9ac08420c66222fb9096fc5de435c3c48542bc5336c51892cffafb41"
+dependencies = [
+ "base64",
+ "bytes",
+ "encoding_rs",
+ "futures-core",
+ "futures-util",
+ "h2 0.3.24",
+ "http 0.2.11",
+ "http-body 0.4.6",
+ "hyper 0.14.28",
+ "hyper-rustls",
+ "ipnet",
+ "js-sys",
+ "log",
+ "mime",
+ "once_cell",
+ "percent-encoding",
+ "pin-project-lite",
+ "rustls 0.21.10",
+ "rustls-native-certs 0.6.3",
+ "rustls-pemfile 1.0.4",
+ "serde",
+ "serde_json",
+ "serde_urlencoded",
+ "system-configuration",
+ "tokio",
+ "tokio-rustls 0.24.1",
+ "tokio-util",
+ "tower-service",
+ "url",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "wasm-streams",
+ "web-sys",
+ "winreg",
+]
+
+[[package]]
+name = "ring"
+version = "0.17.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74"
+dependencies = [
+ "cc",
+ "getrandom",
+ "libc",
+ "spin",
+ "untrusted",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "rnix"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb35cedbeb70e0ccabef2a31bcff0aebd114f19566086300b8f42c725fc2cb5f"
+dependencies = [
+ "rowan",
+]
+
+[[package]]
+name = "rowan"
+version = "0.15.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a58fa8a7ccff2aec4f39cc45bf5f985cec7125ab271cf681c279fd00192b49"
+dependencies = [
+ "countme",
+ "hashbrown 0.14.3",
+ "memoffset 0.9.0",
+ "rustc-hash",
+ "text-size",
+]
+
+[[package]]
+name = "rstest"
+version = "0.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d5316d2a1479eeef1ea21e7f9ddc67c191d497abc8fc3ba2467857abbb68330"
+dependencies = [
+ "futures",
+ "futures-timer",
+ "rstest_macros",
+ "rustc_version",
+]
+
+[[package]]
+name = "rstest_macros"
+version = "0.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "04a9df72cc1f67020b0d63ad9bfe4a323e459ea7eb68e03bd9824db49f9a4c25"
+dependencies = [
+ "cfg-if",
+ "glob",
+ "proc-macro2",
+ "quote",
+ "regex",
+ "relative-path",
+ "rustc_version",
+ "syn 2.0.48",
+ "unicode-ident",
+]
+
+[[package]]
+name = "rstest_reuse"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88530b681abe67924d42cca181d070e3ac20e0740569441a9e35a7cedd2b34a4"
+dependencies = [
+ "quote",
+ "rand",
+ "rustc_version",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "rustc-demangle"
+version = "0.1.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
+
+[[package]]
+name = "rustc-hash"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
+
+[[package]]
+name = "rustc_version"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
+dependencies = [
+ "semver",
+]
+
+[[package]]
+name = "rustix"
+version = "0.38.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca"
+dependencies = [
+ "bitflags 2.4.2",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "rustls"
+version = "0.21.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba"
+dependencies = [
+ "log",
+ "ring",
+ "rustls-webpki 0.101.7",
+ "sct",
+]
+
+[[package]]
+name = "rustls"
+version = "0.22.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e87c9956bd9807afa1f77e0f7594af32566e830e088a5576d27c5b6f30f49d41"
+dependencies = [
+ "log",
+ "ring",
+ "rustls-pki-types",
+ "rustls-webpki 0.102.2",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "rustls-native-certs"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00"
+dependencies = [
+ "openssl-probe",
+ "rustls-pemfile 1.0.4",
+ "schannel",
+ "security-framework",
+]
+
+[[package]]
+name = "rustls-native-certs"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792"
+dependencies = [
+ "openssl-probe",
+ "rustls-pemfile 2.1.0",
+ "rustls-pki-types",
+ "schannel",
+ "security-framework",
+]
+
+[[package]]
+name = "rustls-pemfile"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c"
+dependencies = [
+ "base64",
+]
+
+[[package]]
+name = "rustls-pemfile"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c333bb734fcdedcea57de1602543590f545f127dc8b533324318fd492c5c70b"
+dependencies = [
+ "base64",
+ "rustls-pki-types",
+]
+
+[[package]]
+name = "rustls-pki-types"
+version = "1.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ede67b28608b4c60685c7d54122d4400d90f62b40caee7700e700380a390fa8"
+
+[[package]]
+name = "rustls-webpki"
+version = "0.101.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765"
+dependencies = [
+ "ring",
+ "untrusted",
+]
+
+[[package]]
+name = "rustls-webpki"
+version = "0.102.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "faaa0a62740bedb9b2ef5afa303da42764c012f743917351dc9a237ea1663610"
+dependencies = [
+ "ring",
+ "rustls-pki-types",
+ "untrusted",
+]
+
+[[package]]
+name = "rustversion"
+version = "1.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4"
+
+[[package]]
+name = "rusty-fork"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f"
+dependencies = [
+ "fnv",
+ "quick-error",
+ "tempfile",
+ "wait-timeout",
+]
+
+[[package]]
+name = "rustyline"
+version = "10.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1e83c32c3f3c33b08496e0d1df9ea8c64d39adb8eb36a1ebb1440c690697aef"
+dependencies = [
+ "bitflags 1.3.2",
+ "cfg-if",
+ "clipboard-win",
+ "dirs-next",
+ "fd-lock",
+ "libc",
+ "log",
+ "memchr",
+ "nix 0.25.1",
+ "radix_trie",
+ "scopeguard",
+ "unicode-segmentation",
+ "unicode-width",
+ "utf8parse",
+ "winapi",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c"
+
+[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "schannel"
+version = "0.1.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534"
+dependencies = [
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "scopeguard"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+
+[[package]]
+name = "sct"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414"
+dependencies = [
+ "ring",
+ "untrusted",
+]
+
+[[package]]
+name = "security-framework"
+version = "2.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de"
+dependencies = [
+ "bitflags 1.3.2",
+ "core-foundation",
+ "core-foundation-sys",
+ "libc",
+ "security-framework-sys",
+]
+
+[[package]]
+name = "security-framework-sys"
+version = "2.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "semver"
+version = "1.0.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0"
+
+[[package]]
+name = "serde"
+version = "1.0.197"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.197"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.111"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "serde_path_to_error"
+version = "0.1.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6"
+dependencies = [
+ "itoa",
+ "serde",
+]
+
+[[package]]
+name = "serde_qs"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0431a35568651e363364210c91983c1da5eb29404d9f0928b67d4ebcfa7d330c"
+dependencies = [
+ "percent-encoding",
+ "serde",
+ "thiserror",
+]
+
+[[package]]
+name = "serde_spanned"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "serde_urlencoded"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
+dependencies = [
+ "form_urlencoded",
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "serde_with"
+version = "3.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee80b0e361bbf88fd2f6e242ccd19cfda072cb0faa6ae694ecee08199938569a"
+dependencies = [
+ "base64",
+ "chrono",
+ "hex",
+ "indexmap 1.9.3",
+ "indexmap 2.1.0",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "serde_with_macros",
+ "time",
+]
+
+[[package]]
+name = "serde_with_macros"
+version = "3.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6561dc161a9224638a31d876ccdfefbc1df91d3f3a8342eddb35f055d48c7655"
+dependencies = [
+ "darling",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "sha1"
+version = "0.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
+[[package]]
+name = "sha2"
+version = "0.10.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
+[[package]]
+name = "sharded-slab"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
+dependencies = [
+ "lazy_static",
+]
+
+[[package]]
+name = "signal-hook-registry"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "signature"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
+dependencies = [
+ "rand_core",
+]
+
+[[package]]
+name = "slab"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "sled"
+version = "0.34.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f96b4737c2ce5987354855aed3797279def4ebf734436c6aa4552cf8e169935"
+dependencies = [
+ "crc32fast",
+ "crossbeam-epoch",
+ "crossbeam-utils",
+ "fs2",
+ "fxhash",
+ "libc",
+ "log",
+ "parking_lot 0.11.2",
+]
+
+[[package]]
+name = "smallvec"
+version = "1.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7"
+
+[[package]]
+name = "smol_str"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6845563ada680337a52d43bb0b29f396f2d911616f6573012645b9e3d048a49"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "snafu"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e4de37ad025c587a29e8f3f5605c00f70b98715ef90b9061a815b9e59e9042d6"
+dependencies = [
+ "doc-comment",
+ "snafu-derive",
+]
+
+[[package]]
+name = "snafu-derive"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "990079665f075b699031e9c08fd3ab99be5029b96f3b78dc0709e8f77e4efebf"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "socket2"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9"
+dependencies = [
+ "libc",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "spin"
+version = "0.9.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
+
+[[package]]
+name = "spki"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
+dependencies = [
+ "base64ct",
+ "der",
+]
+
+[[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
+[[package]]
+name = "str-buf"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0"
+
+[[package]]
+name = "strsim"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
+
+[[package]]
+name = "structmeta"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "104842d6278bf64aa9d2f182ba4bde31e8aec7a131d29b7f444bb9b344a09e2a"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "structmeta-derive",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "structmeta-derive"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24420be405b590e2d746d83b01f09af673270cf80e9b003a5fa7b651c58c7d93"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "subtle"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
+
+[[package]]
+name = "syn"
+version = "1.0.109"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.48"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "sync_wrapper"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
+
+[[package]]
+name = "system-configuration"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7"
+dependencies = [
+ "bitflags 1.3.2",
+ "core-foundation",
+ "system-configuration-sys",
+]
+
+[[package]]
+name = "system-configuration-sys"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "tabwriter"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a327282c4f64f6dc37e3bba4c2b6842cc3a992f204fa58d917696a89f691e5f6"
+dependencies = [
+ "unicode-width",
+]
+
+[[package]]
+name = "tempfile"
+version = "3.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa"
+dependencies = [
+ "cfg-if",
+ "fastrand",
+ "redox_syscall 0.4.1",
+ "rustix",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "termcolor"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "test-strategy"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62d6408d1406657be2f9d1701fbae379331d30d2f6e92050710edb0d34eeb480"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "structmeta",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "text-size"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f18aa187839b2bdb1ad2fa35ead8c4c2976b64e4363c386d45ac0f7ee85c9233"
+
+[[package]]
+name = "thiserror"
+version = "1.0.56"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.56"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "thread_local"
+version = "1.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+]
+
+[[package]]
+name = "time"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749"
+dependencies = [
+ "deranged",
+ "itoa",
+ "num-conv",
+ "powerfmt",
+ "serde",
+ "time-core",
+ "time-macros",
+]
+
+[[package]]
+name = "time-core"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
+
+[[package]]
+name = "time-macros"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774"
+dependencies = [
+ "num-conv",
+ "time-core",
+]
+
+[[package]]
+name = "tinytemplate"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
+dependencies = [
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "tinyvec"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
+
+[[package]]
+name = "tokio"
+version = "1.35.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104"
+dependencies = [
+ "backtrace",
+ "bytes",
+ "libc",
+ "mio",
+ "num_cpus",
+ "pin-project-lite",
+ "signal-hook-registry",
+ "socket2",
+ "tokio-macros",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "tokio-io-timeout"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf"
+dependencies = [
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-listener"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96367e127b4cf47b92592a5154a563435fe28fe3fccf25917d4a34ee59c87303"
+dependencies = [
+ "axum 0.7.4",
+ "document-features",
+ "futures-core",
+ "futures-util",
+ "nix 0.26.4",
+ "pin-project",
+ "socket2",
+ "tokio",
+ "tokio-util",
+ "tonic 0.11.0",
+ "tracing",
+]
+
+[[package]]
+name = "tokio-macros"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "tokio-retry"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f57eb36ecbe0fc510036adff84824dd3c24bb781e21bfa67b69d556aa85214f"
+dependencies = [
+ "pin-project",
+ "rand",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-rustls"
+version = "0.24.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081"
+dependencies = [
+ "rustls 0.21.10",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-rustls"
+version = "0.25.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f"
+dependencies = [
+ "rustls 0.22.2",
+ "rustls-pki-types",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-stream"
+version = "0.1.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842"
+dependencies = [
+ "futures-core",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-tar"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d5714c010ca3e5c27114c1cdeb9d14641ace49874aa5626d7149e47aedace75"
+dependencies = [
+ "filetime",
+ "futures-core",
+ "libc",
+ "redox_syscall 0.3.5",
+ "tokio",
+ "tokio-stream",
+ "xattr",
+]
+
+[[package]]
+name = "tokio-test"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e89b3cbabd3ae862100094ae433e1def582cf86451b4e9bf83aa7ac1d8a7d719"
+dependencies = [
+ "async-stream",
+ "bytes",
+ "futures-core",
+ "tokio",
+ "tokio-stream",
+]
+
+[[package]]
+name = "tokio-util"
+version = "0.7.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "futures-io",
+ "futures-sink",
+ "pin-project-lite",
+ "tokio",
+ "tracing",
+]
+
+[[package]]
+name = "toml"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fb9d890e4dc9298b70f740f615f2e05b9db37dce531f6b24fb77ac993f9f217"
+dependencies = [
+ "serde",
+ "serde_spanned",
+ "toml_datetime",
+ "toml_edit",
+]
+
+[[package]]
+name = "toml_datetime"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4553f467ac8e3d374bc9a177a26801e5d0f9b211aa1673fb137a403afd1c9cf5"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "toml_edit"
+version = "0.18.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56c59d8dd7d0dcbc6428bf7aa2f0e823e26e43b3c9aca15bbc9475d23e5fa12b"
+dependencies = [
+ "indexmap 1.9.3",
+ "nom8",
+ "serde",
+ "serde_spanned",
+ "toml_datetime",
+]
+
+[[package]]
+name = "tonic"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3082666a3a6433f7f511c7192923fa1fe07c69332d3c6a2e6bb040b569199d5a"
+dependencies = [
+ "async-trait",
+ "axum 0.6.20",
+ "base64",
+ "bytes",
+ "futures-core",
+ "futures-util",
+ "h2 0.3.24",
+ "http 0.2.11",
+ "http-body 0.4.6",
+ "hyper 0.14.28",
+ "hyper-timeout",
+ "percent-encoding",
+ "pin-project",
+ "prost 0.11.9",
+ "tokio",
+ "tokio-stream",
+ "tower",
+ "tower-layer",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "tonic"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76c4eb7a4e9ef9d4763600161f12f5070b92a578e1b634db88a6887844c91a13"
+dependencies = [
+ "async-stream",
+ "async-trait",
+ "axum 0.6.20",
+ "base64",
+ "bytes",
+ "h2 0.3.24",
+ "http 0.2.11",
+ "http-body 0.4.6",
+ "hyper 0.14.28",
+ "hyper-timeout",
+ "percent-encoding",
+ "pin-project",
+ "prost 0.12.3",
+ "rustls-native-certs 0.7.0",
+ "rustls-pemfile 2.1.0",
+ "rustls-pki-types",
+ "tokio",
+ "tokio-rustls 0.25.0",
+ "tokio-stream",
+ "tower",
+ "tower-layer",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "tonic-build"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be4ef6dd70a610078cb4e338a0f79d06bc759ff1b22d2120c2ff02ae264ba9c2"
+dependencies = [
+ "prettyplease",
+ "proc-macro2",
+ "prost-build",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "tonic-reflection"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "548c227bd5c0fae5925812c4ec6c66ffcfced23ea370cb823f4d18f0fc1cb6a7"
+dependencies = [
+ "prost 0.12.3",
+ "prost-types",
+ "tokio",
+ "tokio-stream",
+ "tonic 0.11.0",
+]
+
+[[package]]
+name = "tower"
+version = "0.4.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c"
+dependencies = [
+ "futures-core",
+ "futures-util",
+ "indexmap 1.9.3",
+ "pin-project",
+ "pin-project-lite",
+ "rand",
+ "slab",
+ "tokio",
+ "tokio-util",
+ "tower-layer",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "tower-layer"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0"
+
+[[package]]
+name = "tower-service"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52"
+
+[[package]]
+name = "tracing"
+version = "0.1.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
+dependencies = [
+ "log",
+ "pin-project-lite",
+ "tracing-attributes",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-attributes"
+version = "0.1.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
+dependencies = [
+ "once_cell",
+ "valuable",
+]
+
+[[package]]
+name = "tracing-futures"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2"
+dependencies = [
+ "pin-project",
+ "tracing",
+]
+
+[[package]]
+name = "tracing-log"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
+dependencies = [
+ "log",
+ "once_cell",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-opentelemetry"
+version = "0.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c67ac25c5407e7b961fafc6f7e9aa5958fd297aada2d20fa2ae1737357e55596"
+dependencies = [
+ "js-sys",
+ "once_cell",
+ "opentelemetry",
+ "opentelemetry_sdk",
+ "smallvec",
+ "tracing",
+ "tracing-core",
+ "tracing-log",
+ "tracing-subscriber",
+ "web-time",
+]
+
+[[package]]
+name = "tracing-serde"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1"
+dependencies = [
+ "serde",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-subscriber"
+version = "0.3.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b"
+dependencies = [
+ "matchers",
+ "nu-ansi-term",
+ "once_cell",
+ "regex",
+ "serde",
+ "serde_json",
+ "sharded-slab",
+ "smallvec",
+ "thread_local",
+ "tracing",
+ "tracing-core",
+ "tracing-log",
+ "tracing-serde",
+]
+
+[[package]]
+name = "try-lock"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
+
+[[package]]
+name = "tvix-build"
+version = "0.1.0"
+dependencies = [
+ "bytes",
+ "clap",
+ "itertools 0.12.0",
+ "prost 0.12.3",
+ "prost-build",
+ "rstest",
+ "thiserror",
+ "tokio",
+ "tokio-listener",
+ "tonic 0.11.0",
+ "tonic-build",
+ "tonic-reflection",
+ "tracing",
+ "tracing-subscriber",
+ "tvix-castore",
+ "url",
+]
+
+[[package]]
+name = "tvix-castore"
+version = "0.1.0"
+dependencies = [
+ "async-process",
+ "async-stream",
+ "async-tempfile",
+ "bigtable_rs",
+ "blake3",
+ "bstr",
+ "bytes",
+ "data-encoding",
+ "digest",
+ "fastcdc",
+ "fuse-backend-rs",
+ "futures",
+ "hex-literal",
+ "lazy_static",
+ "libc",
+ "object_store",
+ "parking_lot 0.12.1",
+ "petgraph",
+ "pin-project-lite",
+ "prost 0.12.3",
+ "prost-build",
+ "rstest",
+ "rstest_reuse",
+ "serde",
+ "serde_qs",
+ "serde_with",
+ "sled",
+ "tempfile",
+ "thiserror",
+ "tokio",
+ "tokio-retry",
+ "tokio-stream",
+ "tokio-tar",
+ "tokio-util",
+ "tonic 0.11.0",
+ "tonic-build",
+ "tonic-reflection",
+ "tower",
+ "tracing",
+ "url",
+ "vhost",
+ "vhost-user-backend",
+ "virtio-bindings 0.2.2",
+ "virtio-queue",
+ "vm-memory",
+ "vmm-sys-util",
+ "walkdir",
+ "xattr",
+ "zstd",
+]
+
+[[package]]
+name = "tvix-cli"
+version = "0.1.0"
+dependencies = [
+ "bytes",
+ "clap",
+ "dirs",
+ "nix-compat",
+ "rustyline",
+ "thiserror",
+ "tokio",
+ "tracing",
+ "tracing-subscriber",
+ "tvix-build",
+ "tvix-castore",
+ "tvix-eval",
+ "tvix-glue",
+ "tvix-store",
+ "wu-manber",
+]
+
+[[package]]
+name = "tvix-eval"
+version = "0.1.0"
+dependencies = [
+ "bstr",
+ "bytes",
+ "codemap",
+ "codemap-diagnostic",
+ "criterion",
+ "data-encoding",
+ "dirs",
+ "genawaiter",
+ "imbl",
+ "itertools 0.12.0",
+ "lazy_static",
+ "lexical-core",
+ "md-5",
+ "os_str_bytes",
+ "path-clean",
+ "pretty_assertions",
+ "proptest",
+ "regex",
+ "rnix",
+ "rowan",
+ "rstest",
+ "serde",
+ "serde_json",
+ "sha1",
+ "sha2",
+ "smol_str",
+ "tabwriter",
+ "tempfile",
+ "test-strategy",
+ "toml",
+ "tvix-eval-builtin-macros",
+ "xml-rs",
+]
+
+[[package]]
+name = "tvix-eval-builtin-macros"
+version = "0.0.1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+ "tvix-eval",
+]
+
+[[package]]
+name = "tvix-glue"
+version = "0.1.0"
+dependencies = [
+ "async-compression",
+ "bstr",
+ "bytes",
+ "criterion",
+ "data-encoding",
+ "futures",
+ "hex-literal",
+ "lazy_static",
+ "magic",
+ "md-5",
+ "nix 0.27.1",
+ "nix-compat",
+ "pin-project",
+ "pretty_assertions",
+ "reqwest",
+ "rstest",
+ "serde",
+ "serde_json",
+ "sha1",
+ "sha2",
+ "tempfile",
+ "thiserror",
+ "tokio",
+ "tokio-tar",
+ "tokio-util",
+ "tracing",
+ "tvix-build",
+ "tvix-castore",
+ "tvix-eval",
+ "tvix-store",
+ "url",
+ "walkdir",
+ "wu-manber",
+]
+
+[[package]]
+name = "tvix-serde"
+version = "0.1.0"
+dependencies = [
+ "bstr",
+ "serde",
+ "tvix-eval",
+]
+
+[[package]]
+name = "tvix-store"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "async-compression",
+ "async-process",
+ "async-stream",
+ "bigtable_rs",
+ "blake3",
+ "bstr",
+ "bytes",
+ "clap",
+ "count-write",
+ "data-encoding",
+ "futures",
+ "lazy_static",
+ "nix-compat",
+ "opentelemetry",
+ "opentelemetry-otlp",
+ "opentelemetry_sdk",
+ "pin-project-lite",
+ "prost 0.12.3",
+ "prost-build",
+ "reqwest",
+ "rstest",
+ "rstest_reuse",
+ "serde",
+ "serde_json",
+ "serde_qs",
+ "serde_with",
+ "sha2",
+ "sled",
+ "tempfile",
+ "thiserror",
+ "tokio",
+ "tokio-listener",
+ "tokio-retry",
+ "tokio-stream",
+ "tokio-util",
+ "tonic 0.11.0",
+ "tonic-build",
+ "tonic-reflection",
+ "tower",
+ "tracing",
+ "tracing-opentelemetry",
+ "tracing-subscriber",
+ "tvix-castore",
+ "url",
+ "walkdir",
+]
+
+[[package]]
+name = "typenum"
+version = "1.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
+
+[[package]]
+name = "typetag"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "661d18414ec032a49ece2d56eee03636e43c4e8d577047ab334c0ba892e29aaf"
+dependencies = [
+ "erased-serde",
+ "inventory",
+ "once_cell",
+ "serde",
+ "typetag-impl",
+]
+
+[[package]]
+name = "typetag-impl"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac73887f47b9312552aa90ef477927ff014d63d1920ca8037c6c1951eab64bb1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "unarray"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94"
+
+[[package]]
+name = "unicase"
+version = "2.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89"
+dependencies = [
+ "version_check",
+]
+
+[[package]]
+name = "unicode-bidi"
+version = "0.3.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+
+[[package]]
+name = "unicode-normalization"
+version = "0.1.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
+dependencies = [
+ "tinyvec",
+]
+
+[[package]]
+name = "unicode-segmentation"
+version = "1.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
+
+[[package]]
+name = "unicode-width"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
+
+[[package]]
+name = "untrusted"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
+
+[[package]]
+name = "url"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "percent-encoding",
+]
+
+[[package]]
+name = "urlencoding"
+version = "2.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
+
+[[package]]
+name = "utf8parse"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
+
+[[package]]
+name = "uuid"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "valuable"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
+
+[[package]]
+name = "vcpkg"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
+
+[[package]]
+name = "version_check"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
+
+[[package]]
+name = "vhost"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6769e8dbf5276b4376439fbf36bb880d203bf614bf7ef444198edc24b5a9f35"
+dependencies = [
+ "bitflags 1.3.2",
+ "libc",
+ "vm-memory",
+ "vmm-sys-util",
+]
+
+[[package]]
+name = "vhost-user-backend"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f237b91db4ac339d639fb43398b52d785fa51e3c7760ac9425148863c1f4303"
+dependencies = [
+ "libc",
+ "log",
+ "vhost",
+ "virtio-bindings 0.1.0",
+ "virtio-queue",
+ "vm-memory",
+ "vmm-sys-util",
+]
+
+[[package]]
+name = "virtio-bindings"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ff512178285488516ed85f15b5d0113a7cdb89e9e8a760b269ae4f02b84bd6b"
+
+[[package]]
+name = "virtio-bindings"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "878bcb1b2812a10c30d53b0ed054999de3d98f25ece91fc173973f9c57aaae86"
+
+[[package]]
+name = "virtio-queue"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ba81e2bcc21c0d2fc5e6683e79367e26ad219197423a498df801d79d5ba77bd"
+dependencies = [
+ "log",
+ "virtio-bindings 0.1.0",
+ "vm-memory",
+ "vmm-sys-util",
+]
+
+[[package]]
+name = "vm-memory"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "688a70366615b45575a424d9c665561c1b5ab2224d494f706b6a6812911a827c"
+dependencies = [
+ "arc-swap",
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "vmm-sys-util"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48b7b084231214f7427041e4220d77dfe726897a6d41fddee450696e66ff2a29"
+dependencies = [
+ "bitflags 1.3.2",
+ "libc",
+]
+
+[[package]]
+name = "wait-timeout"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "walkdir"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee"
+dependencies = [
+ "same-file",
+ "winapi-util",
+]
+
+[[package]]
+name = "want"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
+dependencies = [
+ "try-lock",
+]
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.90"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1223296a201415c7fad14792dbefaace9bd52b62d33453ade1c5b5f07555406"
+dependencies = [
+ "cfg-if",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.90"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fcdc935b63408d58a32f8cc9738a0bffd8f05cc7c002086c6ef20b7312ad9dcd"
+dependencies = [
+ "bumpalo",
+ "log",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-futures"
+version = "0.4.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bde2032aeb86bdfaecc8b261eef3cba735cc426c1f3a3416d1e0791be95fc461"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.90"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e4c238561b2d428924c49815533a8b9121c664599558a5d9ec51f8a1740a999"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.90"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.90"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b"
+
+[[package]]
+name = "wasm-streams"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4609d447824375f43e1ffbc051b50ad8f4b3ae8219680c94452ea05eb240ac7"
+dependencies = [
+ "futures-util",
+ "js-sys",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+]
+
+[[package]]
+name = "web-sys"
+version = "0.3.67"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "58cd2333b6e0be7a39605f0e255892fd7418a682d8da8fe042fe25128794d2ed"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "web-time"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa30049b1c872b72c89866d458eae9f20380ab280ffd1b1e18df2d3e2d98cfe0"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "which"
+version = "4.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7"
+dependencies = [
+ "either",
+ "home",
+ "once_cell",
+ "rustix",
+]
+
+[[package]]
+name = "which"
+version = "5.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9bf3ea8596f3a0dd5980b46430f2058dfe2c36a27ccfbb1845d6fbfcd9ba6e14"
+dependencies = [
+ "either",
+ "home",
+ "once_cell",
+ "rustix",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "windows-core"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
+dependencies = [
+ "windows-targets 0.52.0",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+dependencies = [
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets 0.52.0",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
+dependencies = [
+ "windows_aarch64_gnullvm 0.48.5",
+ "windows_aarch64_msvc 0.48.5",
+ "windows_i686_gnu 0.48.5",
+ "windows_i686_msvc 0.48.5",
+ "windows_x86_64_gnu 0.48.5",
+ "windows_x86_64_gnullvm 0.48.5",
+ "windows_x86_64_msvc 0.48.5",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
+dependencies = [
+ "windows_aarch64_gnullvm 0.52.0",
+ "windows_aarch64_msvc 0.52.0",
+ "windows_i686_gnu 0.52.0",
+ "windows_i686_msvc 0.52.0",
+ "windows_x86_64_gnu 0.52.0",
+ "windows_x86_64_gnullvm 0.52.0",
+ "windows_x86_64_msvc 0.52.0",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
+
+[[package]]
+name = "winreg"
+version = "0.50.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
+dependencies = [
+ "cfg-if",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "wu-manber"
+version = "0.1.0"
+source = "git+https://github.com/tvlfyi/wu-manber.git#0d5b22bea136659f7de60b102a7030e0daaa503d"
+
+[[package]]
+name = "xattr"
+version = "1.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f"
+dependencies = [
+ "libc",
+ "linux-raw-sys",
+ "rustix",
+]
+
+[[package]]
+name = "xml-rs"
+version = "0.8.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fcb9cbac069e033553e8bb871be2fbdffcab578eb25bd0f7c508cedc6dcd75a"
+
+[[package]]
+name = "xz2"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2"
+dependencies = [
+ "lzma-sys",
+]
+
+[[package]]
+name = "yansi"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec"
+
+[[package]]
+name = "zeroize"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d"
+
+[[package]]
+name = "zstd"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bffb3309596d527cfcba7dfc6ed6052f1d39dfbd7c867aa2e865e4a449c10110"
+dependencies = [
+ "zstd-safe",
+]
+
+[[package]]
+name = "zstd-safe"
+version = "7.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43747c7422e2924c11144d5229878b98180ef8b06cca4ab5af37afc8a8d8ea3e"
+dependencies = [
+ "zstd-sys",
+]
+
+[[package]]
+name = "zstd-sys"
+version = "2.0.9+zstd.1.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656"
+dependencies = [
+ "cc",
+ "pkg-config",
+]
diff --git a/tvix/Cargo.nix b/tvix/Cargo.nix
new file mode 100644
index 0000000000..0efb86c66d
--- /dev/null
+++ b/tvix/Cargo.nix
@@ -0,0 +1,17630 @@
+# This file was @generated by crate2nix 0.14.0 with the command:
+#   "generate" "--all-features"
+# See https://github.com/kolloch/crate2nix for more info.
+
+{ nixpkgs ? <nixpkgs>
+, pkgs ? import nixpkgs { config = { }; }
+, lib ? pkgs.lib
+, stdenv ? pkgs.stdenv
+, buildRustCrateForPkgs ? pkgs: pkgs.buildRustCrate
+  # This is used as the `crateOverrides` argument for `buildRustCrate`.
+, defaultCrateOverrides ? pkgs.defaultCrateOverrides
+  # The features to enable for the root_crate or the workspace_members.
+, rootFeatures ? [ "default" ]
+  # If true, throw errors instead of issueing deprecation warnings.
+, strictDeprecation ? false
+  # Used for conditional compilation based on CPU feature detection.
+, targetFeatures ? [ ]
+  # Whether to perform release builds: longer compile times, faster binaries.
+, release ? true
+  # Additional crate2nix configuration if it exists.
+, crateConfig ? if builtins.pathExists ./crate-config.nix
+  then pkgs.callPackage ./crate-config.nix { }
+  else { }
+}:
+
+rec {
+  #
+  # "public" attributes that we attempt to keep stable with new versions of crate2nix.
+  #
+
+
+  # Refer your crate build derivation by name here.
+  # You can override the features with
+  # workspaceMembers."${crateName}".build.override { features = [ "default" "feature1" ... ]; }.
+  workspaceMembers = {
+    "nix-compat" = rec {
+      packageId = "nix-compat";
+      build = internal.buildRustCrateWithFeatures {
+        packageId = "nix-compat";
+      };
+
+      # Debug support which might change between releases.
+      # File a bug if you depend on any for non-debug work!
+      debug = internal.debugCrate { inherit packageId; };
+    };
+    "tvix-build" = rec {
+      packageId = "tvix-build";
+      build = internal.buildRustCrateWithFeatures {
+        packageId = "tvix-build";
+      };
+
+      # Debug support which might change between releases.
+      # File a bug if you depend on any for non-debug work!
+      debug = internal.debugCrate { inherit packageId; };
+    };
+    "tvix-castore" = rec {
+      packageId = "tvix-castore";
+      build = internal.buildRustCrateWithFeatures {
+        packageId = "tvix-castore";
+      };
+
+      # Debug support which might change between releases.
+      # File a bug if you depend on any for non-debug work!
+      debug = internal.debugCrate { inherit packageId; };
+    };
+    "tvix-cli" = rec {
+      packageId = "tvix-cli";
+      build = internal.buildRustCrateWithFeatures {
+        packageId = "tvix-cli";
+      };
+
+      # Debug support which might change between releases.
+      # File a bug if you depend on any for non-debug work!
+      debug = internal.debugCrate { inherit packageId; };
+    };
+    "tvix-eval" = rec {
+      packageId = "tvix-eval";
+      build = internal.buildRustCrateWithFeatures {
+        packageId = "tvix-eval";
+      };
+
+      # Debug support which might change between releases.
+      # File a bug if you depend on any for non-debug work!
+      debug = internal.debugCrate { inherit packageId; };
+    };
+    "tvix-eval-builtin-macros" = rec {
+      packageId = "tvix-eval-builtin-macros";
+      build = internal.buildRustCrateWithFeatures {
+        packageId = "tvix-eval-builtin-macros";
+      };
+
+      # Debug support which might change between releases.
+      # File a bug if you depend on any for non-debug work!
+      debug = internal.debugCrate { inherit packageId; };
+    };
+    "tvix-glue" = rec {
+      packageId = "tvix-glue";
+      build = internal.buildRustCrateWithFeatures {
+        packageId = "tvix-glue";
+      };
+
+      # Debug support which might change between releases.
+      # File a bug if you depend on any for non-debug work!
+      debug = internal.debugCrate { inherit packageId; };
+    };
+    "tvix-serde" = rec {
+      packageId = "tvix-serde";
+      build = internal.buildRustCrateWithFeatures {
+        packageId = "tvix-serde";
+      };
+
+      # Debug support which might change between releases.
+      # File a bug if you depend on any for non-debug work!
+      debug = internal.debugCrate { inherit packageId; };
+    };
+    "tvix-store" = rec {
+      packageId = "tvix-store";
+      build = internal.buildRustCrateWithFeatures {
+        packageId = "tvix-store";
+      };
+
+      # Debug support which might change between releases.
+      # File a bug if you depend on any for non-debug work!
+      debug = internal.debugCrate { inherit packageId; };
+    };
+  };
+
+  # A derivation that joins the outputs of all workspace members together.
+  allWorkspaceMembers = pkgs.symlinkJoin {
+    name = "all-workspace-members";
+    paths =
+      let members = builtins.attrValues workspaceMembers;
+      in builtins.map (m: m.build) members;
+  };
+
+  #
+  # "internal" ("private") attributes that may change in every new version of crate2nix.
+  #
+
+  internal = rec {
+    # Build and dependency information for crates.
+    # Many of the fields are passed one-to-one to buildRustCrate.
+    #
+    # Noteworthy:
+    # * `dependencies`/`buildDependencies`: similar to the corresponding fields for buildRustCrate.
+    #   but with additional information which is used during dependency/feature resolution.
+    # * `resolvedDependencies`: the selected default features reported by cargo - only included for debugging.
+    # * `devDependencies` as of now not used by `buildRustCrate` but used to
+    #   inject test dependencies into the build
+
+    crates = {
+      "addr2line" = rec {
+        crateName = "addr2line";
+        version = "0.21.0";
+        edition = "2018";
+        sha256 = "1jx0k3iwyqr8klqbzk6kjvr496yd94aspis10vwsj5wy7gib4c4a";
+        dependencies = [
+          {
+            name = "gimli";
+            packageId = "gimli";
+            usesDefaultFeatures = false;
+            features = [ "read" ];
+          }
+        ];
+        features = {
+          "alloc" = [ "dep:alloc" ];
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "core" = [ "dep:core" ];
+          "cpp_demangle" = [ "dep:cpp_demangle" ];
+          "default" = [ "rustc-demangle" "cpp_demangle" "std-object" "fallible-iterator" "smallvec" "memmap2" ];
+          "fallible-iterator" = [ "dep:fallible-iterator" ];
+          "memmap2" = [ "dep:memmap2" ];
+          "object" = [ "dep:object" ];
+          "rustc-demangle" = [ "dep:rustc-demangle" ];
+          "rustc-dep-of-std" = [ "core" "alloc" "compiler_builtins" "gimli/rustc-dep-of-std" ];
+          "smallvec" = [ "dep:smallvec" ];
+          "std" = [ "gimli/std" ];
+          "std-object" = [ "std" "object" "object/std" "object/compression" "gimli/endian-reader" ];
+        };
+      };
+      "adler" = rec {
+        crateName = "adler";
+        version = "1.0.2";
+        edition = "2015";
+        sha256 = "1zim79cvzd5yrkzl3nyfx0avijwgk9fqv3yrscdy1cc79ih02qpj";
+        authors = [
+          "Jonas Schievink <jonasschievink@gmail.com>"
+        ];
+        features = {
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "core" = [ "dep:core" ];
+          "default" = [ "std" ];
+          "rustc-dep-of-std" = [ "core" "compiler_builtins" ];
+        };
+      };
+      "aho-corasick" = rec {
+        crateName = "aho-corasick";
+        version = "1.1.2";
+        edition = "2021";
+        sha256 = "1w510wnixvlgimkx1zjbvlxh6xps2vjgfqgwf5a6adlbjp5rv5mj";
+        libName = "aho_corasick";
+        authors = [
+          "Andrew Gallant <jamslam@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "memchr";
+            packageId = "memchr";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "default" = [ "std" "perf-literal" ];
+          "logging" = [ "dep:log" ];
+          "perf-literal" = [ "dep:memchr" ];
+          "std" = [ "memchr?/std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "perf-literal" "std" ];
+      };
+      "android-tzdata" = rec {
+        crateName = "android-tzdata";
+        version = "0.1.1";
+        edition = "2018";
+        sha256 = "1w7ynjxrfs97xg3qlcdns4kgfpwcdv824g611fq32cag4cdr96g9";
+        authors = [
+          "RumovZ"
+        ];
+
+      };
+      "android_system_properties" = rec {
+        crateName = "android_system_properties";
+        version = "0.1.5";
+        edition = "2018";
+        sha256 = "04b3wrz12837j7mdczqd95b732gw5q7q66cv4yn4646lvccp57l1";
+        authors = [
+          "Nicolas Silva <nical@fastmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+          }
+        ];
+
+      };
+      "anes" = rec {
+        crateName = "anes";
+        version = "0.1.6";
+        edition = "2018";
+        sha256 = "16bj1ww1xkwzbckk32j2pnbn5vk6wgsl3q4p3j9551xbcarwnijb";
+        authors = [
+          "Robert Vojta <rvojta@me.com>"
+        ];
+        features = {
+          "bitflags" = [ "dep:bitflags" ];
+          "parser" = [ "bitflags" ];
+        };
+        resolvedDefaultFeatures = [ "default" ];
+      };
+      "anstream" = rec {
+        crateName = "anstream";
+        version = "0.6.11";
+        edition = "2021";
+        sha256 = "19dndamalavhjwp4i74k8hdijcixb7gsfa6ycwyc1r8xn6y1wbkf";
+        dependencies = [
+          {
+            name = "anstyle";
+            packageId = "anstyle";
+          }
+          {
+            name = "anstyle-parse";
+            packageId = "anstyle-parse";
+          }
+          {
+            name = "anstyle-query";
+            packageId = "anstyle-query";
+            optional = true;
+          }
+          {
+            name = "anstyle-wincon";
+            packageId = "anstyle-wincon";
+            optional = true;
+            target = { target, features }: (target."windows" or false);
+          }
+          {
+            name = "colorchoice";
+            packageId = "colorchoice";
+          }
+          {
+            name = "utf8parse";
+            packageId = "utf8parse";
+          }
+        ];
+        features = {
+          "auto" = [ "dep:anstyle-query" ];
+          "default" = [ "auto" "wincon" ];
+          "wincon" = [ "dep:anstyle-wincon" ];
+        };
+        resolvedDefaultFeatures = [ "auto" "default" "wincon" ];
+      };
+      "anstyle" = rec {
+        crateName = "anstyle";
+        version = "1.0.4";
+        edition = "2021";
+        sha256 = "11yxw02b6parn29s757z96rgiqbn8qy0fk9a3p3bhczm85dhfybh";
+        features = {
+          "default" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "anstyle-parse" = rec {
+        crateName = "anstyle-parse";
+        version = "0.2.3";
+        edition = "2021";
+        sha256 = "134jhzrz89labrdwxxnjxqjdg06qvaflj1wkfnmyapwyldfwcnn7";
+        dependencies = [
+          {
+            name = "utf8parse";
+            packageId = "utf8parse";
+            optional = true;
+          }
+        ];
+        features = {
+          "core" = [ "dep:arrayvec" ];
+          "default" = [ "utf8" ];
+          "utf8" = [ "dep:utf8parse" ];
+        };
+        resolvedDefaultFeatures = [ "default" "utf8" ];
+      };
+      "anstyle-query" = rec {
+        crateName = "anstyle-query";
+        version = "1.0.2";
+        edition = "2021";
+        sha256 = "0j3na4b1nma39g4x7cwvj009awxckjf3z2vkwhldgka44hqj72g2";
+        dependencies = [
+          {
+            name = "windows-sys";
+            packageId = "windows-sys 0.52.0";
+            target = { target, features }: (target."windows" or false);
+            features = [ "Win32_System_Console" "Win32_Foundation" ];
+          }
+        ];
+
+      };
+      "anstyle-wincon" = rec {
+        crateName = "anstyle-wincon";
+        version = "3.0.2";
+        edition = "2021";
+        sha256 = "19v0fv400bmp4niqpzxnhg83vz12mmqv7l2l8vi80qcdxj0lpm8w";
+        dependencies = [
+          {
+            name = "anstyle";
+            packageId = "anstyle";
+          }
+          {
+            name = "windows-sys";
+            packageId = "windows-sys 0.52.0";
+            target = { target, features }: (target."windows" or false);
+            features = [ "Win32_System_Console" "Win32_Foundation" ];
+          }
+        ];
+
+      };
+      "anyhow" = rec {
+        crateName = "anyhow";
+        version = "1.0.79";
+        edition = "2018";
+        sha256 = "1ji5irqiwr8yprgqj8zvnli7zd7fz9kzaiddq44jnrl2l289h3h8";
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        features = {
+          "backtrace" = [ "dep:backtrace" ];
+          "default" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "arc-swap" = rec {
+        crateName = "arc-swap";
+        version = "1.6.0";
+        edition = "2018";
+        sha256 = "19n9j146bpxs9phyh48gmlh9jjsdijr9p9br04qms0g9ypfsvp5x";
+        authors = [
+          "Michal 'vorner' Vaner <vorner@vorner.cz>"
+        ];
+        features = {
+          "serde" = [ "dep:serde" ];
+        };
+      };
+      "arrayref" = rec {
+        crateName = "arrayref";
+        version = "0.3.7";
+        edition = "2015";
+        sha256 = "0ia5ndyxqkzdymqr4ls53jdmajf09adjimg5kvw65kkprg930jbb";
+        authors = [
+          "David Roundy <roundyd@physics.oregonstate.edu>"
+        ];
+
+      };
+      "arrayvec" = rec {
+        crateName = "arrayvec";
+        version = "0.7.4";
+        edition = "2018";
+        sha256 = "04b7n722jij0v3fnm3qk072d5ysc2q30rl9fz33zpfhzah30mlwn";
+        authors = [
+          "bluss"
+        ];
+        features = {
+          "default" = [ "std" ];
+          "serde" = [ "dep:serde" ];
+          "zeroize" = [ "dep:zeroize" ];
+        };
+      };
+      "async-channel" = rec {
+        crateName = "async-channel";
+        version = "2.2.0";
+        edition = "2021";
+        sha256 = "1hzhkbrlmgbrrwb1d5aba5f03p42s6z80g5p38s127c27nj470pj";
+        authors = [
+          "Stjepan Glavina <stjepang@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "concurrent-queue";
+            packageId = "concurrent-queue";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "event-listener";
+            packageId = "event-listener 5.2.0";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "event-listener-strategy";
+            packageId = "event-listener-strategy 0.5.0";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-core";
+            packageId = "futures-core";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "pin-project-lite";
+            packageId = "pin-project-lite";
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "std" = [ "concurrent-queue/std" "event-listener/std" "event-listener-strategy/std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "async-compression" = rec {
+        crateName = "async-compression";
+        version = "0.4.9";
+        edition = "2018";
+        sha256 = "14r6vbsbbkqjiqy0qwwywjakdi29jfyidhqp389l5r4gm7bsp7jf";
+        authors = [
+          "Wim Looman <wim@nemo157.com>"
+          "Allen Bui <fairingrey@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "bzip2";
+            packageId = "bzip2";
+            optional = true;
+          }
+          {
+            name = "flate2";
+            packageId = "flate2";
+            optional = true;
+          }
+          {
+            name = "futures-core";
+            packageId = "futures-core";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "memchr";
+            packageId = "memchr";
+          }
+          {
+            name = "pin-project-lite";
+            packageId = "pin-project-lite";
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "xz2";
+            packageId = "xz2";
+            optional = true;
+          }
+          {
+            name = "zstd";
+            packageId = "zstd";
+            rename = "libzstd";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "zstd-safe";
+            packageId = "zstd-safe";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+        ];
+        devDependencies = [
+          {
+            name = "tokio";
+            packageId = "tokio";
+            usesDefaultFeatures = false;
+            features = [ "io-util" "macros" "rt-multi-thread" "io-std" ];
+          }
+        ];
+        features = {
+          "all" = [ "all-implementations" "all-algorithms" ];
+          "all-algorithms" = [ "brotli" "bzip2" "deflate" "gzip" "lzma" "xz" "zlib" "zstd" "deflate64" ];
+          "all-implementations" = [ "futures-io" "tokio" ];
+          "brotli" = [ "dep:brotli" ];
+          "bzip2" = [ "dep:bzip2" ];
+          "deflate" = [ "flate2" ];
+          "deflate64" = [ "dep:deflate64" ];
+          "flate2" = [ "dep:flate2" ];
+          "futures-io" = [ "dep:futures-io" ];
+          "gzip" = [ "flate2" ];
+          "libzstd" = [ "dep:libzstd" ];
+          "lzma" = [ "xz2" ];
+          "tokio" = [ "dep:tokio" ];
+          "xz" = [ "xz2" ];
+          "xz2" = [ "dep:xz2" ];
+          "zlib" = [ "flate2" ];
+          "zstd" = [ "libzstd" "zstd-safe" ];
+          "zstd-safe" = [ "dep:zstd-safe" ];
+          "zstdmt" = [ "zstd" "zstd-safe/zstdmt" ];
+        };
+        resolvedDefaultFeatures = [ "bzip2" "flate2" "gzip" "libzstd" "tokio" "xz" "xz2" "zstd" "zstd-safe" ];
+      };
+      "async-io" = rec {
+        crateName = "async-io";
+        version = "2.3.2";
+        edition = "2021";
+        sha256 = "110847w0ycfhklm3i928avd28x7lf9amblr2wjngi8ngk7sv1k6w";
+        authors = [
+          "Stjepan Glavina <stjepang@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "async-lock";
+            packageId = "async-lock 3.3.0";
+          }
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "concurrent-queue";
+            packageId = "concurrent-queue";
+          }
+          {
+            name = "futures-io";
+            packageId = "futures-io";
+            usesDefaultFeatures = false;
+            features = [ "std" ];
+          }
+          {
+            name = "futures-lite";
+            packageId = "futures-lite";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "parking";
+            packageId = "parking";
+          }
+          {
+            name = "polling";
+            packageId = "polling";
+          }
+          {
+            name = "rustix";
+            packageId = "rustix";
+            usesDefaultFeatures = false;
+            features = [ "fs" "net" "std" ];
+          }
+          {
+            name = "slab";
+            packageId = "slab";
+          }
+          {
+            name = "tracing";
+            packageId = "tracing";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "windows-sys";
+            packageId = "windows-sys 0.52.0";
+            target = { target, features }: (target."windows" or false);
+            features = [ "Win32_Foundation" ];
+          }
+        ];
+
+      };
+      "async-lock 2.8.0" = rec {
+        crateName = "async-lock";
+        version = "2.8.0";
+        edition = "2018";
+        sha256 = "0asq5xdzgp3d5m82y5rg7a0k9q0g95jy6mgc7ivl334x7qlp4wi8";
+        authors = [
+          "Stjepan Glavina <stjepang@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "event-listener";
+            packageId = "event-listener 2.5.3";
+          }
+        ];
+
+      };
+      "async-lock 3.3.0" = rec {
+        crateName = "async-lock";
+        version = "3.3.0";
+        edition = "2021";
+        sha256 = "0yxflkfw46rad4lv86f59b5z555dlfmg1riz1n8830rgi0qb8d6h";
+        authors = [
+          "Stjepan Glavina <stjepang@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "event-listener";
+            packageId = "event-listener 4.0.3";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "event-listener-strategy";
+            packageId = "event-listener-strategy 0.4.0";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "pin-project-lite";
+            packageId = "pin-project-lite";
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "std" = [ "event-listener/std" "event-listener-strategy/std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "async-process" = rec {
+        crateName = "async-process";
+        version = "2.1.0";
+        edition = "2021";
+        sha256 = "1j0cfac9p3kq5dclfzlz6jv5l29kwflh9nvr3ivmdg8ih3v3q7j5";
+        authors = [
+          "Stjepan Glavina <stjepang@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "async-channel";
+            packageId = "async-channel";
+            target = { target, features }: (target."windows" or false);
+          }
+          {
+            name = "async-io";
+            packageId = "async-io";
+            target = { target, features }: (target."unix" or false);
+          }
+          {
+            name = "async-lock";
+            packageId = "async-lock 3.3.0";
+          }
+          {
+            name = "async-signal";
+            packageId = "async-signal";
+            target = { target, features }: (target."unix" or false);
+          }
+          {
+            name = "blocking";
+            packageId = "blocking";
+            target = { target, features }: (target."windows" or false);
+          }
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "event-listener";
+            packageId = "event-listener 5.2.0";
+          }
+          {
+            name = "futures-lite";
+            packageId = "futures-lite";
+          }
+          {
+            name = "rustix";
+            packageId = "rustix";
+            usesDefaultFeatures = false;
+            target = { target, features }: (target."unix" or false);
+            features = [ "std" "fs" ];
+          }
+          {
+            name = "windows-sys";
+            packageId = "windows-sys 0.52.0";
+            usesDefaultFeatures = false;
+            target = { target, features }: (target."windows" or false);
+            features = [ "Win32_Foundation" "Win32_System_Threading" ];
+          }
+        ];
+        devDependencies = [
+          {
+            name = "async-io";
+            packageId = "async-io";
+          }
+        ];
+
+      };
+      "async-signal" = rec {
+        crateName = "async-signal";
+        version = "0.2.5";
+        edition = "2018";
+        sha256 = "1i9466hiqghhmljjnn83a8vnxi8z013xga03f59c89d2cl7xjiwy";
+        authors = [
+          "John Nunley <dev@notgull.net>"
+        ];
+        dependencies = [
+          {
+            name = "async-io";
+            packageId = "async-io";
+            target = { target, features }: (target."unix" or false);
+          }
+          {
+            name = "async-lock";
+            packageId = "async-lock 2.8.0";
+            target = { target, features }: (target."windows" or false);
+          }
+          {
+            name = "atomic-waker";
+            packageId = "atomic-waker";
+            target = { target, features }: (target."windows" or false);
+          }
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "futures-core";
+            packageId = "futures-core";
+          }
+          {
+            name = "futures-io";
+            packageId = "futures-io";
+            target = { target, features }: (target."unix" or false);
+          }
+          {
+            name = "rustix";
+            packageId = "rustix";
+            usesDefaultFeatures = false;
+            target = { target, features }: (target."unix" or false);
+            features = [ "process" "std" ];
+          }
+          {
+            name = "signal-hook-registry";
+            packageId = "signal-hook-registry";
+            target = { target, features }: (target."unix" or false);
+          }
+          {
+            name = "slab";
+            packageId = "slab";
+            target = { target, features }: (target."windows" or false);
+          }
+          {
+            name = "windows-sys";
+            packageId = "windows-sys 0.48.0";
+            usesDefaultFeatures = false;
+            target = { target, features }: (target."windows" or false);
+            features = [ "Win32_Foundation" "Win32_System_Console" ];
+          }
+        ];
+        devDependencies = [
+          {
+            name = "async-io";
+            packageId = "async-io";
+          }
+        ];
+
+      };
+      "async-stream" = rec {
+        crateName = "async-stream";
+        version = "0.3.5";
+        edition = "2018";
+        sha256 = "0l8sjq1rylkb1ak0pdyjn83b3k6x36j22myngl4sqqgg7whdsmnd";
+        authors = [
+          "Carl Lerche <me@carllerche.com>"
+        ];
+        dependencies = [
+          {
+            name = "async-stream-impl";
+            packageId = "async-stream-impl";
+          }
+          {
+            name = "futures-core";
+            packageId = "futures-core";
+          }
+          {
+            name = "pin-project-lite";
+            packageId = "pin-project-lite";
+          }
+        ];
+
+      };
+      "async-stream-impl" = rec {
+        crateName = "async-stream-impl";
+        version = "0.3.5";
+        edition = "2018";
+        sha256 = "14q179j4y8p2z1d0ic6aqgy9fhwz8p9cai1ia8kpw4bw7q12mrhn";
+        procMacro = true;
+        authors = [
+          "Carl Lerche <me@carllerche.com>"
+        ];
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn 2.0.48";
+            features = [ "full" "visit-mut" ];
+          }
+        ];
+
+      };
+      "async-task" = rec {
+        crateName = "async-task";
+        version = "4.7.0";
+        edition = "2018";
+        sha256 = "16975vx6aqy5yf16fs9xz5vx1zq8mwkzfmykvcilc1j7b6c6xczv";
+        authors = [
+          "Stjepan Glavina <stjepang@gmail.com>"
+        ];
+        features = {
+          "default" = [ "std" ];
+          "portable-atomic" = [ "dep:portable-atomic" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "async-tempfile" = rec {
+        crateName = "async-tempfile";
+        version = "0.4.0";
+        edition = "2021";
+        sha256 = "16zx4qcwzq94n13pp6xwa4589apm5y8j20jb7lk4yzn42fqlnzdk";
+        authors = [
+          "Markus Mayer"
+        ];
+        dependencies = [
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "fs" ];
+          }
+          {
+            name = "uuid";
+            packageId = "uuid";
+            optional = true;
+            features = [ "v4" ];
+          }
+        ];
+        devDependencies = [
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "rt-multi-thread" "macros" ];
+          }
+        ];
+        features = {
+          "default" = [ "uuid" ];
+          "uuid" = [ "dep:uuid" ];
+        };
+        resolvedDefaultFeatures = [ "default" "uuid" ];
+      };
+      "async-trait" = rec {
+        crateName = "async-trait";
+        version = "0.1.77";
+        edition = "2021";
+        sha256 = "1adf1jh2yg39rkpmqjqyr9xyd6849p0d95425i6imgbhx0syx069";
+        procMacro = true;
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn 2.0.48";
+            features = [ "full" "visit-mut" ];
+          }
+        ];
+
+      };
+      "atomic-waker" = rec {
+        crateName = "atomic-waker";
+        version = "1.1.2";
+        edition = "2018";
+        sha256 = "1h5av1lw56m0jf0fd3bchxq8a30xv0b4wv8s4zkp4s0i7mfvs18m";
+        authors = [
+          "Stjepan Glavina <stjepang@gmail.com>"
+          "Contributors to futures-rs"
+        ];
+        features = {
+          "portable-atomic" = [ "dep:portable-atomic" ];
+        };
+      };
+      "autocfg" = rec {
+        crateName = "autocfg";
+        version = "1.1.0";
+        edition = "2015";
+        sha256 = "1ylp3cb47ylzabimazvbz9ms6ap784zhb6syaz6c1jqpmcmq0s6l";
+        authors = [
+          "Josh Stone <cuviper@gmail.com>"
+        ];
+
+      };
+      "axum 0.6.20" = rec {
+        crateName = "axum";
+        version = "0.6.20";
+        edition = "2021";
+        sha256 = "1gynqkg3dcy1zd7il69h8a3zax86v6qq5zpawqyn87mr6979x0iv";
+        dependencies = [
+          {
+            name = "async-trait";
+            packageId = "async-trait";
+          }
+          {
+            name = "axum-core";
+            packageId = "axum-core 0.3.4";
+          }
+          {
+            name = "bitflags";
+            packageId = "bitflags 1.3.2";
+          }
+          {
+            name = "bytes";
+            packageId = "bytes";
+          }
+          {
+            name = "futures-util";
+            packageId = "futures-util";
+            usesDefaultFeatures = false;
+            features = [ "alloc" ];
+          }
+          {
+            name = "http";
+            packageId = "http 0.2.11";
+          }
+          {
+            name = "http-body";
+            packageId = "http-body 0.4.6";
+          }
+          {
+            name = "hyper";
+            packageId = "hyper 0.14.28";
+            features = [ "stream" ];
+          }
+          {
+            name = "itoa";
+            packageId = "itoa";
+          }
+          {
+            name = "matchit";
+            packageId = "matchit";
+          }
+          {
+            name = "memchr";
+            packageId = "memchr";
+          }
+          {
+            name = "mime";
+            packageId = "mime";
+          }
+          {
+            name = "percent-encoding";
+            packageId = "percent-encoding";
+          }
+          {
+            name = "pin-project-lite";
+            packageId = "pin-project-lite";
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+          }
+          {
+            name = "sync_wrapper";
+            packageId = "sync_wrapper";
+          }
+          {
+            name = "tower";
+            packageId = "tower";
+            usesDefaultFeatures = false;
+            features = [ "util" ];
+          }
+          {
+            name = "tower-layer";
+            packageId = "tower-layer";
+          }
+          {
+            name = "tower-service";
+            packageId = "tower-service";
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "rustversion";
+            packageId = "rustversion";
+          }
+        ];
+        devDependencies = [
+          {
+            name = "rustversion";
+            packageId = "rustversion";
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+            features = [ "derive" ];
+          }
+          {
+            name = "tower";
+            packageId = "tower";
+            rename = "tower";
+            features = [ "util" "timeout" "limit" "load-shed" "steer" "filter" ];
+          }
+        ];
+        features = {
+          "__private_docs" = [ "tower/full" "dep:tower-http" ];
+          "default" = [ "form" "http1" "json" "matched-path" "original-uri" "query" "tokio" "tower-log" ];
+          "form" = [ "dep:serde_urlencoded" ];
+          "headers" = [ "dep:headers" ];
+          "http1" = [ "hyper/http1" ];
+          "http2" = [ "hyper/http2" ];
+          "json" = [ "dep:serde_json" "dep:serde_path_to_error" ];
+          "macros" = [ "dep:axum-macros" ];
+          "multipart" = [ "dep:multer" ];
+          "query" = [ "dep:serde_urlencoded" ];
+          "tokio" = [ "dep:tokio" "hyper/server" "hyper/tcp" "hyper/runtime" "tower/make" ];
+          "tower-log" = [ "tower/log" ];
+          "tracing" = [ "dep:tracing" "axum-core/tracing" ];
+          "ws" = [ "tokio" "dep:tokio-tungstenite" "dep:sha1" "dep:base64" ];
+        };
+      };
+      "axum 0.7.4" = rec {
+        crateName = "axum";
+        version = "0.7.4";
+        edition = "2021";
+        sha256 = "17kv7v8m981cqmfbv5m538fzxhw51l9bajv06kfddi7njarb8dhj";
+        dependencies = [
+          {
+            name = "async-trait";
+            packageId = "async-trait";
+          }
+          {
+            name = "axum-core";
+            packageId = "axum-core 0.4.3";
+          }
+          {
+            name = "bytes";
+            packageId = "bytes";
+          }
+          {
+            name = "futures-util";
+            packageId = "futures-util";
+            usesDefaultFeatures = false;
+            features = [ "alloc" ];
+          }
+          {
+            name = "http";
+            packageId = "http 1.1.0";
+          }
+          {
+            name = "http-body";
+            packageId = "http-body 1.0.0";
+          }
+          {
+            name = "http-body-util";
+            packageId = "http-body-util";
+          }
+          {
+            name = "hyper";
+            packageId = "hyper 1.2.0";
+            optional = true;
+          }
+          {
+            name = "hyper-util";
+            packageId = "hyper-util";
+            optional = true;
+            features = [ "tokio" "server" "server-auto" ];
+          }
+          {
+            name = "itoa";
+            packageId = "itoa";
+          }
+          {
+            name = "matchit";
+            packageId = "matchit";
+          }
+          {
+            name = "memchr";
+            packageId = "memchr";
+          }
+          {
+            name = "mime";
+            packageId = "mime";
+          }
+          {
+            name = "percent-encoding";
+            packageId = "percent-encoding";
+          }
+          {
+            name = "pin-project-lite";
+            packageId = "pin-project-lite";
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+          }
+          {
+            name = "serde_json";
+            packageId = "serde_json";
+            optional = true;
+            features = [ "raw_value" ];
+          }
+          {
+            name = "serde_path_to_error";
+            packageId = "serde_path_to_error";
+            optional = true;
+          }
+          {
+            name = "serde_urlencoded";
+            packageId = "serde_urlencoded";
+            optional = true;
+          }
+          {
+            name = "sync_wrapper";
+            packageId = "sync_wrapper";
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+            rename = "tokio";
+            optional = true;
+            features = [ "time" ];
+          }
+          {
+            name = "tower";
+            packageId = "tower";
+            usesDefaultFeatures = false;
+            features = [ "util" ];
+          }
+          {
+            name = "tower-layer";
+            packageId = "tower-layer";
+          }
+          {
+            name = "tower-service";
+            packageId = "tower-service";
+          }
+          {
+            name = "tracing";
+            packageId = "tracing";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "rustversion";
+            packageId = "rustversion";
+          }
+        ];
+        devDependencies = [
+          {
+            name = "rustversion";
+            packageId = "rustversion";
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+            features = [ "derive" ];
+          }
+          {
+            name = "serde_json";
+            packageId = "serde_json";
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+            rename = "tokio";
+            features = [ "macros" "rt" "rt-multi-thread" "net" "test-util" ];
+          }
+          {
+            name = "tower";
+            packageId = "tower";
+            rename = "tower";
+            features = [ "util" "timeout" "limit" "load-shed" "steer" "filter" ];
+          }
+          {
+            name = "tracing";
+            packageId = "tracing";
+          }
+        ];
+        features = {
+          "__private_docs" = [ "tower/full" "dep:tower-http" ];
+          "default" = [ "form" "http1" "json" "matched-path" "original-uri" "query" "tokio" "tower-log" "tracing" ];
+          "form" = [ "dep:serde_urlencoded" ];
+          "http1" = [ "dep:hyper" "hyper?/http1" ];
+          "http2" = [ "dep:hyper" "hyper?/http2" ];
+          "json" = [ "dep:serde_json" "dep:serde_path_to_error" ];
+          "macros" = [ "dep:axum-macros" ];
+          "multipart" = [ "dep:multer" ];
+          "query" = [ "dep:serde_urlencoded" ];
+          "tokio" = [ "dep:hyper-util" "dep:tokio" "tokio/net" "tokio/rt" "tower/make" "tokio/macros" ];
+          "tower-log" = [ "tower/log" ];
+          "tracing" = [ "dep:tracing" "axum-core/tracing" ];
+          "ws" = [ "dep:hyper" "tokio" "dep:tokio-tungstenite" "dep:sha1" "dep:base64" ];
+        };
+        resolvedDefaultFeatures = [ "default" "form" "http1" "json" "matched-path" "original-uri" "query" "tokio" "tower-log" "tracing" ];
+      };
+      "axum-core 0.3.4" = rec {
+        crateName = "axum-core";
+        version = "0.3.4";
+        edition = "2021";
+        sha256 = "0b1d9nkqb8znaba4qqzxzc968qwj4ybn4vgpyz9lz4a7l9vsb7vm";
+        dependencies = [
+          {
+            name = "async-trait";
+            packageId = "async-trait";
+          }
+          {
+            name = "bytes";
+            packageId = "bytes";
+          }
+          {
+            name = "futures-util";
+            packageId = "futures-util";
+            usesDefaultFeatures = false;
+            features = [ "alloc" ];
+          }
+          {
+            name = "http";
+            packageId = "http 0.2.11";
+          }
+          {
+            name = "http-body";
+            packageId = "http-body 0.4.6";
+          }
+          {
+            name = "mime";
+            packageId = "mime";
+          }
+          {
+            name = "tower-layer";
+            packageId = "tower-layer";
+          }
+          {
+            name = "tower-service";
+            packageId = "tower-service";
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "rustversion";
+            packageId = "rustversion";
+          }
+        ];
+        devDependencies = [
+          {
+            name = "futures-util";
+            packageId = "futures-util";
+            usesDefaultFeatures = false;
+            features = [ "alloc" ];
+          }
+        ];
+        features = {
+          "__private_docs" = [ "dep:tower-http" ];
+          "tracing" = [ "dep:tracing" ];
+        };
+      };
+      "axum-core 0.4.3" = rec {
+        crateName = "axum-core";
+        version = "0.4.3";
+        edition = "2021";
+        sha256 = "1qx28wg4j6qdcdrisqwyaavlzc0zvbsrcwa99zf9456lfbyn6p51";
+        dependencies = [
+          {
+            name = "async-trait";
+            packageId = "async-trait";
+          }
+          {
+            name = "bytes";
+            packageId = "bytes";
+          }
+          {
+            name = "futures-util";
+            packageId = "futures-util";
+            usesDefaultFeatures = false;
+            features = [ "alloc" ];
+          }
+          {
+            name = "http";
+            packageId = "http 1.1.0";
+          }
+          {
+            name = "http-body";
+            packageId = "http-body 1.0.0";
+          }
+          {
+            name = "http-body-util";
+            packageId = "http-body-util";
+          }
+          {
+            name = "mime";
+            packageId = "mime";
+          }
+          {
+            name = "pin-project-lite";
+            packageId = "pin-project-lite";
+          }
+          {
+            name = "sync_wrapper";
+            packageId = "sync_wrapper";
+          }
+          {
+            name = "tower-layer";
+            packageId = "tower-layer";
+          }
+          {
+            name = "tower-service";
+            packageId = "tower-service";
+          }
+          {
+            name = "tracing";
+            packageId = "tracing";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "rustversion";
+            packageId = "rustversion";
+          }
+        ];
+        devDependencies = [
+          {
+            name = "futures-util";
+            packageId = "futures-util";
+            usesDefaultFeatures = false;
+            features = [ "alloc" ];
+          }
+        ];
+        features = {
+          "__private_docs" = [ "dep:tower-http" ];
+          "tracing" = [ "dep:tracing" ];
+        };
+        resolvedDefaultFeatures = [ "tracing" ];
+      };
+      "backtrace" = rec {
+        crateName = "backtrace";
+        version = "0.3.69";
+        edition = "2018";
+        sha256 = "0dsq23dhw4pfndkx2nsa1ml2g31idm7ss7ljxp8d57avygivg290";
+        authors = [
+          "The Rust Project Developers"
+        ];
+        dependencies = [
+          {
+            name = "addr2line";
+            packageId = "addr2line";
+            usesDefaultFeatures = false;
+            target = { target, features }: (!((target."windows" or false) && ("msvc" == target."env" or null) && (!("uwp" == target."vendor" or null))));
+          }
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            usesDefaultFeatures = false;
+            target = { target, features }: (!((target."windows" or false) && ("msvc" == target."env" or null) && (!("uwp" == target."vendor" or null))));
+          }
+          {
+            name = "miniz_oxide";
+            packageId = "miniz_oxide";
+            usesDefaultFeatures = false;
+            target = { target, features }: (!((target."windows" or false) && ("msvc" == target."env" or null) && (!("uwp" == target."vendor" or null))));
+          }
+          {
+            name = "object";
+            packageId = "object";
+            usesDefaultFeatures = false;
+            target = { target, features }: (!((target."windows" or false) && ("msvc" == target."env" or null) && (!("uwp" == target."vendor" or null))));
+            features = [ "read_core" "elf" "macho" "pe" "unaligned" "archive" ];
+          }
+          {
+            name = "rustc-demangle";
+            packageId = "rustc-demangle";
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "cc";
+            packageId = "cc";
+          }
+        ];
+        features = {
+          "cpp_demangle" = [ "dep:cpp_demangle" ];
+          "default" = [ "std" ];
+          "rustc-serialize" = [ "dep:rustc-serialize" ];
+          "serde" = [ "dep:serde" ];
+          "serialize-rustc" = [ "rustc-serialize" ];
+          "serialize-serde" = [ "serde" ];
+          "verify-winapi" = [ "winapi/dbghelp" "winapi/handleapi" "winapi/libloaderapi" "winapi/memoryapi" "winapi/minwindef" "winapi/processthreadsapi" "winapi/synchapi" "winapi/tlhelp32" "winapi/winbase" "winapi/winnt" ];
+          "winapi" = [ "dep:winapi" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "base64" = rec {
+        crateName = "base64";
+        version = "0.21.7";
+        edition = "2018";
+        sha256 = "0rw52yvsk75kar9wgqfwgb414kvil1gn7mqkrhn9zf1537mpsacx";
+        authors = [
+          "Alice Maz <alice@alicemaz.com>"
+          "Marshall Pierce <marshall@mpierce.org>"
+        ];
+        features = {
+          "default" = [ "std" ];
+          "std" = [ "alloc" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "std" ];
+      };
+      "base64ct" = rec {
+        crateName = "base64ct";
+        version = "1.6.0";
+        edition = "2021";
+        sha256 = "0nvdba4jb8aikv60az40x2w1y96sjdq8z3yp09rwzmkhiwv1lg4c";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        features = {
+          "std" = [ "alloc" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" ];
+      };
+      "bigtable_rs" = rec {
+        crateName = "bigtable_rs";
+        version = "0.2.9";
+        edition = "2021";
+        workspace_member = null;
+        src = pkgs.fetchgit {
+          url = "https://github.com/flokli/bigtable_rs";
+          rev = "0af404741dfc40eb9fa99cf4d4140a09c5c20df7";
+          sha256 = "1njjam1lx2xlnm7a41lga8601vmjgqz0fvc77x24gd04pc7avxll";
+        };
+        authors = [
+          "Fuyang Liu <liufuyang@users.noreply.github.com>"
+        ];
+        dependencies = [
+          {
+            name = "gcp_auth";
+            packageId = "gcp_auth";
+          }
+          {
+            name = "http";
+            packageId = "http 0.2.11";
+          }
+          {
+            name = "log";
+            packageId = "log";
+          }
+          {
+            name = "prost";
+            packageId = "prost 0.12.3";
+          }
+          {
+            name = "prost-types";
+            packageId = "prost-types";
+          }
+          {
+            name = "prost-wkt";
+            packageId = "prost-wkt";
+          }
+          {
+            name = "prost-wkt-types";
+            packageId = "prost-wkt-types";
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+            features = [ "derive" ];
+          }
+          {
+            name = "serde_with";
+            packageId = "serde_with";
+            features = [ "base64" ];
+          }
+          {
+            name = "thiserror";
+            packageId = "thiserror";
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "rt-multi-thread" ];
+          }
+          {
+            name = "tonic";
+            packageId = "tonic 0.11.0";
+            features = [ "tls" "transport" ];
+          }
+          {
+            name = "tower";
+            packageId = "tower";
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "prost-build";
+            packageId = "prost-build";
+          }
+          {
+            name = "prost-wkt-build";
+            packageId = "prost-wkt-build";
+          }
+          {
+            name = "tonic-build";
+            packageId = "tonic-build";
+            features = [ "cleanup-markdown" ];
+          }
+        ];
+
+      };
+      "bit-set" = rec {
+        crateName = "bit-set";
+        version = "0.5.3";
+        edition = "2015";
+        sha256 = "1wcm9vxi00ma4rcxkl3pzzjli6ihrpn9cfdi0c5b4cvga2mxs007";
+        authors = [
+          "Alexis Beingessner <a.beingessner@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "bit-vec";
+            packageId = "bit-vec";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "std" = [ "bit-vec/std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "bit-vec" = rec {
+        crateName = "bit-vec";
+        version = "0.6.3";
+        edition = "2015";
+        sha256 = "1ywqjnv60cdh1slhz67psnp422md6jdliji6alq0gmly2xm9p7rl";
+        authors = [
+          "Alexis Beingessner <a.beingessner@gmail.com>"
+        ];
+        features = {
+          "default" = [ "std" ];
+          "serde" = [ "dep:serde" ];
+          "serde_no_std" = [ "serde/alloc" ];
+          "serde_std" = [ "std" "serde/std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "bitflags 1.3.2" = rec {
+        crateName = "bitflags";
+        version = "1.3.2";
+        edition = "2018";
+        sha256 = "12ki6w8gn1ldq7yz9y680llwk5gmrhrzszaa17g1sbrw2r2qvwxy";
+        authors = [
+          "The Rust Project Developers"
+        ];
+        features = {
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "core" = [ "dep:core" ];
+          "rustc-dep-of-std" = [ "core" "compiler_builtins" ];
+        };
+        resolvedDefaultFeatures = [ "default" ];
+      };
+      "bitflags 2.4.2" = rec {
+        crateName = "bitflags";
+        version = "2.4.2";
+        edition = "2021";
+        sha256 = "1pqd142hyqlzr7p9djxq2ff0jx07a2sb2xp9lhw69cbf80s0jmzd";
+        authors = [
+          "The Rust Project Developers"
+        ];
+        features = {
+          "arbitrary" = [ "dep:arbitrary" ];
+          "bytemuck" = [ "dep:bytemuck" ];
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "core" = [ "dep:core" ];
+          "rustc-dep-of-std" = [ "core" "compiler_builtins" ];
+          "serde" = [ "dep:serde" ];
+        };
+        resolvedDefaultFeatures = [ "std" ];
+      };
+      "bitmaps" = rec {
+        crateName = "bitmaps";
+        version = "3.2.0";
+        edition = "2021";
+        sha256 = "00ql08pm4l9hizkldyy54v0pk96g7zg8x6i72c2vkcq0iawl4dkh";
+        authors = [
+          "Bodil Stokke <bodil@bodil.org>"
+        ];
+        features = {
+          "default" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "blake3" = rec {
+        crateName = "blake3";
+        version = "1.5.0";
+        edition = "2021";
+        sha256 = "11ysh12zcqq6xkjxh5cbrmnwzalprm3z552i5ff7wm5za9hz0c82";
+        authors = [
+          "Jack O'Connor <oconnor663@gmail.com>"
+          "Samuel Neves"
+        ];
+        dependencies = [
+          {
+            name = "arrayref";
+            packageId = "arrayref";
+          }
+          {
+            name = "arrayvec";
+            packageId = "arrayvec";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "constant_time_eq";
+            packageId = "constant_time_eq";
+          }
+          {
+            name = "digest";
+            packageId = "digest";
+            optional = true;
+            features = [ "mac" ];
+          }
+          {
+            name = "rayon";
+            packageId = "rayon";
+            optional = true;
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "cc";
+            packageId = "cc";
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "digest" = [ "dep:digest" ];
+          "mmap" = [ "std" "dep:memmap2" ];
+          "rayon" = [ "dep:rayon" "std" ];
+          "serde" = [ "dep:serde" ];
+          "traits-preview" = [ "digest" ];
+          "zeroize" = [ "dep:zeroize" "arrayvec/zeroize" ];
+        };
+        resolvedDefaultFeatures = [ "default" "digest" "rayon" "std" "traits-preview" ];
+      };
+      "block-buffer" = rec {
+        crateName = "block-buffer";
+        version = "0.10.4";
+        edition = "2018";
+        sha256 = "0w9sa2ypmrsqqvc20nhwr75wbb5cjr4kkyhpjm1z1lv2kdicfy1h";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        dependencies = [
+          {
+            name = "generic-array";
+            packageId = "generic-array";
+          }
+        ];
+
+      };
+      "blocking" = rec {
+        crateName = "blocking";
+        version = "1.5.1";
+        edition = "2018";
+        sha256 = "064i3d6b8ln34fgdw49nmx9m36bwi3r3nv8c9xhcrpf4ilz92dva";
+        authors = [
+          "Stjepan Glavina <stjepang@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "async-channel";
+            packageId = "async-channel";
+          }
+          {
+            name = "async-lock";
+            packageId = "async-lock 3.3.0";
+            target = { target, features }: (!(builtins.elem "wasm" target."family"));
+          }
+          {
+            name = "async-task";
+            packageId = "async-task";
+          }
+          {
+            name = "fastrand";
+            packageId = "fastrand";
+          }
+          {
+            name = "futures-io";
+            packageId = "futures-io";
+            usesDefaultFeatures = false;
+            features = [ "std" ];
+          }
+          {
+            name = "futures-lite";
+            packageId = "futures-lite";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "piper";
+            packageId = "piper";
+          }
+          {
+            name = "tracing";
+            packageId = "tracing";
+            usesDefaultFeatures = false;
+          }
+        ];
+        devDependencies = [
+          {
+            name = "futures-lite";
+            packageId = "futures-lite";
+          }
+        ];
+
+      };
+      "bstr" = rec {
+        crateName = "bstr";
+        version = "1.9.0";
+        edition = "2021";
+        sha256 = "1p6hzf3wqwwynv6w4pn17jg21amfafph9kb5sfvf1idlli8h13y4";
+        authors = [
+          "Andrew Gallant <jamslam@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "memchr";
+            packageId = "memchr";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "regex-automata";
+            packageId = "regex-automata 0.4.3";
+            optional = true;
+            usesDefaultFeatures = false;
+            features = [ "dfa-search" ];
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "alloc" = [ "memchr/alloc" "serde?/alloc" ];
+          "default" = [ "std" "unicode" ];
+          "serde" = [ "dep:serde" ];
+          "std" = [ "alloc" "memchr/std" "serde?/std" ];
+          "unicode" = [ "dep:regex-automata" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "serde" "std" "unicode" ];
+      };
+      "bumpalo" = rec {
+        crateName = "bumpalo";
+        version = "3.14.0";
+        edition = "2021";
+        sha256 = "1v4arnv9kwk54v5d0qqpv4vyw2sgr660nk0w3apzixi1cm3yfc3z";
+        authors = [
+          "Nick Fitzgerald <fitzgen@gmail.com>"
+        ];
+        features = {
+          "allocator-api2" = [ "dep:allocator-api2" ];
+        };
+        resolvedDefaultFeatures = [ "default" ];
+      };
+      "byteorder" = rec {
+        crateName = "byteorder";
+        version = "1.5.0";
+        edition = "2021";
+        sha256 = "0jzncxyf404mwqdbspihyzpkndfgda450l0893pz5xj685cg5l0z";
+        authors = [
+          "Andrew Gallant <jamslam@gmail.com>"
+        ];
+        features = {
+          "default" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "bytes" = rec {
+        crateName = "bytes";
+        version = "1.5.0";
+        edition = "2018";
+        sha256 = "08w2i8ac912l8vlvkv3q51cd4gr09pwlg3sjsjffcizlrb0i5gd2";
+        authors = [
+          "Carl Lerche <me@carllerche.com>"
+          "Sean McArthur <sean@seanmonstar.com>"
+        ];
+        features = {
+          "default" = [ "std" ];
+          "serde" = [ "dep:serde" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "bzip2" = rec {
+        crateName = "bzip2";
+        version = "0.4.4";
+        edition = "2015";
+        sha256 = "1y27wgqkx3k2jmh4k26vra2kqjq1qc1asww8hac3cv1zxyk1dcdx";
+        authors = [
+          "Alex Crichton <alex@alexcrichton.com>"
+        ];
+        dependencies = [
+          {
+            name = "bzip2-sys";
+            packageId = "bzip2-sys";
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+          }
+        ];
+        features = {
+          "futures" = [ "dep:futures" ];
+          "static" = [ "bzip2-sys/static" ];
+          "tokio" = [ "tokio-io" "futures" ];
+          "tokio-io" = [ "dep:tokio-io" ];
+        };
+      };
+      "bzip2-sys" = rec {
+        crateName = "bzip2-sys";
+        version = "0.1.11+1.0.8";
+        edition = "2015";
+        links = "bzip2";
+        sha256 = "1p2crnv8d8gpz5c2vlvzl0j55i3yqg5bi0kwsl1531x77xgraskk";
+        libName = "bzip2_sys";
+        libPath = "lib.rs";
+        authors = [
+          "Alex Crichton <alex@alexcrichton.com>"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "cc";
+            packageId = "cc";
+          }
+          {
+            name = "pkg-config";
+            packageId = "pkg-config";
+          }
+        ];
+        features = { };
+      };
+      "caps" = rec {
+        crateName = "caps";
+        version = "0.5.5";
+        edition = "2018";
+        sha256 = "02vk0w48rncgvfmj2mz2kpzvdgc14z225451w7lvvkwvaansl2qr";
+        authors = [
+          "Luca Bruno <lucab@lucabruno.net>"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+          }
+          {
+            name = "thiserror";
+            packageId = "thiserror";
+          }
+        ];
+        features = {
+          "serde" = [ "dep:serde" ];
+          "serde_support" = [ "serde" ];
+        };
+      };
+      "cast" = rec {
+        crateName = "cast";
+        version = "0.3.0";
+        edition = "2018";
+        sha256 = "1dbyngbyz2qkk0jn2sxil8vrz3rnpcj142y184p9l4nbl9radcip";
+        authors = [
+          "Jorge Aparicio <jorge@japaric.io>"
+        ];
+        features = { };
+      };
+      "cc" = rec {
+        crateName = "cc";
+        version = "1.0.83";
+        edition = "2018";
+        crateBin = [ ];
+        sha256 = "1l643zidlb5iy1dskc5ggqs4wqa29a02f44piczqc8zcnsq4y5zi";
+        authors = [
+          "Alex Crichton <alex@alexcrichton.com>"
+        ];
+        dependencies = [
+          {
+            name = "jobserver";
+            packageId = "jobserver";
+            optional = true;
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            usesDefaultFeatures = false;
+            target = { target, features }: (target."unix" or false);
+          }
+        ];
+        features = {
+          "jobserver" = [ "dep:jobserver" ];
+          "parallel" = [ "jobserver" ];
+        };
+        resolvedDefaultFeatures = [ "jobserver" "parallel" ];
+      };
+      "cfg-if" = rec {
+        crateName = "cfg-if";
+        version = "1.0.0";
+        edition = "2018";
+        sha256 = "1za0vb97n4brpzpv8lsbnzmq5r8f2b0cpqqr0sy8h5bn751xxwds";
+        authors = [
+          "Alex Crichton <alex@alexcrichton.com>"
+        ];
+        features = {
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "core" = [ "dep:core" ];
+          "rustc-dep-of-std" = [ "core" "compiler_builtins" ];
+        };
+      };
+      "chrono" = rec {
+        crateName = "chrono";
+        version = "0.4.34";
+        edition = "2021";
+        sha256 = "12zk0ja924f55va2fs0qj34xaygq46fy92blmc7qkmcj9dj1bh2v";
+        dependencies = [
+          {
+            name = "android-tzdata";
+            packageId = "android-tzdata";
+            optional = true;
+            target = { target, features }: ("android" == target."os" or null);
+          }
+          {
+            name = "iana-time-zone";
+            packageId = "iana-time-zone";
+            optional = true;
+            target = { target, features }: (target."unix" or false);
+            features = [ "fallback" ];
+          }
+          {
+            name = "js-sys";
+            packageId = "js-sys";
+            optional = true;
+            target = { target, features }: (("wasm32" == target."arch" or null) && (!(("emscripten" == target."os" or null) || ("wasi" == target."os" or null))));
+          }
+          {
+            name = "num-traits";
+            packageId = "num-traits";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "wasm-bindgen";
+            packageId = "wasm-bindgen";
+            optional = true;
+            target = { target, features }: (("wasm32" == target."arch" or null) && (!(("emscripten" == target."os" or null) || ("wasi" == target."os" or null))));
+          }
+          {
+            name = "windows-targets";
+            packageId = "windows-targets 0.52.0";
+            optional = true;
+            target = { target, features }: (target."windows" or false);
+          }
+        ];
+        features = {
+          "android-tzdata" = [ "dep:android-tzdata" ];
+          "arbitrary" = [ "dep:arbitrary" ];
+          "clock" = [ "winapi" "iana-time-zone" "android-tzdata" "now" ];
+          "default" = [ "clock" "std" "oldtime" "wasmbind" ];
+          "iana-time-zone" = [ "dep:iana-time-zone" ];
+          "js-sys" = [ "dep:js-sys" ];
+          "now" = [ "std" ];
+          "pure-rust-locales" = [ "dep:pure-rust-locales" ];
+          "rkyv" = [ "dep:rkyv" "rkyv/size_32" ];
+          "rkyv-16" = [ "dep:rkyv" "rkyv?/size_16" ];
+          "rkyv-32" = [ "dep:rkyv" "rkyv?/size_32" ];
+          "rkyv-64" = [ "dep:rkyv" "rkyv?/size_64" ];
+          "rkyv-validation" = [ "rkyv?/validation" ];
+          "rustc-serialize" = [ "dep:rustc-serialize" ];
+          "serde" = [ "dep:serde" ];
+          "std" = [ "alloc" ];
+          "unstable-locales" = [ "pure-rust-locales" ];
+          "wasm-bindgen" = [ "dep:wasm-bindgen" ];
+          "wasmbind" = [ "wasm-bindgen" "js-sys" ];
+          "winapi" = [ "windows-targets" ];
+          "windows-targets" = [ "dep:windows-targets" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "android-tzdata" "clock" "default" "iana-time-zone" "js-sys" "now" "oldtime" "serde" "std" "wasm-bindgen" "wasmbind" "winapi" "windows-targets" ];
+      };
+      "ciborium" = rec {
+        crateName = "ciborium";
+        version = "0.2.1";
+        edition = "2021";
+        sha256 = "09p9gr3jxys51v0fzwsmxym2p7pcz9mhng2xib74lnlfqzv93zgg";
+        authors = [
+          "Nathaniel McCallum <npmccallum@profian.com>"
+        ];
+        dependencies = [
+          {
+            name = "ciborium-io";
+            packageId = "ciborium-io";
+            features = [ "alloc" ];
+          }
+          {
+            name = "ciborium-ll";
+            packageId = "ciborium-ll";
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+            usesDefaultFeatures = false;
+            features = [ "alloc" "derive" ];
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "std" = [ "ciborium-io/std" "serde/std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "ciborium-io" = rec {
+        crateName = "ciborium-io";
+        version = "0.2.1";
+        edition = "2021";
+        sha256 = "0mi6ci27lpz3azksxrvgzl9jc4a3dfr20pjx7y2nkcrjalbikyfd";
+        authors = [
+          "Nathaniel McCallum <npmccallum@profian.com>"
+        ];
+        features = {
+          "std" = [ "alloc" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "std" ];
+      };
+      "ciborium-ll" = rec {
+        crateName = "ciborium-ll";
+        version = "0.2.1";
+        edition = "2021";
+        sha256 = "0az2vabamfk75m74ylgf6nzqgqgma5yf25bc1ripfg09ri7a5yny";
+        authors = [
+          "Nathaniel McCallum <npmccallum@profian.com>"
+        ];
+        dependencies = [
+          {
+            name = "ciborium-io";
+            packageId = "ciborium-io";
+          }
+          {
+            name = "half";
+            packageId = "half";
+          }
+        ];
+        features = {
+          "std" = [ "alloc" ];
+        };
+      };
+      "clap" = rec {
+        crateName = "clap";
+        version = "4.4.18";
+        edition = "2021";
+        crateBin = [ ];
+        sha256 = "0p46h346y8nval6gwzh27if3icbi9dwl95fg5ir36ihrqip8smqy";
+        dependencies = [
+          {
+            name = "clap_builder";
+            packageId = "clap_builder";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "clap_derive";
+            packageId = "clap_derive";
+            optional = true;
+          }
+        ];
+        features = {
+          "cargo" = [ "clap_builder/cargo" ];
+          "color" = [ "clap_builder/color" ];
+          "debug" = [ "clap_builder/debug" "clap_derive?/debug" ];
+          "default" = [ "std" "color" "help" "usage" "error-context" "suggestions" ];
+          "deprecated" = [ "clap_builder/deprecated" "clap_derive?/deprecated" ];
+          "derive" = [ "dep:clap_derive" ];
+          "env" = [ "clap_builder/env" ];
+          "error-context" = [ "clap_builder/error-context" ];
+          "help" = [ "clap_builder/help" ];
+          "std" = [ "clap_builder/std" ];
+          "string" = [ "clap_builder/string" ];
+          "suggestions" = [ "clap_builder/suggestions" ];
+          "unicode" = [ "clap_builder/unicode" ];
+          "unstable-doc" = [ "clap_builder/unstable-doc" "derive" ];
+          "unstable-styles" = [ "clap_builder/unstable-styles" ];
+          "unstable-v5" = [ "clap_builder/unstable-v5" "clap_derive?/unstable-v5" "deprecated" ];
+          "usage" = [ "clap_builder/usage" ];
+          "wrap_help" = [ "clap_builder/wrap_help" ];
+        };
+        resolvedDefaultFeatures = [ "color" "default" "derive" "env" "error-context" "help" "std" "suggestions" "usage" ];
+      };
+      "clap_builder" = rec {
+        crateName = "clap_builder";
+        version = "4.4.18";
+        edition = "2021";
+        sha256 = "1iyif47075caa4x1p3ygk18b07lb4xl4k48w4c061i2hxi0dzx2d";
+        dependencies = [
+          {
+            name = "anstream";
+            packageId = "anstream";
+            optional = true;
+          }
+          {
+            name = "anstyle";
+            packageId = "anstyle";
+          }
+          {
+            name = "clap_lex";
+            packageId = "clap_lex";
+          }
+          {
+            name = "strsim";
+            packageId = "strsim";
+            optional = true;
+          }
+        ];
+        features = {
+          "color" = [ "dep:anstream" ];
+          "debug" = [ "dep:backtrace" ];
+          "default" = [ "std" "color" "help" "usage" "error-context" "suggestions" ];
+          "std" = [ "anstyle/std" ];
+          "suggestions" = [ "dep:strsim" "error-context" ];
+          "unicode" = [ "dep:unicode-width" "dep:unicase" ];
+          "unstable-doc" = [ "cargo" "wrap_help" "env" "unicode" "string" ];
+          "unstable-styles" = [ "color" ];
+          "unstable-v5" = [ "deprecated" ];
+          "wrap_help" = [ "help" "dep:terminal_size" ];
+        };
+        resolvedDefaultFeatures = [ "color" "env" "error-context" "help" "std" "suggestions" "usage" ];
+      };
+      "clap_derive" = rec {
+        crateName = "clap_derive";
+        version = "4.4.7";
+        edition = "2021";
+        sha256 = "0hk4hcxl56qwqsf4hmf7c0gr19r9fbxk0ah2bgkr36pmmaph966g";
+        procMacro = true;
+        dependencies = [
+          {
+            name = "heck";
+            packageId = "heck";
+          }
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn 2.0.48";
+            features = [ "full" ];
+          }
+        ];
+        features = {
+          "raw-deprecated" = [ "deprecated" ];
+          "unstable-v5" = [ "deprecated" ];
+        };
+        resolvedDefaultFeatures = [ "default" ];
+      };
+      "clap_lex" = rec {
+        crateName = "clap_lex";
+        version = "0.6.0";
+        edition = "2021";
+        sha256 = "1l8bragdvim7mva9flvd159dskn2bdkpl0jqrr41wnjfn8pcfbvh";
+
+      };
+      "clipboard-win" = rec {
+        crateName = "clipboard-win";
+        version = "4.5.0";
+        edition = "2018";
+        sha256 = "0qh3rypkf1lazniq4nr04hxsck0d55rigb5sjvpvgnap4dyc54bi";
+        authors = [
+          "Douman <douman@gmx.se>"
+        ];
+        dependencies = [
+          {
+            name = "error-code";
+            packageId = "error-code";
+            target = { target, features }: (target."windows" or false);
+          }
+          {
+            name = "str-buf";
+            packageId = "str-buf";
+            target = { target, features }: (target."windows" or false);
+          }
+          {
+            name = "winapi";
+            packageId = "winapi";
+            usesDefaultFeatures = false;
+            target = { target, features }: (target."windows" or false);
+            features = [ "basetsd" "shellapi" "winbase" "winuser" "winerror" "stringapiset" "errhandlingapi" "synchapi" ];
+          }
+        ];
+        features = {
+          "std" = [ "error-code/std" ];
+        };
+      };
+      "codemap" = rec {
+        crateName = "codemap";
+        version = "0.1.3";
+        edition = "2015";
+        sha256 = "091azkslwkcijj3lp9ymb084y9a0wm4fkil7m613ja68r2snkrxr";
+        authors = [
+          "Kevin Mehall <km@kevinmehall.net>"
+        ];
+
+      };
+      "codemap-diagnostic" = rec {
+        crateName = "codemap-diagnostic";
+        version = "0.1.2";
+        edition = "2015";
+        sha256 = "08l1b84bn8r8a72rbvyi2v8a5i0j0kk0a5gr7fb6lmjvw05pf86c";
+        authors = [
+          "Kevin Mehall <km@kevinmehall.net>"
+          "The Rust Project Developers"
+        ];
+        dependencies = [
+          {
+            name = "codemap";
+            packageId = "codemap";
+          }
+          {
+            name = "termcolor";
+            packageId = "termcolor";
+          }
+        ];
+
+      };
+      "colorchoice" = rec {
+        crateName = "colorchoice";
+        version = "1.0.0";
+        edition = "2021";
+        sha256 = "1ix7w85kwvyybwi2jdkl3yva2r2bvdcc3ka2grjfzfgrapqimgxc";
+
+      };
+      "concurrent-queue" = rec {
+        crateName = "concurrent-queue";
+        version = "2.4.0";
+        edition = "2018";
+        sha256 = "0qvk23ynj311adb4z7v89wk3bs65blps4n24q8rgl23vjk6lhq6i";
+        authors = [
+          "Stjepan Glavina <stjepang@gmail.com>"
+          "Taiki Endo <te316e89@gmail.com>"
+          "John Nunley <jtnunley01@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "crossbeam-utils";
+            packageId = "crossbeam-utils";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "loom" = [ "dep:loom" ];
+          "portable-atomic" = [ "dep:portable-atomic" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "const-oid" = rec {
+        crateName = "const-oid";
+        version = "0.9.6";
+        edition = "2021";
+        sha256 = "1y0jnqaq7p2wvspnx7qj76m7hjcqpz73qzvr9l2p9n2s51vr6if2";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        features = {
+          "arbitrary" = [ "dep:arbitrary" ];
+        };
+      };
+      "constant_time_eq" = rec {
+        crateName = "constant_time_eq";
+        version = "0.3.0";
+        edition = "2021";
+        sha256 = "1hl0y8frzlhpr58rh8rlg4bm53ax09ikj2i5fk7gpyphvhq4s57p";
+        authors = [
+          "Cesar Eduardo Barros <cesarb@cesarb.eti.br>"
+        ];
+        features = { };
+      };
+      "core-foundation" = rec {
+        crateName = "core-foundation";
+        version = "0.9.4";
+        edition = "2018";
+        sha256 = "13zvbbj07yk3b61b8fhwfzhy35535a583irf23vlcg59j7h9bqci";
+        authors = [
+          "The Servo Project Developers"
+        ];
+        dependencies = [
+          {
+            name = "core-foundation-sys";
+            packageId = "core-foundation-sys";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+          }
+        ];
+        features = {
+          "chrono" = [ "dep:chrono" ];
+          "default" = [ "link" ];
+          "link" = [ "core-foundation-sys/link" ];
+          "mac_os_10_7_support" = [ "core-foundation-sys/mac_os_10_7_support" ];
+          "mac_os_10_8_features" = [ "core-foundation-sys/mac_os_10_8_features" ];
+          "uuid" = [ "dep:uuid" ];
+          "with-chrono" = [ "chrono" ];
+          "with-uuid" = [ "uuid" ];
+        };
+        resolvedDefaultFeatures = [ "default" "link" ];
+      };
+      "core-foundation-sys" = rec {
+        crateName = "core-foundation-sys";
+        version = "0.8.6";
+        edition = "2018";
+        sha256 = "13w6sdf06r0hn7bx2b45zxsg1mm2phz34jikm6xc5qrbr6djpsh6";
+        authors = [
+          "The Servo Project Developers"
+        ];
+        features = {
+          "default" = [ "link" ];
+        };
+        resolvedDefaultFeatures = [ "default" "link" ];
+      };
+      "count-write" = rec {
+        crateName = "count-write";
+        version = "0.1.0";
+        edition = "2018";
+        sha256 = "11bswmgr81s3jagdci1pr6qh9vnz9zsbbf2dqpi260daa2mhgmff";
+        authors = [
+          "SOFe <sofe2038@gmail.com>"
+        ];
+        features = {
+          "futures" = [ "futures-io-preview" ];
+          "futures-io-preview" = [ "dep:futures-io-preview" ];
+          "tokio" = [ "tokio-io" ];
+          "tokio-io" = [ "dep:tokio-io" ];
+        };
+      };
+      "countme" = rec {
+        crateName = "countme";
+        version = "3.0.1";
+        edition = "2018";
+        sha256 = "0dn62hhvgmwyxslh14r4nlbvz8h50cp5mnn1qhqsw63vs7yva13p";
+        authors = [
+          "Aleksey Kladov <aleksey.kladov@gmail.com>"
+        ];
+        features = {
+          "dashmap" = [ "dep:dashmap" ];
+          "enable" = [ "dashmap" "once_cell" "rustc-hash" ];
+          "once_cell" = [ "dep:once_cell" ];
+          "print_at_exit" = [ "enable" ];
+          "rustc-hash" = [ "dep:rustc-hash" ];
+        };
+      };
+      "cpufeatures" = rec {
+        crateName = "cpufeatures";
+        version = "0.2.12";
+        edition = "2018";
+        sha256 = "012m7rrak4girqlii3jnqwrr73gv1i980q4wra5yyyhvzwk5xzjk";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (pkgs.rust.lib.toRustTarget stdenv.hostPlatform == "aarch64-linux-android");
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (("aarch64" == target."arch" or null) && ("linux" == target."os" or null));
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (("aarch64" == target."arch" or null) && ("apple" == target."vendor" or null));
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (("loongarch64" == target."arch" or null) && ("linux" == target."os" or null));
+          }
+        ];
+
+      };
+      "crc32fast" = rec {
+        crateName = "crc32fast";
+        version = "1.3.2";
+        edition = "2015";
+        sha256 = "03c8f29yx293yf43xar946xbls1g60c207m9drf8ilqhr25vsh5m";
+        authors = [
+          "Sam Rijs <srijs@airpost.net>"
+          "Alex Crichton <alex@alexcrichton.com>"
+        ];
+        dependencies = [
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "criterion" = rec {
+        crateName = "criterion";
+        version = "0.5.1";
+        edition = "2018";
+        sha256 = "0bv9ipygam3z8kk6k771gh9zi0j0lb9ir0xi1pc075ljg80jvcgj";
+        authors = [
+          "Jorge Aparicio <japaricious@gmail.com>"
+          "Brook Heisler <brookheisler@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "anes";
+            packageId = "anes";
+          }
+          {
+            name = "cast";
+            packageId = "cast";
+          }
+          {
+            name = "ciborium";
+            packageId = "ciborium";
+          }
+          {
+            name = "clap";
+            packageId = "clap";
+            usesDefaultFeatures = false;
+            features = [ "std" ];
+          }
+          {
+            name = "criterion-plot";
+            packageId = "criterion-plot";
+          }
+          {
+            name = "is-terminal";
+            packageId = "is-terminal";
+          }
+          {
+            name = "itertools";
+            packageId = "itertools 0.10.5";
+          }
+          {
+            name = "num-traits";
+            packageId = "num-traits";
+            usesDefaultFeatures = false;
+            features = [ "std" ];
+          }
+          {
+            name = "once_cell";
+            packageId = "once_cell";
+          }
+          {
+            name = "oorandom";
+            packageId = "oorandom";
+          }
+          {
+            name = "plotters";
+            packageId = "plotters";
+            optional = true;
+            usesDefaultFeatures = false;
+            features = [ "svg_backend" "area_series" "line_series" ];
+          }
+          {
+            name = "rayon";
+            packageId = "rayon";
+            optional = true;
+          }
+          {
+            name = "regex";
+            packageId = "regex";
+            usesDefaultFeatures = false;
+            features = [ "std" ];
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+          }
+          {
+            name = "serde_derive";
+            packageId = "serde_derive";
+          }
+          {
+            name = "serde_json";
+            packageId = "serde_json";
+          }
+          {
+            name = "tinytemplate";
+            packageId = "tinytemplate";
+          }
+          {
+            name = "walkdir";
+            packageId = "walkdir";
+          }
+        ];
+        features = {
+          "async" = [ "futures" ];
+          "async-std" = [ "dep:async-std" ];
+          "async_futures" = [ "futures/executor" "async" ];
+          "async_smol" = [ "smol" "async" ];
+          "async_std" = [ "async-std" "async" ];
+          "async_tokio" = [ "tokio" "async" ];
+          "csv" = [ "dep:csv" ];
+          "csv_output" = [ "csv" ];
+          "default" = [ "rayon" "plotters" "cargo_bench_support" ];
+          "futures" = [ "dep:futures" ];
+          "plotters" = [ "dep:plotters" ];
+          "rayon" = [ "dep:rayon" ];
+          "smol" = [ "dep:smol" ];
+          "stable" = [ "csv_output" "html_reports" "async_futures" "async_smol" "async_tokio" "async_std" ];
+          "tokio" = [ "dep:tokio" ];
+        };
+        resolvedDefaultFeatures = [ "cargo_bench_support" "default" "html_reports" "plotters" "rayon" ];
+      };
+      "criterion-plot" = rec {
+        crateName = "criterion-plot";
+        version = "0.5.0";
+        edition = "2018";
+        sha256 = "1c866xkjqqhzg4cjvg01f8w6xc1j3j7s58rdksl52skq89iq4l3b";
+        authors = [
+          "Jorge Aparicio <japaricious@gmail.com>"
+          "Brook Heisler <brookheisler@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "cast";
+            packageId = "cast";
+          }
+          {
+            name = "itertools";
+            packageId = "itertools 0.10.5";
+          }
+        ];
+
+      };
+      "crossbeam-channel" = rec {
+        crateName = "crossbeam-channel";
+        version = "0.5.11";
+        edition = "2021";
+        sha256 = "16v48qdflpw3hgdik70bhsj7hympna79q7ci47rw0mlgnxsw2v8p";
+        dependencies = [
+          {
+            name = "crossbeam-utils";
+            packageId = "crossbeam-utils";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "std" = [ "crossbeam-utils/std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "crossbeam-deque" = rec {
+        crateName = "crossbeam-deque";
+        version = "0.8.5";
+        edition = "2021";
+        sha256 = "03bp38ljx4wj6vvy4fbhx41q8f585zyqix6pncz1mkz93z08qgv1";
+        dependencies = [
+          {
+            name = "crossbeam-epoch";
+            packageId = "crossbeam-epoch";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "crossbeam-utils";
+            packageId = "crossbeam-utils";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "std" = [ "crossbeam-epoch/std" "crossbeam-utils/std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "crossbeam-epoch" = rec {
+        crateName = "crossbeam-epoch";
+        version = "0.9.18";
+        edition = "2021";
+        sha256 = "03j2np8llwf376m3fxqx859mgp9f83hj1w34153c7a9c7i5ar0jv";
+        dependencies = [
+          {
+            name = "crossbeam-utils";
+            packageId = "crossbeam-utils";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "loom" = [ "loom-crate" "crossbeam-utils/loom" ];
+          "loom-crate" = [ "dep:loom-crate" ];
+          "nightly" = [ "crossbeam-utils/nightly" ];
+          "std" = [ "alloc" "crossbeam-utils/std" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "std" ];
+      };
+      "crossbeam-utils" = rec {
+        crateName = "crossbeam-utils";
+        version = "0.8.19";
+        edition = "2021";
+        sha256 = "0iakrb1b8fjqrag7wphl94d10irhbh2fw1g444xslsywqyn3p3i4";
+        features = {
+          "default" = [ "std" ];
+          "loom" = [ "dep:loom" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "crypto-common" = rec {
+        crateName = "crypto-common";
+        version = "0.1.6";
+        edition = "2018";
+        sha256 = "1cvby95a6xg7kxdz5ln3rl9xh66nz66w46mm3g56ri1z5x815yqv";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        dependencies = [
+          {
+            name = "generic-array";
+            packageId = "generic-array";
+            features = [ "more_lengths" ];
+          }
+          {
+            name = "typenum";
+            packageId = "typenum";
+          }
+        ];
+        features = {
+          "getrandom" = [ "rand_core/getrandom" ];
+          "rand_core" = [ "dep:rand_core" ];
+        };
+        resolvedDefaultFeatures = [ "std" ];
+      };
+      "curve25519-dalek" = rec {
+        crateName = "curve25519-dalek";
+        version = "4.1.1";
+        edition = "2021";
+        sha256 = "0p7ns5917k6369gajrsbfj24llc5zfm635yh3abla7sb5rm8r6z8";
+        authors = [
+          "Isis Lovecruft <isis@patternsinthevoid.net>"
+          "Henry de Valence <hdevalence@hdevalence.ca>"
+        ];
+        dependencies = [
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "cpufeatures";
+            packageId = "cpufeatures";
+            target = { target, features }: ("x86_64" == target."arch" or null);
+          }
+          {
+            name = "curve25519-dalek-derive";
+            packageId = "curve25519-dalek-derive";
+            target = { target, features }: ((!("fiat" == target."curve25519_dalek_backend" or null)) && (!("serial" == target."curve25519_dalek_backend" or null)) && ("x86_64" == target."arch" or null));
+          }
+          {
+            name = "digest";
+            packageId = "digest";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "fiat-crypto";
+            packageId = "fiat-crypto";
+            usesDefaultFeatures = false;
+            target = { target, features }: ("fiat" == target."curve25519_dalek_backend" or null);
+          }
+          {
+            name = "subtle";
+            packageId = "subtle";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "zeroize";
+            packageId = "zeroize";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "platforms";
+            packageId = "platforms";
+          }
+          {
+            name = "rustc_version";
+            packageId = "rustc_version";
+          }
+        ];
+        features = {
+          "alloc" = [ "zeroize?/alloc" ];
+          "default" = [ "alloc" "precomputed-tables" "zeroize" ];
+          "digest" = [ "dep:digest" ];
+          "ff" = [ "dep:ff" ];
+          "group" = [ "dep:group" "rand_core" ];
+          "group-bits" = [ "group" "ff/bits" ];
+          "rand_core" = [ "dep:rand_core" ];
+          "serde" = [ "dep:serde" ];
+          "zeroize" = [ "dep:zeroize" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "digest" "precomputed-tables" "zeroize" ];
+      };
+      "curve25519-dalek-derive" = rec {
+        crateName = "curve25519-dalek-derive";
+        version = "0.1.1";
+        edition = "2021";
+        sha256 = "1cry71xxrr0mcy5my3fb502cwfxy6822k4pm19cwrilrg7hq4s7l";
+        procMacro = true;
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn 2.0.48";
+            features = [ "full" ];
+          }
+        ];
+
+      };
+      "darling" = rec {
+        crateName = "darling";
+        version = "0.20.8";
+        edition = "2018";
+        sha256 = "14a38qsi9104kvk1z11rqj0bnz1866dyhnvgvbgzz17d2g6nzqsl";
+        authors = [
+          "Ted Driggs <ted.driggs@outlook.com>"
+        ];
+        dependencies = [
+          {
+            name = "darling_core";
+            packageId = "darling_core";
+          }
+          {
+            name = "darling_macro";
+            packageId = "darling_macro";
+          }
+        ];
+        features = {
+          "default" = [ "suggestions" ];
+          "diagnostics" = [ "darling_core/diagnostics" ];
+          "suggestions" = [ "darling_core/suggestions" ];
+        };
+        resolvedDefaultFeatures = [ "default" "suggestions" ];
+      };
+      "darling_core" = rec {
+        crateName = "darling_core";
+        version = "0.20.8";
+        edition = "2018";
+        sha256 = "03x7s149p06xfwcq0lgkk4yxh6jf7jckny18nzp1yyk87b1g2b4w";
+        authors = [
+          "Ted Driggs <ted.driggs@outlook.com>"
+        ];
+        dependencies = [
+          {
+            name = "fnv";
+            packageId = "fnv";
+          }
+          {
+            name = "ident_case";
+            packageId = "ident_case";
+          }
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "strsim";
+            packageId = "strsim";
+            optional = true;
+          }
+          {
+            name = "syn";
+            packageId = "syn 2.0.48";
+            features = [ "full" "extra-traits" ];
+          }
+        ];
+        features = {
+          "strsim" = [ "dep:strsim" ];
+          "suggestions" = [ "strsim" ];
+        };
+        resolvedDefaultFeatures = [ "strsim" "suggestions" ];
+      };
+      "darling_macro" = rec {
+        crateName = "darling_macro";
+        version = "0.20.8";
+        edition = "2018";
+        sha256 = "0gwkz0cjfy3fgcc1zmm7azzhj5qpja34s0cklcria4l38sjyss56";
+        procMacro = true;
+        authors = [
+          "Ted Driggs <ted.driggs@outlook.com>"
+        ];
+        dependencies = [
+          {
+            name = "darling_core";
+            packageId = "darling_core";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn 2.0.48";
+          }
+        ];
+
+      };
+      "data-encoding" = rec {
+        crateName = "data-encoding";
+        version = "2.5.0";
+        edition = "2018";
+        sha256 = "1rcbnwfmfxhlshzbn3r7srm3azqha3mn33yxyqxkzz2wpqcjm5ky";
+        authors = [
+          "Julien Cretin <git@ia0.eu>"
+        ];
+        features = {
+          "default" = [ "std" ];
+          "std" = [ "alloc" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "std" ];
+      };
+      "der" = rec {
+        crateName = "der";
+        version = "0.7.8";
+        edition = "2021";
+        sha256 = "070bwiyr80800h31c5zd96ckkgagfjgnrrdmz3dzg2lccsd3dypz";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        dependencies = [
+          {
+            name = "const-oid";
+            packageId = "const-oid";
+            optional = true;
+          }
+          {
+            name = "zeroize";
+            packageId = "zeroize";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "alloc" = [ "zeroize?/alloc" ];
+          "arbitrary" = [ "dep:arbitrary" "const-oid?/arbitrary" "std" ];
+          "bytes" = [ "dep:bytes" "alloc" ];
+          "derive" = [ "dep:der_derive" ];
+          "flagset" = [ "dep:flagset" ];
+          "oid" = [ "dep:const-oid" ];
+          "pem" = [ "dep:pem-rfc7468" "alloc" "zeroize" ];
+          "std" = [ "alloc" ];
+          "time" = [ "dep:time" ];
+          "zeroize" = [ "dep:zeroize" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "oid" "std" "zeroize" ];
+      };
+      "deranged" = rec {
+        crateName = "deranged";
+        version = "0.3.11";
+        edition = "2021";
+        sha256 = "1d1ibqqnr5qdrpw8rclwrf1myn3wf0dygl04idf4j2s49ah6yaxl";
+        authors = [
+          "Jacob Pratt <jacob@jhpratt.dev>"
+        ];
+        dependencies = [
+          {
+            name = "powerfmt";
+            packageId = "powerfmt";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "num" = [ "dep:num-traits" ];
+          "powerfmt" = [ "dep:powerfmt" ];
+          "quickcheck" = [ "dep:quickcheck" "alloc" ];
+          "rand" = [ "dep:rand" ];
+          "serde" = [ "dep:serde" ];
+          "std" = [ "alloc" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "powerfmt" "serde" "std" ];
+      };
+      "diff" = rec {
+        crateName = "diff";
+        version = "0.1.13";
+        edition = "2015";
+        sha256 = "1j0nzjxci2zqx63hdcihkp0a4dkdmzxd7my4m7zk6cjyfy34j9an";
+        authors = [
+          "Utkarsh Kukreti <utkarshkukreti@gmail.com>"
+        ];
+
+      };
+      "digest" = rec {
+        crateName = "digest";
+        version = "0.10.7";
+        edition = "2018";
+        sha256 = "14p2n6ih29x81akj097lvz7wi9b6b9hvls0lwrv7b6xwyy0s5ncy";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        dependencies = [
+          {
+            name = "block-buffer";
+            packageId = "block-buffer";
+            optional = true;
+          }
+          {
+            name = "crypto-common";
+            packageId = "crypto-common";
+          }
+          {
+            name = "subtle";
+            packageId = "subtle";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "blobby" = [ "dep:blobby" ];
+          "block-buffer" = [ "dep:block-buffer" ];
+          "const-oid" = [ "dep:const-oid" ];
+          "core-api" = [ "block-buffer" ];
+          "default" = [ "core-api" ];
+          "dev" = [ "blobby" ];
+          "mac" = [ "subtle" ];
+          "oid" = [ "const-oid" ];
+          "rand_core" = [ "crypto-common/rand_core" ];
+          "std" = [ "alloc" "crypto-common/std" ];
+          "subtle" = [ "dep:subtle" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "block-buffer" "core-api" "default" "mac" "std" "subtle" ];
+      };
+      "dirs" = rec {
+        crateName = "dirs";
+        version = "4.0.0";
+        edition = "2015";
+        sha256 = "0n8020zl4f0frfnzvgb9agvk4a14i1kjz4daqnxkgslndwmaffna";
+        authors = [
+          "Simon Ochsenreither <simon@ochsenreither.de>"
+        ];
+        dependencies = [
+          {
+            name = "dirs-sys";
+            packageId = "dirs-sys";
+          }
+        ];
+
+      };
+      "dirs-next" = rec {
+        crateName = "dirs-next";
+        version = "2.0.0";
+        edition = "2018";
+        sha256 = "1q9kr151h9681wwp6is18750ssghz6j9j7qm7qi1ngcwy7mzi35r";
+        authors = [
+          "The @xdg-rs members"
+        ];
+        dependencies = [
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "dirs-sys-next";
+            packageId = "dirs-sys-next";
+          }
+        ];
+
+      };
+      "dirs-sys" = rec {
+        crateName = "dirs-sys";
+        version = "0.3.7";
+        edition = "2015";
+        sha256 = "19md1cnkazham8a6kh22v12d8hh3raqahfk6yb043vrjr68is78v";
+        authors = [
+          "Simon Ochsenreither <simon@ochsenreither.de>"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (target."unix" or false);
+          }
+          {
+            name = "redox_users";
+            packageId = "redox_users";
+            usesDefaultFeatures = false;
+            target = { target, features }: ("redox" == target."os" or null);
+          }
+          {
+            name = "winapi";
+            packageId = "winapi";
+            target = { target, features }: (target."windows" or false);
+            features = [ "knownfolders" "objbase" "shlobj" "winbase" "winerror" ];
+          }
+        ];
+
+      };
+      "dirs-sys-next" = rec {
+        crateName = "dirs-sys-next";
+        version = "0.1.2";
+        edition = "2018";
+        sha256 = "0kavhavdxv4phzj4l0psvh55hszwnr0rcz8sxbvx20pyqi2a3gaf";
+        authors = [
+          "The @xdg-rs members"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (target."unix" or false);
+          }
+          {
+            name = "redox_users";
+            packageId = "redox_users";
+            usesDefaultFeatures = false;
+            target = { target, features }: ("redox" == target."os" or null);
+          }
+          {
+            name = "winapi";
+            packageId = "winapi";
+            target = { target, features }: (target."windows" or false);
+            features = [ "knownfolders" "objbase" "shlobj" "winbase" "winerror" ];
+          }
+        ];
+
+      };
+      "doc-comment" = rec {
+        crateName = "doc-comment";
+        version = "0.3.3";
+        edition = "2015";
+        sha256 = "043sprsf3wl926zmck1bm7gw0jq50mb76lkpk49vasfr6ax1p97y";
+        libName = "doc_comment";
+        authors = [
+          "Guillaume Gomez <guillaume1.gomez@gmail.com>"
+        ];
+        features = { };
+      };
+      "document-features" = rec {
+        crateName = "document-features";
+        version = "0.2.8";
+        edition = "2018";
+        sha256 = "15cvgxqngxslgllz15m8aban6wqfgsi6nlhr0g25yfsnd6nq4lpg";
+        procMacro = true;
+        libPath = "lib.rs";
+        authors = [
+          "Slint Developers <info@slint-ui.com>"
+        ];
+        dependencies = [
+          {
+            name = "litrs";
+            packageId = "litrs";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = { };
+        resolvedDefaultFeatures = [ "default" ];
+      };
+      "ed25519" = rec {
+        crateName = "ed25519";
+        version = "2.2.3";
+        edition = "2021";
+        sha256 = "0lydzdf26zbn82g7xfczcac9d7mzm3qgx934ijjrd5hjpjx32m8i";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        dependencies = [
+          {
+            name = "pkcs8";
+            packageId = "pkcs8";
+            optional = true;
+          }
+          {
+            name = "signature";
+            packageId = "signature";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "alloc" = [ "pkcs8?/alloc" ];
+          "default" = [ "std" ];
+          "pem" = [ "alloc" "pkcs8/pem" ];
+          "pkcs8" = [ "dep:pkcs8" ];
+          "serde" = [ "dep:serde" ];
+          "serde_bytes" = [ "serde" "dep:serde_bytes" ];
+          "std" = [ "pkcs8?/std" "signature/std" ];
+          "zeroize" = [ "dep:zeroize" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "std" ];
+      };
+      "ed25519-dalek" = rec {
+        crateName = "ed25519-dalek";
+        version = "2.1.0";
+        edition = "2021";
+        sha256 = "1h13qm789m9gdjl6jazss80hqi8ll37m0afwcnw23zcbqjp8wqhz";
+        authors = [
+          "isis lovecruft <isis@patternsinthevoid.net>"
+          "Tony Arcieri <bascule@gmail.com>"
+          "Michael Rosenberg <michael@mrosenberg.pub>"
+        ];
+        dependencies = [
+          {
+            name = "curve25519-dalek";
+            packageId = "curve25519-dalek";
+            usesDefaultFeatures = false;
+            features = [ "digest" ];
+          }
+          {
+            name = "ed25519";
+            packageId = "ed25519";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "sha2";
+            packageId = "sha2";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "subtle";
+            packageId = "subtle";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "zeroize";
+            packageId = "zeroize";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+        ];
+        devDependencies = [
+          {
+            name = "curve25519-dalek";
+            packageId = "curve25519-dalek";
+            usesDefaultFeatures = false;
+            features = [ "digest" "rand_core" ];
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+            features = [ "derive" ];
+          }
+        ];
+        features = {
+          "alloc" = [ "curve25519-dalek/alloc" "ed25519/alloc" "serde?/alloc" "zeroize/alloc" ];
+          "asm" = [ "sha2/asm" ];
+          "batch" = [ "alloc" "merlin" "rand_core" ];
+          "default" = [ "fast" "std" "zeroize" ];
+          "digest" = [ "signature/digest" ];
+          "fast" = [ "curve25519-dalek/precomputed-tables" ];
+          "legacy_compatibility" = [ "curve25519-dalek/legacy_compatibility" ];
+          "merlin" = [ "dep:merlin" ];
+          "pem" = [ "alloc" "ed25519/pem" "pkcs8" ];
+          "pkcs8" = [ "ed25519/pkcs8" ];
+          "rand_core" = [ "dep:rand_core" ];
+          "serde" = [ "dep:serde" "ed25519/serde" ];
+          "signature" = [ "dep:signature" ];
+          "std" = [ "alloc" "ed25519/std" "serde?/std" "sha2/std" ];
+          "zeroize" = [ "dep:zeroize" "curve25519-dalek/zeroize" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "fast" "std" "zeroize" ];
+      };
+      "either" = rec {
+        crateName = "either";
+        version = "1.9.0";
+        edition = "2018";
+        sha256 = "01qy3anr7jal5lpc20791vxrw0nl6vksb5j7x56q2fycgcyy8sm2";
+        authors = [
+          "bluss"
+        ];
+        features = {
+          "default" = [ "use_std" ];
+          "serde" = [ "dep:serde" ];
+        };
+        resolvedDefaultFeatures = [ "default" "use_std" ];
+      };
+      "encoding_rs" = rec {
+        crateName = "encoding_rs";
+        version = "0.8.33";
+        edition = "2018";
+        sha256 = "1qa5k4a0ipdrxq4xg9amms9r9pnnfn7nfh2i9m3mw0ka563b6s3j";
+        authors = [
+          "Henri Sivonen <hsivonen@hsivonen.fi>"
+        ];
+        dependencies = [
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+        ];
+        features = {
+          "default" = [ "alloc" ];
+          "fast-legacy-encode" = [ "fast-hangul-encode" "fast-hanja-encode" "fast-kanji-encode" "fast-gb-hanzi-encode" "fast-big5-hanzi-encode" ];
+          "packed_simd" = [ "dep:packed_simd" ];
+          "serde" = [ "dep:serde" ];
+          "simd-accel" = [ "packed_simd" "packed_simd/into_bits" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" ];
+      };
+      "endian-type" = rec {
+        crateName = "endian-type";
+        version = "0.1.2";
+        edition = "2015";
+        sha256 = "0bbh88zaig1jfqrm7w3gx0pz81kw2jakk3055vbgapw3dmk08ky3";
+        authors = [
+          "Lolirofle <lolipopple@hotmail.com>"
+        ];
+
+      };
+      "enum-primitive-derive" = rec {
+        crateName = "enum-primitive-derive";
+        version = "0.3.0";
+        edition = "2018";
+        sha256 = "0k6wcf58h5kh64yq5nfq71va53kaya0kzxwsjwbgwm2n2zd9axxs";
+        procMacro = true;
+        authors = [
+          "Doug Goldstein <cardoe@cardoe.com>"
+        ];
+        dependencies = [
+          {
+            name = "num-traits";
+            packageId = "num-traits";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn 2.0.48";
+          }
+        ];
+
+      };
+      "equivalent" = rec {
+        crateName = "equivalent";
+        version = "1.0.1";
+        edition = "2015";
+        sha256 = "1malmx5f4lkfvqasz319lq6gb3ddg19yzf9s8cykfsgzdmyq0hsl";
+
+      };
+      "erased-serde" = rec {
+        crateName = "erased-serde";
+        version = "0.4.4";
+        edition = "2021";
+        sha256 = "1lx0si6iljzmfpblhn4b0ip3kw2yv4vjyca0riqz3ix311q80wrb";
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "serde";
+            packageId = "serde";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "alloc" = [ "serde/alloc" ];
+          "default" = [ "std" ];
+          "std" = [ "alloc" "serde/std" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" ];
+      };
+      "errno" = rec {
+        crateName = "errno";
+        version = "0.3.8";
+        edition = "2018";
+        sha256 = "0ia28ylfsp36i27g1qih875cyyy4by2grf80ki8vhgh6vinf8n52";
+        authors = [
+          "Chris Wong <lambda.fairy@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+            usesDefaultFeatures = false;
+            target = { target, features }: ("hermit" == target."os" or null);
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            usesDefaultFeatures = false;
+            target = { target, features }: ("wasi" == target."os" or null);
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            usesDefaultFeatures = false;
+            target = { target, features }: (target."unix" or false);
+          }
+          {
+            name = "windows-sys";
+            packageId = "windows-sys 0.52.0";
+            target = { target, features }: (target."windows" or false);
+            features = [ "Win32_Foundation" "Win32_System_Diagnostics_Debug" ];
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "std" = [ "libc/std" ];
+        };
+        resolvedDefaultFeatures = [ "std" ];
+      };
+      "error-code" = rec {
+        crateName = "error-code";
+        version = "2.3.1";
+        edition = "2018";
+        sha256 = "08baxlf8qz01lgjsdbfhs193r9y1nlc566s5xvzyf4dzwy8qkwb4";
+        authors = [
+          "Douman <douman@gmx.se>"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "str-buf";
+            packageId = "str-buf";
+          }
+        ];
+        features = { };
+      };
+      "event-listener 2.5.3" = rec {
+        crateName = "event-listener";
+        version = "2.5.3";
+        edition = "2018";
+        sha256 = "1q4w3pndc518crld6zsqvvpy9lkzwahp2zgza9kbzmmqh9gif1h2";
+        authors = [
+          "Stjepan Glavina <stjepang@gmail.com>"
+        ];
+
+      };
+      "event-listener 4.0.3" = rec {
+        crateName = "event-listener";
+        version = "4.0.3";
+        edition = "2021";
+        sha256 = "0vk4smw1vf871vi76af1zn7w69jg3zmpjddpby2qq91bkg21bck7";
+        authors = [
+          "Stjepan Glavina <stjepang@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "concurrent-queue";
+            packageId = "concurrent-queue";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "parking";
+            packageId = "parking";
+            optional = true;
+            target = { target, features }: (!(builtins.elem "wasm" target."family"));
+          }
+          {
+            name = "pin-project-lite";
+            packageId = "pin-project-lite";
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "parking" = [ "dep:parking" ];
+          "portable-atomic" = [ "portable-atomic-util" "portable_atomic_crate" ];
+          "portable-atomic-util" = [ "dep:portable-atomic-util" ];
+          "portable_atomic_crate" = [ "dep:portable_atomic_crate" ];
+          "std" = [ "concurrent-queue/std" "parking" ];
+        };
+        resolvedDefaultFeatures = [ "parking" "std" ];
+      };
+      "event-listener 5.2.0" = rec {
+        crateName = "event-listener";
+        version = "5.2.0";
+        edition = "2021";
+        sha256 = "14fcnjgpfl22645nhc3hzkdq3a1v0srqacc3kfassg7sjj8vhprb";
+        authors = [
+          "Stjepan Glavina <stjepang@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "concurrent-queue";
+            packageId = "concurrent-queue";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "parking";
+            packageId = "parking";
+            optional = true;
+            target = { target, features }: (!(builtins.elem "wasm" target."family"));
+          }
+          {
+            name = "pin-project-lite";
+            packageId = "pin-project-lite";
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "parking" = [ "dep:parking" ];
+          "portable-atomic" = [ "portable-atomic-util" "portable_atomic_crate" ];
+          "portable-atomic-util" = [ "dep:portable-atomic-util" ];
+          "portable_atomic_crate" = [ "dep:portable_atomic_crate" ];
+          "std" = [ "concurrent-queue/std" "parking" ];
+        };
+        resolvedDefaultFeatures = [ "default" "parking" "std" ];
+      };
+      "event-listener-strategy 0.4.0" = rec {
+        crateName = "event-listener-strategy";
+        version = "0.4.0";
+        edition = "2018";
+        sha256 = "1lwprdjqp2ibbxhgm9khw7s7y7k4xiqj5i5yprqiks6mnrq4v3lm";
+        authors = [
+          "John Nunley <dev@notgull.net>"
+        ];
+        dependencies = [
+          {
+            name = "event-listener";
+            packageId = "event-listener 4.0.3";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "pin-project-lite";
+            packageId = "pin-project-lite";
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "std" = [ "event-listener/std" ];
+        };
+        resolvedDefaultFeatures = [ "std" ];
+      };
+      "event-listener-strategy 0.5.0" = rec {
+        crateName = "event-listener-strategy";
+        version = "0.5.0";
+        edition = "2021";
+        sha256 = "148jflvjrq0zrr3dx3srv88jksj1klm4amy3b9fifjdpm75azvgy";
+        authors = [
+          "John Nunley <dev@notgull.net>"
+        ];
+        dependencies = [
+          {
+            name = "event-listener";
+            packageId = "event-listener 5.2.0";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "pin-project-lite";
+            packageId = "pin-project-lite";
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "std" = [ "event-listener/std" ];
+        };
+        resolvedDefaultFeatures = [ "std" ];
+      };
+      "fastcdc" = rec {
+        crateName = "fastcdc";
+        version = "3.1.0";
+        edition = "2018";
+        sha256 = "1wi82qd58j3ysf8m2dhb092qga6rj1wwbppgsajabadzjz862457";
+        authors = [
+          "Nathan Fiedler <nathanfiedler@fastmail.fm>"
+        ];
+        dependencies = [
+          {
+            name = "async-stream";
+            packageId = "async-stream";
+            optional = true;
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+            optional = true;
+            features = [ "io-util" ];
+          }
+          {
+            name = "tokio-stream";
+            packageId = "tokio-stream";
+            optional = true;
+          }
+        ];
+        devDependencies = [
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "fs" "io-util" "rt" "rt-multi-thread" "macros" ];
+          }
+        ];
+        features = {
+          "async-stream" = [ "dep:async-stream" ];
+          "futures" = [ "dep:futures" ];
+          "tokio" = [ "dep:tokio" "tokio-stream" "async-stream" ];
+          "tokio-stream" = [ "dep:tokio-stream" ];
+        };
+        resolvedDefaultFeatures = [ "async-stream" "default" "tokio" "tokio-stream" ];
+      };
+      "fastrand" = rec {
+        crateName = "fastrand";
+        version = "2.0.1";
+        edition = "2018";
+        sha256 = "19flpv5zbzpf0rk4x77z4zf25in0brg8l7m304d3yrf47qvwxjr5";
+        authors = [
+          "Stjepan Glavina <stjepang@gmail.com>"
+        ];
+        features = {
+          "default" = [ "std" ];
+          "getrandom" = [ "dep:getrandom" ];
+          "js" = [ "std" "getrandom" ];
+          "std" = [ "alloc" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "std" ];
+      };
+      "fd-lock" = rec {
+        crateName = "fd-lock";
+        version = "3.0.13";
+        edition = "2018";
+        sha256 = "1df1jdncda67g65hrnmd2zsl7q5hdn8cm84chdalxndsx7akw0zg";
+        authors = [
+          "Yoshua Wuyts <yoshuawuyts@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "rustix";
+            packageId = "rustix";
+            target = { target, features }: (target."unix" or false);
+            features = [ "fs" ];
+          }
+          {
+            name = "windows-sys";
+            packageId = "windows-sys 0.48.0";
+            target = { target, features }: (target."windows" or false);
+            features = [ "Win32_Foundation" "Win32_Storage_FileSystem" "Win32_System_IO" ];
+          }
+        ];
+
+      };
+      "fiat-crypto" = rec {
+        crateName = "fiat-crypto";
+        version = "0.2.5";
+        edition = "2018";
+        sha256 = "1dxn0g50pv0ppal779vi7k40fr55pbhkyv4in7i13pgl4sn3wmr7";
+        authors = [
+          "Fiat Crypto library authors <jgross@mit.edu>"
+        ];
+        features = {
+          "default" = [ "std" ];
+        };
+      };
+      "filetime" = rec {
+        crateName = "filetime";
+        version = "0.2.23";
+        edition = "2018";
+        sha256 = "1za0sbq7fqidk8aaq9v7m9ms0sv8mmi49g6p5cphpan819q4gr0y";
+        authors = [
+          "Alex Crichton <alex@alexcrichton.com>"
+        ];
+        dependencies = [
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (target."unix" or false);
+          }
+          {
+            name = "redox_syscall";
+            packageId = "redox_syscall 0.4.1";
+            target = { target, features }: ("redox" == target."os" or null);
+          }
+          {
+            name = "windows-sys";
+            packageId = "windows-sys 0.52.0";
+            target = { target, features }: (target."windows" or false);
+            features = [ "Win32_Foundation" "Win32_Storage_FileSystem" ];
+          }
+        ];
+
+      };
+      "fixedbitset" = rec {
+        crateName = "fixedbitset";
+        version = "0.4.2";
+        edition = "2015";
+        sha256 = "101v41amgv5n9h4hcghvrbfk5vrncx1jwm35rn5szv4rk55i7rqc";
+        authors = [
+          "bluss"
+        ];
+        features = {
+          "default" = [ "std" ];
+          "serde" = [ "dep:serde" ];
+        };
+      };
+      "flate2" = rec {
+        crateName = "flate2";
+        version = "1.0.28";
+        edition = "2018";
+        sha256 = "03llhsh4gqdirnfxxb9g2w9n0721dyn4yjir3pz7z4vjaxb3yc26";
+        authors = [
+          "Alex Crichton <alex@alexcrichton.com>"
+          "Josh Triplett <josh@joshtriplett.org>"
+        ];
+        dependencies = [
+          {
+            name = "crc32fast";
+            packageId = "crc32fast";
+          }
+          {
+            name = "miniz_oxide";
+            packageId = "miniz_oxide";
+            optional = true;
+            usesDefaultFeatures = false;
+            features = [ "with-alloc" ];
+          }
+          {
+            name = "miniz_oxide";
+            packageId = "miniz_oxide";
+            usesDefaultFeatures = false;
+            target = { target, features }: (("wasm32" == target."arch" or null) && (!("emscripten" == target."os" or null)));
+            features = [ "with-alloc" ];
+          }
+        ];
+        features = {
+          "any_zlib" = [ "any_impl" ];
+          "cloudflare-zlib-sys" = [ "dep:cloudflare-zlib-sys" ];
+          "cloudflare_zlib" = [ "any_zlib" "cloudflare-zlib-sys" ];
+          "default" = [ "rust_backend" ];
+          "libz-ng-sys" = [ "dep:libz-ng-sys" ];
+          "libz-sys" = [ "dep:libz-sys" ];
+          "miniz-sys" = [ "rust_backend" ];
+          "miniz_oxide" = [ "dep:miniz_oxide" ];
+          "rust_backend" = [ "miniz_oxide" "any_impl" ];
+          "zlib" = [ "any_zlib" "libz-sys" ];
+          "zlib-default" = [ "any_zlib" "libz-sys/default" ];
+          "zlib-ng" = [ "any_zlib" "libz-ng-sys" ];
+          "zlib-ng-compat" = [ "zlib" "libz-sys/zlib-ng" ];
+        };
+        resolvedDefaultFeatures = [ "any_impl" "default" "miniz_oxide" "rust_backend" ];
+      };
+      "fnv" = rec {
+        crateName = "fnv";
+        version = "1.0.7";
+        edition = "2015";
+        sha256 = "1hc2mcqha06aibcaza94vbi81j6pr9a1bbxrxjfhc91zin8yr7iz";
+        libPath = "lib.rs";
+        authors = [
+          "Alex Crichton <alex@alexcrichton.com>"
+        ];
+        features = {
+          "default" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "form_urlencoded" = rec {
+        crateName = "form_urlencoded";
+        version = "1.2.1";
+        edition = "2018";
+        sha256 = "0milh8x7nl4f450s3ddhg57a3flcv6yq8hlkyk6fyr3mcb128dp1";
+        authors = [
+          "The rust-url developers"
+        ];
+        dependencies = [
+          {
+            name = "percent-encoding";
+            packageId = "percent-encoding";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "alloc" = [ "percent-encoding/alloc" ];
+          "default" = [ "std" ];
+          "std" = [ "alloc" "percent-encoding/std" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "std" ];
+      };
+      "fs2" = rec {
+        crateName = "fs2";
+        version = "0.4.3";
+        edition = "2015";
+        sha256 = "04v2hwk7035c088f19mfl5b1lz84gnvv2hv6m935n0hmirszqr4m";
+        authors = [
+          "Dan Burkert <dan@danburkert.com>"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (target."unix" or false);
+          }
+          {
+            name = "winapi";
+            packageId = "winapi";
+            target = { target, features }: (target."windows" or false);
+            features = [ "handleapi" "processthreadsapi" "winerror" "fileapi" "winbase" "std" ];
+          }
+        ];
+
+      };
+      "fuse-backend-rs" = rec {
+        crateName = "fuse-backend-rs";
+        version = "0.11.0";
+        edition = "2018";
+        sha256 = "0jyldvp0kvjk21j5vqga42lkksaf7zg8jkj3l6h2dv20kyl66nif";
+        authors = [
+          "Liu Bo <bo.liu@linux.alibaba.com>"
+          "Liu Jiang <gerry@linux.alibaba.com>"
+          "Peng Tao <bergwolf@hyper.sh>"
+        ];
+        dependencies = [
+          {
+            name = "arc-swap";
+            packageId = "arc-swap";
+          }
+          {
+            name = "bitflags";
+            packageId = "bitflags 1.3.2";
+          }
+          {
+            name = "caps";
+            packageId = "caps";
+            optional = true;
+            target = { target, features }: ("linux" == target."os" or null);
+          }
+          {
+            name = "core-foundation-sys";
+            packageId = "core-foundation-sys";
+            optional = true;
+            target = { target, features }: ("macos" == target."os" or null);
+          }
+          {
+            name = "lazy_static";
+            packageId = "lazy_static";
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+          }
+          {
+            name = "log";
+            packageId = "log";
+          }
+          {
+            name = "mio";
+            packageId = "mio";
+            features = [ "os-poll" "os-ext" ];
+          }
+          {
+            name = "nix";
+            packageId = "nix 0.24.3";
+          }
+          {
+            name = "vhost";
+            packageId = "vhost";
+            optional = true;
+            features = [ "vhost-user-slave" ];
+          }
+          {
+            name = "virtio-queue";
+            packageId = "virtio-queue";
+            optional = true;
+          }
+          {
+            name = "vm-memory";
+            packageId = "vm-memory";
+            features = [ "backend-mmap" ];
+          }
+          {
+            name = "vmm-sys-util";
+            packageId = "vmm-sys-util";
+            optional = true;
+          }
+        ];
+        devDependencies = [
+          {
+            name = "vm-memory";
+            packageId = "vm-memory";
+            features = [ "backend-mmap" "backend-bitmap" ];
+          }
+          {
+            name = "vmm-sys-util";
+            packageId = "vmm-sys-util";
+          }
+        ];
+        features = {
+          "async-io" = [ "async-trait" "tokio-uring" "tokio/fs" "tokio/net" "tokio/sync" "tokio/rt" "tokio/macros" "io-uring" ];
+          "async-trait" = [ "dep:async-trait" ];
+          "caps" = [ "dep:caps" ];
+          "core-foundation-sys" = [ "dep:core-foundation-sys" ];
+          "dbs-snapshot" = [ "dep:dbs-snapshot" ];
+          "default" = [ "fusedev" ];
+          "fusedev" = [ "vmm-sys-util" "caps" "core-foundation-sys" ];
+          "io-uring" = [ "dep:io-uring" ];
+          "persist" = [ "dbs-snapshot" "versionize" "versionize_derive" ];
+          "tokio" = [ "dep:tokio" ];
+          "tokio-uring" = [ "dep:tokio-uring" ];
+          "versionize" = [ "dep:versionize" ];
+          "versionize_derive" = [ "dep:versionize_derive" ];
+          "vhost" = [ "dep:vhost" ];
+          "vhost-user-fs" = [ "virtiofs" "vhost" "caps" ];
+          "virtio-queue" = [ "dep:virtio-queue" ];
+          "virtiofs" = [ "virtio-queue" "caps" "vmm-sys-util" ];
+          "vmm-sys-util" = [ "dep:vmm-sys-util" ];
+        };
+        resolvedDefaultFeatures = [ "caps" "core-foundation-sys" "default" "fusedev" "vhost" "vhost-user-fs" "virtio-queue" "virtiofs" "vmm-sys-util" ];
+      };
+      "futures" = rec {
+        crateName = "futures";
+        version = "0.3.30";
+        edition = "2018";
+        sha256 = "1c04g14bccmprwsvx2j9m2blhwrynq7vhl151lsvcv4gi0b6jp34";
+        dependencies = [
+          {
+            name = "futures-channel";
+            packageId = "futures-channel";
+            usesDefaultFeatures = false;
+            features = [ "sink" ];
+          }
+          {
+            name = "futures-core";
+            packageId = "futures-core";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-executor";
+            packageId = "futures-executor";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-io";
+            packageId = "futures-io";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-sink";
+            packageId = "futures-sink";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-task";
+            packageId = "futures-task";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-util";
+            packageId = "futures-util";
+            usesDefaultFeatures = false;
+            features = [ "sink" ];
+          }
+        ];
+        features = {
+          "alloc" = [ "futures-core/alloc" "futures-task/alloc" "futures-sink/alloc" "futures-channel/alloc" "futures-util/alloc" ];
+          "async-await" = [ "futures-util/async-await" "futures-util/async-await-macro" ];
+          "bilock" = [ "futures-util/bilock" ];
+          "compat" = [ "std" "futures-util/compat" ];
+          "default" = [ "std" "async-await" "executor" ];
+          "executor" = [ "std" "futures-executor/std" ];
+          "futures-executor" = [ "dep:futures-executor" ];
+          "io-compat" = [ "compat" "futures-util/io-compat" ];
+          "std" = [ "alloc" "futures-core/std" "futures-task/std" "futures-io/std" "futures-sink/std" "futures-util/std" "futures-util/io" "futures-util/channel" ];
+          "thread-pool" = [ "executor" "futures-executor/thread-pool" ];
+          "unstable" = [ "futures-core/unstable" "futures-task/unstable" "futures-channel/unstable" "futures-io/unstable" "futures-util/unstable" ];
+          "write-all-vectored" = [ "futures-util/write-all-vectored" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "async-await" "default" "executor" "futures-executor" "std" ];
+      };
+      "futures-channel" = rec {
+        crateName = "futures-channel";
+        version = "0.3.30";
+        edition = "2018";
+        sha256 = "0y6b7xxqdjm9hlcjpakcg41qfl7lihf6gavk8fyqijsxhvbzgj7a";
+        dependencies = [
+          {
+            name = "futures-core";
+            packageId = "futures-core";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-sink";
+            packageId = "futures-sink";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "alloc" = [ "futures-core/alloc" ];
+          "default" = [ "std" ];
+          "futures-sink" = [ "dep:futures-sink" ];
+          "sink" = [ "futures-sink" ];
+          "std" = [ "alloc" "futures-core/std" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "futures-sink" "sink" "std" ];
+      };
+      "futures-core" = rec {
+        crateName = "futures-core";
+        version = "0.3.30";
+        edition = "2018";
+        sha256 = "07aslayrn3lbggj54kci0ishmd1pr367fp7iks7adia1p05miinz";
+        features = {
+          "default" = [ "std" ];
+          "portable-atomic" = [ "dep:portable-atomic" ];
+          "std" = [ "alloc" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "std" ];
+      };
+      "futures-executor" = rec {
+        crateName = "futures-executor";
+        version = "0.3.30";
+        edition = "2018";
+        sha256 = "07dh08gs9vfll2h36kq32q9xd86xm6lyl9xikmmwlkqnmrrgqxm5";
+        dependencies = [
+          {
+            name = "futures-core";
+            packageId = "futures-core";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-task";
+            packageId = "futures-task";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-util";
+            packageId = "futures-util";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "num_cpus" = [ "dep:num_cpus" ];
+          "std" = [ "futures-core/std" "futures-task/std" "futures-util/std" ];
+          "thread-pool" = [ "std" "num_cpus" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "futures-io" = rec {
+        crateName = "futures-io";
+        version = "0.3.30";
+        edition = "2018";
+        sha256 = "1hgh25isvsr4ybibywhr4dpys8mjnscw4wfxxwca70cn1gi26im4";
+        features = {
+          "default" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "futures-lite" = rec {
+        crateName = "futures-lite";
+        version = "2.3.0";
+        edition = "2021";
+        sha256 = "19gk4my8zhfym6gwnpdjiyv2hw8cc098skkbkhryjdaf0yspwljj";
+        authors = [
+          "Stjepan Glavina <stjepang@gmail.com>"
+          "Contributors to futures-rs"
+        ];
+        dependencies = [
+          {
+            name = "fastrand";
+            packageId = "fastrand";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-core";
+            packageId = "futures-core";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-io";
+            packageId = "futures-io";
+            optional = true;
+          }
+          {
+            name = "parking";
+            packageId = "parking";
+            optional = true;
+          }
+          {
+            name = "pin-project-lite";
+            packageId = "pin-project-lite";
+          }
+        ];
+        features = {
+          "default" = [ "race" "std" ];
+          "fastrand" = [ "dep:fastrand" ];
+          "futures-io" = [ "dep:futures-io" ];
+          "memchr" = [ "dep:memchr" ];
+          "parking" = [ "dep:parking" ];
+          "race" = [ "fastrand" ];
+          "std" = [ "alloc" "fastrand/std" "futures-io" "parking" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "fastrand" "futures-io" "parking" "race" "std" ];
+      };
+      "futures-macro" = rec {
+        crateName = "futures-macro";
+        version = "0.3.30";
+        edition = "2018";
+        sha256 = "1b49qh9d402y8nka4q6wvvj0c88qq91wbr192mdn5h54nzs0qxc7";
+        procMacro = true;
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn 2.0.48";
+            features = [ "full" ];
+          }
+        ];
+
+      };
+      "futures-sink" = rec {
+        crateName = "futures-sink";
+        version = "0.3.30";
+        edition = "2018";
+        sha256 = "1dag8xyyaya8n8mh8smx7x6w2dpmafg2din145v973a3hw7f1f4z";
+        features = {
+          "default" = [ "std" ];
+          "std" = [ "alloc" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "std" ];
+      };
+      "futures-task" = rec {
+        crateName = "futures-task";
+        version = "0.3.30";
+        edition = "2018";
+        sha256 = "013h1724454hj8qczp8vvs10qfiqrxr937qsrv6rhii68ahlzn1q";
+        features = {
+          "default" = [ "std" ];
+          "std" = [ "alloc" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "std" ];
+      };
+      "futures-timer" = rec {
+        crateName = "futures-timer";
+        version = "3.0.2";
+        edition = "2018";
+        sha256 = "0b5v7lk9838ix6jdcrainsyrh7xrf24pwm61dp13907qkn806jz6";
+        authors = [
+          "Alex Crichton <alex@alexcrichton.com>"
+        ];
+        features = {
+          "gloo-timers" = [ "dep:gloo-timers" ];
+          "send_wrapper" = [ "dep:send_wrapper" ];
+          "wasm-bindgen" = [ "gloo-timers" "send_wrapper" ];
+        };
+      };
+      "futures-util" = rec {
+        crateName = "futures-util";
+        version = "0.3.30";
+        edition = "2018";
+        sha256 = "0j0xqhcir1zf2dcbpd421kgw6wvsk0rpxflylcysn1rlp3g02r1x";
+        dependencies = [
+          {
+            name = "futures-channel";
+            packageId = "futures-channel";
+            optional = true;
+            usesDefaultFeatures = false;
+            features = [ "std" ];
+          }
+          {
+            name = "futures-core";
+            packageId = "futures-core";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-io";
+            packageId = "futures-io";
+            optional = true;
+            usesDefaultFeatures = false;
+            features = [ "std" ];
+          }
+          {
+            name = "futures-macro";
+            packageId = "futures-macro";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-sink";
+            packageId = "futures-sink";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-task";
+            packageId = "futures-task";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "memchr";
+            packageId = "memchr";
+            optional = true;
+          }
+          {
+            name = "pin-project-lite";
+            packageId = "pin-project-lite";
+          }
+          {
+            name = "pin-utils";
+            packageId = "pin-utils";
+          }
+          {
+            name = "slab";
+            packageId = "slab";
+            optional = true;
+          }
+        ];
+        features = {
+          "alloc" = [ "futures-core/alloc" "futures-task/alloc" ];
+          "async-await-macro" = [ "async-await" "futures-macro" ];
+          "channel" = [ "std" "futures-channel" ];
+          "compat" = [ "std" "futures_01" ];
+          "default" = [ "std" "async-await" "async-await-macro" ];
+          "futures-channel" = [ "dep:futures-channel" ];
+          "futures-io" = [ "dep:futures-io" ];
+          "futures-macro" = [ "dep:futures-macro" ];
+          "futures-sink" = [ "dep:futures-sink" ];
+          "futures_01" = [ "dep:futures_01" ];
+          "io" = [ "std" "futures-io" "memchr" ];
+          "io-compat" = [ "io" "compat" "tokio-io" ];
+          "memchr" = [ "dep:memchr" ];
+          "portable-atomic" = [ "futures-core/portable-atomic" ];
+          "sink" = [ "futures-sink" ];
+          "slab" = [ "dep:slab" ];
+          "std" = [ "alloc" "futures-core/std" "futures-task/std" "slab" ];
+          "tokio-io" = [ "dep:tokio-io" ];
+          "unstable" = [ "futures-core/unstable" "futures-task/unstable" ];
+          "write-all-vectored" = [ "io" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "async-await" "async-await-macro" "channel" "default" "futures-channel" "futures-io" "futures-macro" "futures-sink" "io" "memchr" "sink" "slab" "std" ];
+      };
+      "fxhash" = rec {
+        crateName = "fxhash";
+        version = "0.2.1";
+        edition = "2015";
+        sha256 = "037mb9ichariqi45xm6mz0b11pa92gj38ba0409z3iz239sns6y3";
+        libPath = "lib.rs";
+        authors = [
+          "cbreeden <github@u.breeden.cc>"
+        ];
+        dependencies = [
+          {
+            name = "byteorder";
+            packageId = "byteorder";
+          }
+        ];
+
+      };
+      "gcp_auth" = rec {
+        crateName = "gcp_auth";
+        version = "0.10.0";
+        edition = "2021";
+        sha256 = "1m7lsh2gc7n9p0gs9k2qbxsrvchw1vz6dyz9a2ma322vd3m72b6y";
+        dependencies = [
+          {
+            name = "async-trait";
+            packageId = "async-trait";
+          }
+          {
+            name = "base64";
+            packageId = "base64";
+          }
+          {
+            name = "chrono";
+            packageId = "chrono";
+            features = [ "serde" ];
+          }
+          {
+            name = "home";
+            packageId = "home";
+          }
+          {
+            name = "hyper";
+            packageId = "hyper 0.14.28";
+            features = [ "client" "runtime" "http2" ];
+          }
+          {
+            name = "hyper-rustls";
+            packageId = "hyper-rustls";
+            usesDefaultFeatures = false;
+            features = [ "tokio-runtime" "http1" "http2" ];
+          }
+          {
+            name = "ring";
+            packageId = "ring";
+          }
+          {
+            name = "rustls";
+            packageId = "rustls 0.21.10";
+          }
+          {
+            name = "rustls-pemfile";
+            packageId = "rustls-pemfile 1.0.4";
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+            features = [ "derive" "rc" ];
+          }
+          {
+            name = "serde_json";
+            packageId = "serde_json";
+          }
+          {
+            name = "thiserror";
+            packageId = "thiserror";
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "fs" "sync" ];
+          }
+          {
+            name = "tracing";
+            packageId = "tracing";
+          }
+          {
+            name = "tracing-futures";
+            packageId = "tracing-futures";
+          }
+          {
+            name = "url";
+            packageId = "url";
+          }
+          {
+            name = "which";
+            packageId = "which 5.0.0";
+          }
+        ];
+        devDependencies = [
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "macros" "parking_lot" "rt-multi-thread" ];
+          }
+        ];
+        features = {
+          "default" = [ "hyper-rustls/rustls-native-certs" ];
+          "webpki-roots" = [ "hyper-rustls/webpki-roots" ];
+        };
+        resolvedDefaultFeatures = [ "default" ];
+      };
+      "genawaiter" = rec {
+        crateName = "genawaiter";
+        version = "0.99.1";
+        edition = "2018";
+        sha256 = "1861a6vy9lc9a8lbw496m9j9jcjcn9nf7rkm6jqkkpnb3cvd0sy8";
+        authors = [
+          "John Simon <john@whatisaph.one>"
+        ];
+        dependencies = [
+          {
+            name = "genawaiter-macro";
+            packageId = "genawaiter-macro";
+          }
+        ];
+        features = {
+          "default" = [ "proc_macro" ];
+          "futures-core" = [ "dep:futures-core" ];
+          "futures03" = [ "futures-core" ];
+          "genawaiter-proc-macro" = [ "dep:genawaiter-proc-macro" ];
+          "proc-macro-hack" = [ "dep:proc-macro-hack" ];
+          "proc_macro" = [ "genawaiter-proc-macro" "proc-macro-hack" "genawaiter-macro/proc_macro" ];
+        };
+      };
+      "genawaiter-macro" = rec {
+        crateName = "genawaiter-macro";
+        version = "0.99.1";
+        edition = "2018";
+        sha256 = "1g6zmr88fk48f1ksz9ik1i2mwjsiam9s4p9aybhvs2zwzphxychb";
+        authors = [
+          "Devin R <devin.ragotzy@gmail.com>"
+        ];
+        features = { };
+      };
+      "generic-array" = rec {
+        crateName = "generic-array";
+        version = "0.14.7";
+        edition = "2015";
+        sha256 = "16lyyrzrljfq424c3n8kfwkqihlimmsg5nhshbbp48np3yjrqr45";
+        libName = "generic_array";
+        authors = [
+          "Bartłomiej Kamiński <fizyk20@gmail.com>"
+          "Aaron Trent <novacrazy@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "typenum";
+            packageId = "typenum";
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "version_check";
+            packageId = "version_check";
+          }
+        ];
+        features = {
+          "serde" = [ "dep:serde" ];
+          "zeroize" = [ "dep:zeroize" ];
+        };
+        resolvedDefaultFeatures = [ "more_lengths" ];
+      };
+      "getrandom" = rec {
+        crateName = "getrandom";
+        version = "0.2.12";
+        edition = "2018";
+        sha256 = "1d8jb9bv38nkwlqqdjcav6gxckgwc9g30pm3qq506rvncpm9400r";
+        authors = [
+          "The Rand Project Developers"
+        ];
+        dependencies = [
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            usesDefaultFeatures = false;
+            target = { target, features }: (target."unix" or false);
+          }
+          {
+            name = "wasi";
+            packageId = "wasi";
+            usesDefaultFeatures = false;
+            target = { target, features }: ("wasi" == target."os" or null);
+          }
+        ];
+        features = {
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "core" = [ "dep:core" ];
+          "js" = [ "wasm-bindgen" "js-sys" ];
+          "js-sys" = [ "dep:js-sys" ];
+          "rustc-dep-of-std" = [ "compiler_builtins" "core" "libc/rustc-dep-of-std" "wasi/rustc-dep-of-std" ];
+          "wasm-bindgen" = [ "dep:wasm-bindgen" ];
+        };
+        resolvedDefaultFeatures = [ "std" ];
+      };
+      "gimli" = rec {
+        crateName = "gimli";
+        version = "0.28.1";
+        edition = "2018";
+        sha256 = "0lv23wc8rxvmjia3mcxc6hj9vkqnv1bqq0h8nzjcgf71mrxx6wa2";
+        features = {
+          "default" = [ "read-all" "write" ];
+          "endian-reader" = [ "read" "dep:stable_deref_trait" ];
+          "fallible-iterator" = [ "dep:fallible-iterator" ];
+          "read" = [ "read-core" ];
+          "read-all" = [ "read" "std" "fallible-iterator" "endian-reader" ];
+          "rustc-dep-of-std" = [ "dep:core" "dep:alloc" "dep:compiler_builtins" ];
+          "std" = [ "fallible-iterator?/std" "stable_deref_trait?/std" ];
+          "write" = [ "dep:indexmap" ];
+        };
+        resolvedDefaultFeatures = [ "read" "read-core" ];
+      };
+      "glob" = rec {
+        crateName = "glob";
+        version = "0.3.1";
+        edition = "2015";
+        sha256 = "16zca52nglanv23q5qrwd5jinw3d3as5ylya6y1pbx47vkxvrynj";
+        authors = [
+          "The Rust Project Developers"
+        ];
+
+      };
+      "h2 0.3.24" = rec {
+        crateName = "h2";
+        version = "0.3.24";
+        edition = "2018";
+        sha256 = "1jf9488b66nayxzp3iw3b2rb64y49hdbbywnv9wfwrsv14i48b5v";
+        authors = [
+          "Carl Lerche <me@carllerche.com>"
+          "Sean McArthur <sean@seanmonstar.com>"
+        ];
+        dependencies = [
+          {
+            name = "bytes";
+            packageId = "bytes";
+          }
+          {
+            name = "fnv";
+            packageId = "fnv";
+          }
+          {
+            name = "futures-core";
+            packageId = "futures-core";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-sink";
+            packageId = "futures-sink";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-util";
+            packageId = "futures-util";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "http";
+            packageId = "http 0.2.11";
+          }
+          {
+            name = "indexmap";
+            packageId = "indexmap 2.1.0";
+            features = [ "std" ];
+          }
+          {
+            name = "slab";
+            packageId = "slab";
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "io-util" ];
+          }
+          {
+            name = "tokio-util";
+            packageId = "tokio-util";
+            features = [ "codec" "io" ];
+          }
+          {
+            name = "tracing";
+            packageId = "tracing";
+            usesDefaultFeatures = false;
+            features = [ "std" ];
+          }
+        ];
+        devDependencies = [
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "rt-multi-thread" "macros" "sync" "net" ];
+          }
+        ];
+        features = { };
+      };
+      "h2 0.4.3" = rec {
+        crateName = "h2";
+        version = "0.4.3";
+        edition = "2021";
+        sha256 = "1m4rj76zl77jany6p10k4mm1cqwsrlc1dmgmxwp3jy7kwk92vvji";
+        authors = [
+          "Carl Lerche <me@carllerche.com>"
+          "Sean McArthur <sean@seanmonstar.com>"
+        ];
+        dependencies = [
+          {
+            name = "bytes";
+            packageId = "bytes";
+          }
+          {
+            name = "fnv";
+            packageId = "fnv";
+          }
+          {
+            name = "futures-core";
+            packageId = "futures-core";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-sink";
+            packageId = "futures-sink";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-util";
+            packageId = "futures-util";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "http";
+            packageId = "http 1.1.0";
+          }
+          {
+            name = "indexmap";
+            packageId = "indexmap 2.1.0";
+            features = [ "std" ];
+          }
+          {
+            name = "slab";
+            packageId = "slab";
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "io-util" ];
+          }
+          {
+            name = "tokio-util";
+            packageId = "tokio-util";
+            features = [ "codec" "io" ];
+          }
+          {
+            name = "tracing";
+            packageId = "tracing";
+            usesDefaultFeatures = false;
+            features = [ "std" ];
+          }
+        ];
+        devDependencies = [
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "rt-multi-thread" "macros" "sync" "net" ];
+          }
+        ];
+        features = { };
+      };
+      "half" = rec {
+        crateName = "half";
+        version = "1.8.2";
+        edition = "2018";
+        sha256 = "1mqbmx2m9qd4lslkb42fzgldsklhv9c4bxsc8j82r80d8m24mfza";
+        authors = [
+          "Kathryn Long <squeeself@gmail.com>"
+        ];
+        features = {
+          "bytemuck" = [ "dep:bytemuck" ];
+          "num-traits" = [ "dep:num-traits" ];
+          "serde" = [ "dep:serde" ];
+          "serialize" = [ "serde" ];
+          "std" = [ "alloc" ];
+          "zerocopy" = [ "dep:zerocopy" ];
+        };
+      };
+      "hashbrown 0.12.3" = rec {
+        crateName = "hashbrown";
+        version = "0.12.3";
+        edition = "2021";
+        sha256 = "1268ka4750pyg2pbgsr43f0289l5zah4arir2k4igx5a8c6fg7la";
+        authors = [
+          "Amanieu d'Antras <amanieu@gmail.com>"
+        ];
+        features = {
+          "ahash" = [ "dep:ahash" ];
+          "ahash-compile-time-rng" = [ "ahash/compile-time-rng" ];
+          "alloc" = [ "dep:alloc" ];
+          "bumpalo" = [ "dep:bumpalo" ];
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "core" = [ "dep:core" ];
+          "default" = [ "ahash" "inline-more" ];
+          "rayon" = [ "dep:rayon" ];
+          "rustc-dep-of-std" = [ "nightly" "core" "compiler_builtins" "alloc" "rustc-internal-api" ];
+          "serde" = [ "dep:serde" ];
+        };
+        resolvedDefaultFeatures = [ "raw" ];
+      };
+      "hashbrown 0.14.3" = rec {
+        crateName = "hashbrown";
+        version = "0.14.3";
+        edition = "2021";
+        sha256 = "012nywlg0lj9kwanh69my5x67vjlfmzfi9a0rq4qvis2j8fil3r9";
+        authors = [
+          "Amanieu d'Antras <amanieu@gmail.com>"
+        ];
+        features = {
+          "ahash" = [ "dep:ahash" ];
+          "alloc" = [ "dep:alloc" ];
+          "allocator-api2" = [ "dep:allocator-api2" ];
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "core" = [ "dep:core" ];
+          "default" = [ "ahash" "inline-more" "allocator-api2" ];
+          "equivalent" = [ "dep:equivalent" ];
+          "nightly" = [ "allocator-api2?/nightly" "bumpalo/allocator_api" ];
+          "rayon" = [ "dep:rayon" ];
+          "rkyv" = [ "dep:rkyv" ];
+          "rustc-dep-of-std" = [ "nightly" "core" "compiler_builtins" "alloc" "rustc-internal-api" ];
+          "serde" = [ "dep:serde" ];
+        };
+        resolvedDefaultFeatures = [ "inline-more" "raw" ];
+      };
+      "heck" = rec {
+        crateName = "heck";
+        version = "0.4.1";
+        edition = "2018";
+        sha256 = "1a7mqsnycv5z4z5vnv1k34548jzmc0ajic7c1j8jsaspnhw5ql4m";
+        authors = [
+          "Without Boats <woboats@gmail.com>"
+        ];
+        features = {
+          "unicode" = [ "unicode-segmentation" ];
+          "unicode-segmentation" = [ "dep:unicode-segmentation" ];
+        };
+        resolvedDefaultFeatures = [ "default" ];
+      };
+      "hermit-abi" = rec {
+        crateName = "hermit-abi";
+        version = "0.3.4";
+        edition = "2021";
+        sha256 = "07v5vbwb9kx0yxgdpx15h38ynpzhaqx5ncriryipypi5707hwgax";
+        authors = [
+          "Stefan Lankes"
+        ];
+        features = {
+          "alloc" = [ "dep:alloc" ];
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "core" = [ "dep:core" ];
+          "rustc-dep-of-std" = [ "core" "alloc" "compiler_builtins/rustc-dep-of-std" ];
+        };
+        resolvedDefaultFeatures = [ "default" ];
+      };
+      "hex" = rec {
+        crateName = "hex";
+        version = "0.4.3";
+        edition = "2018";
+        sha256 = "0w1a4davm1lgzpamwnba907aysmlrnygbqmfis2mqjx5m552a93z";
+        authors = [
+          "KokaKiwi <kokakiwi@kokakiwi.net>"
+        ];
+        features = {
+          "default" = [ "std" ];
+          "serde" = [ "dep:serde" ];
+          "std" = [ "alloc" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" ];
+      };
+      "hex-literal" = rec {
+        crateName = "hex-literal";
+        version = "0.4.1";
+        edition = "2021";
+        sha256 = "0iny5inkixsdr41pm2vkqh3fl66752z5j5c0cdxw16yl9ryjdqkg";
+        authors = [
+          "RustCrypto Developers"
+        ];
+
+      };
+      "home" = rec {
+        crateName = "home";
+        version = "0.5.9";
+        edition = "2021";
+        sha256 = "19grxyg35rqfd802pcc9ys1q3lafzlcjcv2pl2s5q8xpyr5kblg3";
+        authors = [
+          "Brian Anderson <andersrb@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "windows-sys";
+            packageId = "windows-sys 0.52.0";
+            target = { target, features }: (target."windows" or false);
+            features = [ "Win32_Foundation" "Win32_UI_Shell" "Win32_System_Com" ];
+          }
+        ];
+
+      };
+      "http 0.2.11" = rec {
+        crateName = "http";
+        version = "0.2.11";
+        edition = "2018";
+        sha256 = "1fwz3mhh86h5kfnr5767jlx9agpdggclq7xsqx930fflzakb2iw9";
+        authors = [
+          "Alex Crichton <alex@alexcrichton.com>"
+          "Carl Lerche <me@carllerche.com>"
+          "Sean McArthur <sean@seanmonstar.com>"
+        ];
+        dependencies = [
+          {
+            name = "bytes";
+            packageId = "bytes";
+          }
+          {
+            name = "fnv";
+            packageId = "fnv";
+          }
+          {
+            name = "itoa";
+            packageId = "itoa";
+          }
+        ];
+
+      };
+      "http 1.1.0" = rec {
+        crateName = "http";
+        version = "1.1.0";
+        edition = "2018";
+        sha256 = "0n426lmcxas6h75c2cp25m933pswlrfjz10v91vc62vib2sdvf91";
+        authors = [
+          "Alex Crichton <alex@alexcrichton.com>"
+          "Carl Lerche <me@carllerche.com>"
+          "Sean McArthur <sean@seanmonstar.com>"
+        ];
+        dependencies = [
+          {
+            name = "bytes";
+            packageId = "bytes";
+          }
+          {
+            name = "fnv";
+            packageId = "fnv";
+          }
+          {
+            name = "itoa";
+            packageId = "itoa";
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "http-body 0.4.6" = rec {
+        crateName = "http-body";
+        version = "0.4.6";
+        edition = "2018";
+        sha256 = "1lmyjfk6bqk6k9gkn1dxq770sb78pqbqshga241hr5p995bb5skw";
+        authors = [
+          "Carl Lerche <me@carllerche.com>"
+          "Lucio Franco <luciofranco14@gmail.com>"
+          "Sean McArthur <sean@seanmonstar.com>"
+        ];
+        dependencies = [
+          {
+            name = "bytes";
+            packageId = "bytes";
+          }
+          {
+            name = "http";
+            packageId = "http 0.2.11";
+          }
+          {
+            name = "pin-project-lite";
+            packageId = "pin-project-lite";
+          }
+        ];
+
+      };
+      "http-body 1.0.0" = rec {
+        crateName = "http-body";
+        version = "1.0.0";
+        edition = "2018";
+        sha256 = "0hyn8n3iadrbwq8y0p1rl1275s4nm49bllw5wji29g4aa3dqbb0w";
+        authors = [
+          "Carl Lerche <me@carllerche.com>"
+          "Lucio Franco <luciofranco14@gmail.com>"
+          "Sean McArthur <sean@seanmonstar.com>"
+        ];
+        dependencies = [
+          {
+            name = "bytes";
+            packageId = "bytes";
+          }
+          {
+            name = "http";
+            packageId = "http 1.1.0";
+          }
+        ];
+
+      };
+      "http-body-util" = rec {
+        crateName = "http-body-util";
+        version = "0.1.1";
+        edition = "2018";
+        sha256 = "07agldas2qgcfc05ckiarlmf9vzragbda823nqhrqrc6mjrghx84";
+        authors = [
+          "Carl Lerche <me@carllerche.com>"
+          "Lucio Franco <luciofranco14@gmail.com>"
+          "Sean McArthur <sean@seanmonstar.com>"
+        ];
+        dependencies = [
+          {
+            name = "bytes";
+            packageId = "bytes";
+          }
+          {
+            name = "futures-core";
+            packageId = "futures-core";
+          }
+          {
+            name = "http";
+            packageId = "http 1.1.0";
+          }
+          {
+            name = "http-body";
+            packageId = "http-body 1.0.0";
+          }
+          {
+            name = "pin-project-lite";
+            packageId = "pin-project-lite";
+          }
+        ];
+
+      };
+      "httparse" = rec {
+        crateName = "httparse";
+        version = "1.8.0";
+        edition = "2018";
+        sha256 = "010rrfahm1jss3p022fqf3j3jmm72vhn4iqhykahb9ynpaag75yq";
+        authors = [
+          "Sean McArthur <sean@seanmonstar.com>"
+        ];
+        features = {
+          "default" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "httpdate" = rec {
+        crateName = "httpdate";
+        version = "1.0.3";
+        edition = "2021";
+        sha256 = "1aa9rd2sac0zhjqh24c9xvir96g188zldkx0hr6dnnlx5904cfyz";
+        authors = [
+          "Pyfisch <pyfisch@posteo.org>"
+        ];
+
+      };
+      "humantime" = rec {
+        crateName = "humantime";
+        version = "2.1.0";
+        edition = "2018";
+        sha256 = "1r55pfkkf5v0ji1x6izrjwdq9v6sc7bv99xj6srywcar37xmnfls";
+        authors = [
+          "Paul Colomiets <paul@colomiets.name>"
+        ];
+
+      };
+      "hyper 0.14.28" = rec {
+        crateName = "hyper";
+        version = "0.14.28";
+        edition = "2018";
+        sha256 = "107gkvqx4h9bl17d602zkm2dgpfq86l2dr36yzfsi8l3xcsy35mz";
+        authors = [
+          "Sean McArthur <sean@seanmonstar.com>"
+        ];
+        dependencies = [
+          {
+            name = "bytes";
+            packageId = "bytes";
+          }
+          {
+            name = "futures-channel";
+            packageId = "futures-channel";
+          }
+          {
+            name = "futures-core";
+            packageId = "futures-core";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-util";
+            packageId = "futures-util";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "h2";
+            packageId = "h2 0.3.24";
+            optional = true;
+          }
+          {
+            name = "http";
+            packageId = "http 0.2.11";
+          }
+          {
+            name = "http-body";
+            packageId = "http-body 0.4.6";
+          }
+          {
+            name = "httparse";
+            packageId = "httparse";
+          }
+          {
+            name = "httpdate";
+            packageId = "httpdate";
+          }
+          {
+            name = "itoa";
+            packageId = "itoa";
+          }
+          {
+            name = "pin-project-lite";
+            packageId = "pin-project-lite";
+          }
+          {
+            name = "socket2";
+            packageId = "socket2";
+            optional = true;
+            features = [ "all" ];
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "sync" ];
+          }
+          {
+            name = "tower-service";
+            packageId = "tower-service";
+          }
+          {
+            name = "tracing";
+            packageId = "tracing";
+            usesDefaultFeatures = false;
+            features = [ "std" ];
+          }
+          {
+            name = "want";
+            packageId = "want";
+          }
+        ];
+        devDependencies = [
+          {
+            name = "futures-util";
+            packageId = "futures-util";
+            usesDefaultFeatures = false;
+            features = [ "alloc" ];
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "fs" "macros" "io-std" "io-util" "rt" "rt-multi-thread" "sync" "time" "test-util" ];
+          }
+        ];
+        features = {
+          "ffi" = [ "libc" ];
+          "full" = [ "client" "http1" "http2" "server" "stream" "runtime" ];
+          "h2" = [ "dep:h2" ];
+          "http2" = [ "h2" ];
+          "libc" = [ "dep:libc" ];
+          "runtime" = [ "tcp" "tokio/rt" "tokio/time" ];
+          "socket2" = [ "dep:socket2" ];
+          "tcp" = [ "socket2" "tokio/net" "tokio/rt" "tokio/time" ];
+        };
+        resolvedDefaultFeatures = [ "client" "default" "full" "h2" "http1" "http2" "runtime" "server" "socket2" "stream" "tcp" ];
+      };
+      "hyper 1.2.0" = rec {
+        crateName = "hyper";
+        version = "1.2.0";
+        edition = "2021";
+        sha256 = "0fi6k7hz5fmdph0a5r8hw50d7h2n9zxkizmafcmb65f67bblhr8q";
+        authors = [
+          "Sean McArthur <sean@seanmonstar.com>"
+        ];
+        dependencies = [
+          {
+            name = "bytes";
+            packageId = "bytes";
+          }
+          {
+            name = "futures-channel";
+            packageId = "futures-channel";
+            optional = true;
+          }
+          {
+            name = "futures-util";
+            packageId = "futures-util";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "h2";
+            packageId = "h2 0.4.3";
+            optional = true;
+          }
+          {
+            name = "http";
+            packageId = "http 1.1.0";
+          }
+          {
+            name = "http-body";
+            packageId = "http-body 1.0.0";
+          }
+          {
+            name = "httparse";
+            packageId = "httparse";
+            optional = true;
+          }
+          {
+            name = "httpdate";
+            packageId = "httpdate";
+            optional = true;
+          }
+          {
+            name = "itoa";
+            packageId = "itoa";
+            optional = true;
+          }
+          {
+            name = "pin-project-lite";
+            packageId = "pin-project-lite";
+            optional = true;
+          }
+          {
+            name = "smallvec";
+            packageId = "smallvec";
+            optional = true;
+            features = [ "const_generics" "const_new" ];
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "sync" ];
+          }
+        ];
+        devDependencies = [
+          {
+            name = "futures-channel";
+            packageId = "futures-channel";
+            features = [ "sink" ];
+          }
+          {
+            name = "futures-util";
+            packageId = "futures-util";
+            usesDefaultFeatures = false;
+            features = [ "sink" ];
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "fs" "macros" "net" "io-std" "io-util" "rt" "rt-multi-thread" "sync" "time" "test-util" ];
+          }
+        ];
+        features = {
+          "client" = [ "dep:want" "dep:pin-project-lite" "dep:smallvec" ];
+          "ffi" = [ "dep:libc" "dep:http-body-util" ];
+          "full" = [ "client" "http1" "http2" "server" ];
+          "http1" = [ "dep:futures-channel" "dep:futures-util" "dep:httparse" "dep:itoa" ];
+          "http2" = [ "dep:futures-channel" "dep:futures-util" "dep:h2" ];
+          "server" = [ "dep:httpdate" "dep:pin-project-lite" "dep:smallvec" ];
+          "tracing" = [ "dep:tracing" ];
+        };
+        resolvedDefaultFeatures = [ "default" "http1" "http2" "server" ];
+      };
+      "hyper-rustls" = rec {
+        crateName = "hyper-rustls";
+        version = "0.24.2";
+        edition = "2021";
+        sha256 = "1475j4a2nczz4aajzzsq3hpwg1zacmzbqg393a14j80ff8izsgpc";
+        dependencies = [
+          {
+            name = "futures-util";
+            packageId = "futures-util";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "http";
+            packageId = "http 0.2.11";
+          }
+          {
+            name = "hyper";
+            packageId = "hyper 0.14.28";
+            usesDefaultFeatures = false;
+            features = [ "client" ];
+          }
+          {
+            name = "rustls";
+            packageId = "rustls 0.21.10";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "rustls-native-certs";
+            packageId = "rustls-native-certs 0.6.3";
+            optional = true;
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+          }
+          {
+            name = "tokio-rustls";
+            packageId = "tokio-rustls 0.24.1";
+            usesDefaultFeatures = false;
+          }
+        ];
+        devDependencies = [
+          {
+            name = "hyper";
+            packageId = "hyper 0.14.28";
+            features = [ "full" ];
+          }
+          {
+            name = "rustls";
+            packageId = "rustls 0.21.10";
+            usesDefaultFeatures = false;
+            features = [ "tls12" ];
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "io-std" "macros" "net" "rt-multi-thread" ];
+          }
+        ];
+        features = {
+          "acceptor" = [ "hyper/server" "tokio-runtime" ];
+          "default" = [ "native-tokio" "http1" "tls12" "logging" "acceptor" ];
+          "http1" = [ "hyper/http1" ];
+          "http2" = [ "hyper/http2" ];
+          "log" = [ "dep:log" ];
+          "logging" = [ "log" "tokio-rustls/logging" "rustls/logging" ];
+          "native-tokio" = [ "tokio-runtime" "rustls-native-certs" ];
+          "rustls-native-certs" = [ "dep:rustls-native-certs" ];
+          "tls12" = [ "tokio-rustls/tls12" "rustls/tls12" ];
+          "tokio-runtime" = [ "hyper/runtime" ];
+          "webpki-roots" = [ "dep:webpki-roots" ];
+          "webpki-tokio" = [ "tokio-runtime" "webpki-roots" ];
+        };
+        resolvedDefaultFeatures = [ "http1" "http2" "rustls-native-certs" "tokio-runtime" ];
+      };
+      "hyper-timeout" = rec {
+        crateName = "hyper-timeout";
+        version = "0.4.1";
+        edition = "2018";
+        sha256 = "1c8k3g8k2yh1gxvsx9p7amkimgxhl9kafwpj7jyf8ywc5r45ifdv";
+        authors = [
+          "Herman J. Radtke III <herman@hermanradtke.com>"
+        ];
+        dependencies = [
+          {
+            name = "hyper";
+            packageId = "hyper 0.14.28";
+            features = [ "client" ];
+          }
+          {
+            name = "pin-project-lite";
+            packageId = "pin-project-lite";
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+          }
+          {
+            name = "tokio-io-timeout";
+            packageId = "tokio-io-timeout";
+          }
+        ];
+        devDependencies = [
+          {
+            name = "hyper";
+            packageId = "hyper 0.14.28";
+            features = [ "client" "http1" "tcp" ];
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "io-std" "io-util" "macros" ];
+          }
+        ];
+
+      };
+      "hyper-util" = rec {
+        crateName = "hyper-util";
+        version = "0.1.3";
+        edition = "2021";
+        sha256 = "1akngan7j0n2n0wd25c6952mvqbkj9gp1lcwzyxjc0d37l8yyf6a";
+        authors = [
+          "Sean McArthur <sean@seanmonstar.com>"
+        ];
+        dependencies = [
+          {
+            name = "bytes";
+            packageId = "bytes";
+          }
+          {
+            name = "futures-util";
+            packageId = "futures-util";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "http";
+            packageId = "http 1.1.0";
+          }
+          {
+            name = "http-body";
+            packageId = "http-body 1.0.0";
+          }
+          {
+            name = "hyper";
+            packageId = "hyper 1.2.0";
+          }
+          {
+            name = "pin-project-lite";
+            packageId = "pin-project-lite";
+          }
+          {
+            name = "socket2";
+            packageId = "socket2";
+            optional = true;
+            features = [ "all" ];
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+            optional = true;
+            features = [ "net" "rt" "time" ];
+          }
+        ];
+        devDependencies = [
+          {
+            name = "bytes";
+            packageId = "bytes";
+          }
+          {
+            name = "hyper";
+            packageId = "hyper 1.2.0";
+            features = [ "full" ];
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "macros" "test-util" ];
+          }
+        ];
+        features = {
+          "client" = [ "hyper/client" "dep:tracing" "dep:futures-channel" "dep:tower" "dep:tower-service" ];
+          "client-legacy" = [ "client" ];
+          "full" = [ "client" "client-legacy" "server" "server-auto" "service" "http1" "http2" "tokio" ];
+          "http1" = [ "hyper/http1" ];
+          "http2" = [ "hyper/http2" ];
+          "server" = [ "hyper/server" ];
+          "server-auto" = [ "server" "http1" "http2" ];
+          "service" = [ "dep:tower" "dep:tower-service" ];
+          "tokio" = [ "dep:tokio" "dep:socket2" ];
+        };
+        resolvedDefaultFeatures = [ "default" "http1" "http2" "server" "server-auto" "tokio" ];
+      };
+      "iana-time-zone" = rec {
+        crateName = "iana-time-zone";
+        version = "0.1.60";
+        edition = "2018";
+        sha256 = "0hdid5xz3jznm04lysjm3vi93h3c523w0hcc3xba47jl3ddbpzz7";
+        authors = [
+          "Andrew Straw <strawman@astraw.com>"
+          "René Kijewski <rene.kijewski@fu-berlin.de>"
+          "Ryan Lopopolo <rjl@hyperbo.la>"
+        ];
+        dependencies = [
+          {
+            name = "android_system_properties";
+            packageId = "android_system_properties";
+            target = { target, features }: ("android" == target."os" or null);
+          }
+          {
+            name = "core-foundation-sys";
+            packageId = "core-foundation-sys";
+            target = { target, features }: (("macos" == target."os" or null) || ("ios" == target."os" or null));
+          }
+          {
+            name = "iana-time-zone-haiku";
+            packageId = "iana-time-zone-haiku";
+            target = { target, features }: ("haiku" == target."os" or null);
+          }
+          {
+            name = "js-sys";
+            packageId = "js-sys";
+            target = { target, features }: ("wasm32" == target."arch" or null);
+          }
+          {
+            name = "wasm-bindgen";
+            packageId = "wasm-bindgen";
+            target = { target, features }: ("wasm32" == target."arch" or null);
+          }
+          {
+            name = "windows-core";
+            packageId = "windows-core";
+            target = { target, features }: ("windows" == target."os" or null);
+          }
+        ];
+        features = { };
+        resolvedDefaultFeatures = [ "fallback" ];
+      };
+      "iana-time-zone-haiku" = rec {
+        crateName = "iana-time-zone-haiku";
+        version = "0.1.2";
+        edition = "2018";
+        sha256 = "17r6jmj31chn7xs9698r122mapq85mfnv98bb4pg6spm0si2f67k";
+        authors = [
+          "René Kijewski <crates.io@k6i.de>"
+        ];
+        buildDependencies = [
+          {
+            name = "cc";
+            packageId = "cc";
+          }
+        ];
+
+      };
+      "ident_case" = rec {
+        crateName = "ident_case";
+        version = "1.0.1";
+        edition = "2015";
+        sha256 = "0fac21q6pwns8gh1hz3nbq15j8fi441ncl6w4vlnd1cmc55kiq5r";
+        authors = [
+          "Ted Driggs <ted.driggs@outlook.com>"
+        ];
+
+      };
+      "idna" = rec {
+        crateName = "idna";
+        version = "0.5.0";
+        edition = "2018";
+        sha256 = "1xhjrcjqq0l5bpzvdgylvpkgk94panxgsirzhjnnqfdgc4a9nkb3";
+        authors = [
+          "The rust-url developers"
+        ];
+        dependencies = [
+          {
+            name = "unicode-bidi";
+            packageId = "unicode-bidi";
+            usesDefaultFeatures = false;
+            features = [ "hardcoded-data" ];
+          }
+          {
+            name = "unicode-normalization";
+            packageId = "unicode-normalization";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "std" = [ "alloc" "unicode-bidi/std" "unicode-normalization/std" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "std" ];
+      };
+      "imbl" = rec {
+        crateName = "imbl";
+        version = "2.0.3";
+        edition = "2018";
+        sha256 = "11bhchs0d1bbbmr8ari4y4d62vqxs7xg4fkhjlhgbv98h0n193cp";
+        authors = [
+          "Bodil Stokke <bodil@bodil.org>"
+          "Joe Neeman <joeneeman@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "bitmaps";
+            packageId = "bitmaps";
+          }
+          {
+            name = "imbl-sized-chunks";
+            packageId = "imbl-sized-chunks";
+          }
+          {
+            name = "proptest";
+            packageId = "proptest";
+            optional = true;
+          }
+          {
+            name = "rand_core";
+            packageId = "rand_core";
+          }
+          {
+            name = "rand_xoshiro";
+            packageId = "rand_xoshiro";
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+            optional = true;
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "version_check";
+            packageId = "version_check";
+          }
+        ];
+        devDependencies = [
+          {
+            name = "proptest";
+            packageId = "proptest";
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+          }
+        ];
+        features = {
+          "arbitrary" = [ "dep:arbitrary" ];
+          "proptest" = [ "dep:proptest" ];
+          "quickcheck" = [ "dep:quickcheck" ];
+          "rayon" = [ "dep:rayon" ];
+          "refpool" = [ "dep:refpool" ];
+          "serde" = [ "dep:serde" ];
+        };
+        resolvedDefaultFeatures = [ "proptest" "serde" ];
+      };
+      "imbl-sized-chunks" = rec {
+        crateName = "imbl-sized-chunks";
+        version = "0.1.2";
+        edition = "2021";
+        sha256 = "0qzdw55na2w6fd44p7y9rh05nxa98gzpaigmwg57sy7db3xhch0l";
+        authors = [
+          "Bodil Stokke <bodil@bodil.org>"
+          "Joe Neeman <joeneeman@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "bitmaps";
+            packageId = "bitmaps";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "arbitrary" = [ "dep:arbitrary" ];
+          "array-ops" = [ "dep:array-ops" ];
+          "default" = [ "std" ];
+          "refpool" = [ "dep:refpool" ];
+          "ringbuffer" = [ "array-ops" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "indexmap 1.9.3" = rec {
+        crateName = "indexmap";
+        version = "1.9.3";
+        edition = "2021";
+        sha256 = "16dxmy7yvk51wvnih3a3im6fp5lmx0wx76i03n06wyak6cwhw1xx";
+        dependencies = [
+          {
+            name = "hashbrown";
+            packageId = "hashbrown 0.12.3";
+            usesDefaultFeatures = false;
+            features = [ "raw" ];
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "autocfg";
+            packageId = "autocfg";
+          }
+        ];
+        features = {
+          "arbitrary" = [ "dep:arbitrary" ];
+          "quickcheck" = [ "dep:quickcheck" ];
+          "rayon" = [ "dep:rayon" ];
+          "rustc-rayon" = [ "dep:rustc-rayon" ];
+          "serde" = [ "dep:serde" ];
+          "serde-1" = [ "serde" ];
+        };
+        resolvedDefaultFeatures = [ "serde" "serde-1" "std" ];
+      };
+      "indexmap 2.1.0" = rec {
+        crateName = "indexmap";
+        version = "2.1.0";
+        edition = "2021";
+        sha256 = "07rxrqmryr1xfnmhrjlz8ic6jw28v6h5cig3ws2c9d0wifhy2c6m";
+        dependencies = [
+          {
+            name = "equivalent";
+            packageId = "equivalent";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "hashbrown";
+            packageId = "hashbrown 0.14.3";
+            usesDefaultFeatures = false;
+            features = [ "raw" ];
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "arbitrary" = [ "dep:arbitrary" ];
+          "default" = [ "std" ];
+          "quickcheck" = [ "dep:quickcheck" ];
+          "rayon" = [ "dep:rayon" ];
+          "rustc-rayon" = [ "dep:rustc-rayon" ];
+          "serde" = [ "dep:serde" ];
+        };
+        resolvedDefaultFeatures = [ "default" "serde" "std" ];
+      };
+      "instant" = rec {
+        crateName = "instant";
+        version = "0.1.12";
+        edition = "2018";
+        sha256 = "0b2bx5qdlwayriidhrag8vhy10kdfimfhmb3jnjmsz2h9j1bwnvs";
+        authors = [
+          "sebcrozet <developer@crozet.re>"
+        ];
+        dependencies = [
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+        ];
+        features = {
+          "js-sys" = [ "dep:js-sys" ];
+          "stdweb" = [ "dep:stdweb" ];
+          "wasm-bindgen" = [ "js-sys" "wasm-bindgen_rs" "web-sys" ];
+          "wasm-bindgen_rs" = [ "dep:wasm-bindgen_rs" ];
+          "web-sys" = [ "dep:web-sys" ];
+        };
+      };
+      "inventory" = rec {
+        crateName = "inventory";
+        version = "0.3.15";
+        edition = "2021";
+        sha256 = "0rspmi9qxz9hkajg4dx5hhwmcd3n3qw107hl3050hrs1izbd6n7r";
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+
+      };
+      "ipnet" = rec {
+        crateName = "ipnet";
+        version = "2.9.0";
+        edition = "2018";
+        sha256 = "1hzrcysgwf0knf83ahb3535hrkw63mil88iqc6kjaryfblrqylcg";
+        authors = [
+          "Kris Price <kris@krisprice.nz>"
+        ];
+        features = {
+          "default" = [ "std" ];
+          "heapless" = [ "dep:heapless" ];
+          "json" = [ "serde" "schemars" ];
+          "schemars" = [ "dep:schemars" ];
+          "ser_as_str" = [ "heapless" ];
+          "serde" = [ "dep:serde" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "is-terminal" = rec {
+        crateName = "is-terminal";
+        version = "0.4.10";
+        edition = "2018";
+        sha256 = "0m9la3f7cs77y85nkbcjsxkb7k861fc6bdhahyfidgh7gljh1b8b";
+        authors = [
+          "softprops <d.tangren@gmail.com>"
+          "Dan Gohman <dev@sunfishcode.online>"
+        ];
+        dependencies = [
+          {
+            name = "hermit-abi";
+            packageId = "hermit-abi";
+            target = { target, features }: ("hermit" == target."os" or null);
+          }
+          {
+            name = "rustix";
+            packageId = "rustix";
+            target = { target, features }: (!((target."windows" or false) || ("hermit" == target."os" or null) || ("unknown" == target."os" or null)));
+            features = [ "termios" ];
+          }
+          {
+            name = "windows-sys";
+            packageId = "windows-sys 0.52.0";
+            target = { target, features }: (target."windows" or false);
+            features = [ "Win32_Foundation" "Win32_Storage_FileSystem" "Win32_System_Console" ];
+          }
+        ];
+        devDependencies = [
+          {
+            name = "rustix";
+            packageId = "rustix";
+            target = { target, features }: (!((target."windows" or false) || ("hermit" == target."os" or null) || ("unknown" == target."os" or null)));
+            features = [ "stdio" ];
+          }
+        ];
+
+      };
+      "itertools 0.10.5" = rec {
+        crateName = "itertools";
+        version = "0.10.5";
+        edition = "2018";
+        sha256 = "0ww45h7nxx5kj6z2y6chlskxd1igvs4j507anr6dzg99x1h25zdh";
+        authors = [
+          "bluss"
+        ];
+        dependencies = [
+          {
+            name = "either";
+            packageId = "either";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "default" = [ "use_std" ];
+          "use_std" = [ "use_alloc" "either/use_std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "use_alloc" "use_std" ];
+      };
+      "itertools 0.11.0" = rec {
+        crateName = "itertools";
+        version = "0.11.0";
+        edition = "2018";
+        sha256 = "0mzyqcc59azx9g5cg6fs8k529gvh4463smmka6jvzs3cd2jp7hdi";
+        authors = [
+          "bluss"
+        ];
+        dependencies = [
+          {
+            name = "either";
+            packageId = "either";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "default" = [ "use_std" ];
+          "use_std" = [ "use_alloc" "either/use_std" ];
+        };
+        resolvedDefaultFeatures = [ "use_alloc" ];
+      };
+      "itertools 0.12.0" = rec {
+        crateName = "itertools";
+        version = "0.12.0";
+        edition = "2018";
+        sha256 = "1c07gzdlc6a1c8p8jrvvw3gs52bss3y58cs2s21d9i978l36pnr5";
+        authors = [
+          "bluss"
+        ];
+        dependencies = [
+          {
+            name = "either";
+            packageId = "either";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "default" = [ "use_std" ];
+          "use_std" = [ "use_alloc" "either/use_std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "use_alloc" "use_std" ];
+      };
+      "itoa" = rec {
+        crateName = "itoa";
+        version = "1.0.10";
+        edition = "2018";
+        sha256 = "0k7xjfki7mnv6yzjrbnbnjllg86acmbnk4izz2jmm1hx2wd6v95i";
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        features = {
+          "no-panic" = [ "dep:no-panic" ];
+        };
+      };
+      "jobserver" = rec {
+        crateName = "jobserver";
+        version = "0.1.27";
+        edition = "2018";
+        sha256 = "0z9w6vfqwbr6hfk9yaw7kydlh6f7k39xdlszxlh39in4acwzcdwc";
+        authors = [
+          "Alex Crichton <alex@alexcrichton.com>"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (target."unix" or false);
+          }
+        ];
+
+      };
+      "js-sys" = rec {
+        crateName = "js-sys";
+        version = "0.3.67";
+        edition = "2018";
+        sha256 = "1lar78p13w781b4zf44a0sk26i461fczbdrhpan6kjav4gqkc7cs";
+        authors = [
+          "The wasm-bindgen Developers"
+        ];
+        dependencies = [
+          {
+            name = "wasm-bindgen";
+            packageId = "wasm-bindgen";
+          }
+        ];
+
+      };
+      "lazy_static" = rec {
+        crateName = "lazy_static";
+        version = "1.4.0";
+        edition = "2015";
+        sha256 = "0in6ikhw8mgl33wjv6q6xfrb5b9jr16q8ygjy803fay4zcisvaz2";
+        authors = [
+          "Marvin Löbel <loebel.marvin@gmail.com>"
+        ];
+        features = {
+          "spin" = [ "dep:spin" ];
+          "spin_no_std" = [ "spin" ];
+        };
+      };
+      "lexical-core" = rec {
+        crateName = "lexical-core";
+        version = "0.8.5";
+        edition = "2018";
+        sha256 = "0ihf0x3vrk25fq3bv9q35m0xax0wmvwkh0j0pjm2yk4ddvh5vpic";
+        authors = [
+          "Alex Huszagh <ahuszagh@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "lexical-parse-float";
+            packageId = "lexical-parse-float";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "lexical-parse-integer";
+            packageId = "lexical-parse-integer";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "lexical-util";
+            packageId = "lexical-util";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "lexical-write-float";
+            packageId = "lexical-write-float";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "lexical-write-integer";
+            packageId = "lexical-write-integer";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "compact" = [ "lexical-write-integer/compact" "lexical-write-float/compact" "lexical-parse-integer/compact" "lexical-parse-float/compact" ];
+          "default" = [ "std" "write-integers" "write-floats" "parse-integers" "parse-floats" ];
+          "f128" = [ "lexical-util/f128" "lexical-parse-float/f128" "lexical-write-float/f128" ];
+          "f16" = [ "lexical-util/f16" "lexical-parse-float/f16" "lexical-write-float/f16" ];
+          "format" = [ "lexical-util/format" "lexical-parse-integer/format" "lexical-parse-float/format" "lexical-write-integer/format" "lexical-write-float/format" ];
+          "lexical-parse-float" = [ "dep:lexical-parse-float" ];
+          "lexical-parse-integer" = [ "dep:lexical-parse-integer" ];
+          "lexical-write-float" = [ "dep:lexical-write-float" ];
+          "lexical-write-integer" = [ "dep:lexical-write-integer" ];
+          "lint" = [ "lexical-util/lint" "lexical-write-integer/lint" "lexical-write-float/lint" "lexical-parse-integer/lint" "lexical-parse-float/lint" ];
+          "nightly" = [ "lexical-write-integer/nightly" "lexical-write-float/nightly" "lexical-parse-integer/nightly" "lexical-parse-float/nightly" ];
+          "parse-floats" = [ "lexical-parse-float" "parse" "floats" ];
+          "parse-integers" = [ "lexical-parse-integer" "parse" "integers" ];
+          "power-of-two" = [ "lexical-util/power-of-two" "lexical-write-integer/power-of-two" "lexical-write-float/power-of-two" "lexical-parse-integer/power-of-two" "lexical-parse-float/power-of-two" ];
+          "radix" = [ "lexical-util/radix" "lexical-write-integer/radix" "lexical-write-float/radix" "lexical-parse-integer/radix" "lexical-parse-float/radix" ];
+          "safe" = [ "lexical-write-integer/safe" "lexical-write-float/safe" "lexical-parse-integer/safe" "lexical-parse-float/safe" ];
+          "std" = [ "lexical-util/std" "lexical-write-integer/std" "lexical-write-float/std" "lexical-parse-integer/std" "lexical-parse-float/std" ];
+          "write-floats" = [ "lexical-write-float" "write" "floats" ];
+          "write-integers" = [ "lexical-write-integer" "write" "integers" ];
+        };
+        resolvedDefaultFeatures = [ "default" "floats" "format" "integers" "lexical-parse-float" "lexical-parse-integer" "lexical-write-float" "lexical-write-integer" "parse" "parse-floats" "parse-integers" "std" "write" "write-floats" "write-integers" ];
+      };
+      "lexical-parse-float" = rec {
+        crateName = "lexical-parse-float";
+        version = "0.8.5";
+        edition = "2018";
+        sha256 = "0py0gp8hlzcrlvjqmqlpl2v1as65iiqxq2xsabxvhc01pmg3lfv8";
+        authors = [
+          "Alex Huszagh <ahuszagh@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "lexical-parse-integer";
+            packageId = "lexical-parse-integer";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "lexical-util";
+            packageId = "lexical-util";
+            usesDefaultFeatures = false;
+            features = [ "parse-floats" ];
+          }
+          {
+            name = "static_assertions";
+            packageId = "static_assertions";
+          }
+        ];
+        features = {
+          "compact" = [ "lexical-util/compact" "lexical-parse-integer/compact" ];
+          "default" = [ "std" ];
+          "f128" = [ "lexical-util/f128" ];
+          "f16" = [ "lexical-util/f16" ];
+          "format" = [ "lexical-util/format" "lexical-parse-integer/format" ];
+          "lint" = [ "lexical-util/lint" "lexical-parse-integer/lint" ];
+          "nightly" = [ "lexical-parse-integer/nightly" ];
+          "power-of-two" = [ "lexical-util/power-of-two" "lexical-parse-integer/power-of-two" ];
+          "radix" = [ "lexical-util/radix" "lexical-parse-integer/radix" "power-of-two" ];
+          "safe" = [ "lexical-parse-integer/safe" ];
+          "std" = [ "lexical-util/std" "lexical-parse-integer/std" ];
+        };
+        resolvedDefaultFeatures = [ "format" "std" ];
+      };
+      "lexical-parse-integer" = rec {
+        crateName = "lexical-parse-integer";
+        version = "0.8.6";
+        edition = "2018";
+        sha256 = "1sayji3mpvb2xsjq56qcq3whfz8px9a6fxk5v7v15hyhbr4982bd";
+        authors = [
+          "Alex Huszagh <ahuszagh@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "lexical-util";
+            packageId = "lexical-util";
+            usesDefaultFeatures = false;
+            features = [ "parse-integers" ];
+          }
+          {
+            name = "static_assertions";
+            packageId = "static_assertions";
+          }
+        ];
+        features = {
+          "compact" = [ "lexical-util/compact" ];
+          "default" = [ "std" ];
+          "format" = [ "lexical-util/format" ];
+          "lint" = [ "lexical-util/lint" ];
+          "power-of-two" = [ "lexical-util/power-of-two" ];
+          "radix" = [ "lexical-util/radix" "power-of-two" ];
+          "std" = [ "lexical-util/std" ];
+        };
+        resolvedDefaultFeatures = [ "format" "std" ];
+      };
+      "lexical-util" = rec {
+        crateName = "lexical-util";
+        version = "0.8.5";
+        edition = "2018";
+        sha256 = "1z73qkv7yxhsbc4aiginn1dqmsj8jarkrdlyxc88g2gz2vzvjmaj";
+        authors = [
+          "Alex Huszagh <ahuszagh@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "static_assertions";
+            packageId = "static_assertions";
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "f128" = [ "floats" ];
+          "f16" = [ "floats" ];
+          "parse-floats" = [ "parse" "floats" ];
+          "parse-integers" = [ "parse" "integers" ];
+          "radix" = [ "power-of-two" ];
+          "write-floats" = [ "write" "floats" ];
+          "write-integers" = [ "write" "integers" ];
+        };
+        resolvedDefaultFeatures = [ "floats" "format" "integers" "parse" "parse-floats" "parse-integers" "std" "write" "write-floats" "write-integers" ];
+      };
+      "lexical-write-float" = rec {
+        crateName = "lexical-write-float";
+        version = "0.8.5";
+        edition = "2018";
+        sha256 = "0qk825l0csvnksh9sywb51996cjc2bylq6rxjaiha7sqqjhvmjmc";
+        authors = [
+          "Alex Huszagh <ahuszagh@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "lexical-util";
+            packageId = "lexical-util";
+            usesDefaultFeatures = false;
+            features = [ "write-floats" ];
+          }
+          {
+            name = "lexical-write-integer";
+            packageId = "lexical-write-integer";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "static_assertions";
+            packageId = "static_assertions";
+          }
+        ];
+        features = {
+          "compact" = [ "lexical-util/compact" "lexical-write-integer/compact" ];
+          "default" = [ "std" ];
+          "f128" = [ "lexical-util/f128" ];
+          "f16" = [ "lexical-util/f16" ];
+          "format" = [ "lexical-util/format" ];
+          "lint" = [ "lexical-util/lint" "lexical-write-integer/lint" ];
+          "nightly" = [ "lexical-write-integer/nightly" ];
+          "power-of-two" = [ "lexical-util/power-of-two" "lexical-write-integer/power-of-two" ];
+          "radix" = [ "lexical-util/radix" "lexical-write-integer/radix" "power-of-two" ];
+          "safe" = [ "lexical-write-integer/safe" ];
+          "std" = [ "lexical-util/std" "lexical-write-integer/std" ];
+        };
+        resolvedDefaultFeatures = [ "format" "std" ];
+      };
+      "lexical-write-integer" = rec {
+        crateName = "lexical-write-integer";
+        version = "0.8.5";
+        edition = "2018";
+        sha256 = "0ii4hmvqrg6pd4j9y1pkhkp0nw2wpivjzmljh6v6ca22yk8z7dp1";
+        authors = [
+          "Alex Huszagh <ahuszagh@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "lexical-util";
+            packageId = "lexical-util";
+            usesDefaultFeatures = false;
+            features = [ "write-integers" ];
+          }
+          {
+            name = "static_assertions";
+            packageId = "static_assertions";
+          }
+        ];
+        features = {
+          "compact" = [ "lexical-util/compact" ];
+          "default" = [ "std" ];
+          "format" = [ "lexical-util/format" ];
+          "lint" = [ "lexical-util/lint" ];
+          "power-of-two" = [ "lexical-util/power-of-two" ];
+          "radix" = [ "lexical-util/radix" "power-of-two" ];
+          "std" = [ "lexical-util/std" ];
+        };
+        resolvedDefaultFeatures = [ "format" "std" ];
+      };
+      "libc" = rec {
+        crateName = "libc";
+        version = "0.2.152";
+        edition = "2015";
+        sha256 = "1rsnma7hnw22w7jh9yqg43slddvfbnfzrvm3s7s4kinbj1jvzqqk";
+        authors = [
+          "The Rust Project Developers"
+        ];
+        features = {
+          "default" = [ "std" ];
+          "rustc-dep-of-std" = [ "align" "rustc-std-workspace-core" ];
+          "rustc-std-workspace-core" = [ "dep:rustc-std-workspace-core" ];
+          "use_std" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "extra_traits" "std" ];
+      };
+      "libm" = rec {
+        crateName = "libm";
+        version = "0.2.8";
+        edition = "2018";
+        sha256 = "0n4hk1rs8pzw8hdfmwn96c4568s93kfxqgcqswr7sajd2diaihjf";
+        authors = [
+          "Jorge Aparicio <jorge@japaric.io>"
+        ];
+        features = {
+          "musl-reference-tests" = [ "rand" ];
+          "rand" = [ "dep:rand" ];
+        };
+        resolvedDefaultFeatures = [ "default" ];
+      };
+      "libredox" = rec {
+        crateName = "libredox";
+        version = "0.0.1";
+        edition = "2021";
+        sha256 = "1s2fh4ikpp9xl0lsl01pi0n8pw1q9s3ld452vd8qh1v63v537j45";
+        authors = [
+          "4lDO2 <4lDO2@protonmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "bitflags";
+            packageId = "bitflags 2.4.2";
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+          }
+          {
+            name = "redox_syscall";
+            packageId = "redox_syscall 0.4.1";
+          }
+        ];
+        features = {
+          "default" = [ "scheme" "call" ];
+          "scheme" = [ "call" ];
+        };
+        resolvedDefaultFeatures = [ "call" ];
+      };
+      "linux-raw-sys" = rec {
+        crateName = "linux-raw-sys";
+        version = "0.4.13";
+        edition = "2021";
+        sha256 = "172k2c6422gsc914ig8rh99mb9yc7siw6ikc3d9xw1k7vx0s3k81";
+        authors = [
+          "Dan Gohman <dev@sunfishcode.online>"
+        ];
+        features = {
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "core" = [ "dep:core" ];
+          "default" = [ "std" "general" "errno" ];
+          "rustc-dep-of-std" = [ "core" "compiler_builtins" "no_std" ];
+        };
+        resolvedDefaultFeatures = [ "elf" "errno" "general" "if_ether" "ioctl" "net" "netlink" "no_std" "prctl" "std" "xdp" ];
+      };
+      "litrs" = rec {
+        crateName = "litrs";
+        version = "0.4.1";
+        edition = "2018";
+        sha256 = "19cssch9gc0x2snd9089nvwzz79zx6nzsi3icffpx25p4hck1kml";
+        authors = [
+          "Lukas Kalbertodt <lukas.kalbertodt@gmail.com>"
+        ];
+        features = {
+          "check_suffix" = [ "unicode-xid" ];
+          "default" = [ "proc-macro2" ];
+          "proc-macro2" = [ "dep:proc-macro2" ];
+          "unicode-xid" = [ "dep:unicode-xid" ];
+        };
+      };
+      "lock_api" = rec {
+        crateName = "lock_api";
+        version = "0.4.11";
+        edition = "2018";
+        sha256 = "0iggx0h4jx63xm35861106af3jkxq06fpqhpkhgw0axi2n38y5iw";
+        authors = [
+          "Amanieu d'Antras <amanieu@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "scopeguard";
+            packageId = "scopeguard";
+            usesDefaultFeatures = false;
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "autocfg";
+            packageId = "autocfg";
+          }
+        ];
+        features = {
+          "default" = [ "atomic_usize" ];
+          "owning_ref" = [ "dep:owning_ref" ];
+          "serde" = [ "dep:serde" ];
+        };
+        resolvedDefaultFeatures = [ "atomic_usize" "default" ];
+      };
+      "log" = rec {
+        crateName = "log";
+        version = "0.4.20";
+        edition = "2015";
+        sha256 = "13rf7wphnwd61vazpxr7fiycin6cb1g8fmvgqg18i464p0y1drmm";
+        authors = [
+          "The Rust Project Developers"
+        ];
+        features = {
+          "kv_unstable" = [ "value-bag" ];
+          "kv_unstable_serde" = [ "kv_unstable_std" "value-bag/serde" "serde" ];
+          "kv_unstable_std" = [ "std" "kv_unstable" "value-bag/error" ];
+          "kv_unstable_sval" = [ "kv_unstable" "value-bag/sval" "sval" "sval_ref" ];
+          "serde" = [ "dep:serde" ];
+          "sval" = [ "dep:sval" ];
+          "sval_ref" = [ "dep:sval_ref" ];
+          "value-bag" = [ "dep:value-bag" ];
+        };
+        resolvedDefaultFeatures = [ "std" ];
+      };
+      "lzma-sys" = rec {
+        crateName = "lzma-sys";
+        version = "0.1.20";
+        edition = "2018";
+        links = "lzma";
+        sha256 = "09sxp20waxyglgn3cjz8qjkspb3ryz2fwx4rigkwvrk46ymh9njz";
+        authors = [
+          "Alex Crichton <alex@alexcrichton.com>"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "cc";
+            packageId = "cc";
+          }
+          {
+            name = "pkg-config";
+            packageId = "pkg-config";
+          }
+        ];
+        features = { };
+      };
+      "magic" = rec {
+        crateName = "magic";
+        version = "0.16.2";
+        edition = "2018";
+        sha256 = "0g9py31aw19j5sr5lznb068byhgbiynflvizjrxcwgccvw1sw052";
+        authors = [
+          "Daniel Micay <danielmicay@gmail.com>"
+          "Petar Radošević <petar@wunki.org>"
+          "lilydjwg <lilydjwg@gmail.com>"
+          "Jeff Belgum <belgum@bastille.io>"
+          "Onur Aslan <onur@onur.im>"
+          "robo9k <robo9k@symlink.io>"
+        ];
+        dependencies = [
+          {
+            name = "bitflags";
+            packageId = "bitflags 2.4.2";
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "magic-sys";
+            packageId = "magic-sys";
+          }
+          {
+            name = "thiserror";
+            packageId = "thiserror";
+          }
+        ];
+
+      };
+      "magic-sys" = rec {
+        crateName = "magic-sys";
+        version = "0.3.0";
+        edition = "2015";
+        links = "magic";
+        sha256 = "1g5k9d9igxv4h23nbhp8bqa5gdpkd3ahgm0rh5i0s54mi3h6my7g";
+        authors = [
+          "robo9k <robo9k@symlink.io>"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+            usesDefaultFeatures = false;
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "vcpkg";
+            packageId = "vcpkg";
+          }
+        ];
+        features = {
+          "default" = [ "v5-38" ];
+          "v5-05" = [ "v5-04" ];
+          "v5-10" = [ "v5-05" ];
+          "v5-13" = [ "v5-10" ];
+          "v5-20" = [ "v5-13" ];
+          "v5-21" = [ "v5-20" ];
+          "v5-22" = [ "v5-21" ];
+          "v5-23" = [ "v5-22" ];
+          "v5-25" = [ "v5-23" ];
+          "v5-27" = [ "v5-25" ];
+          "v5-32" = [ "v5-27" ];
+          "v5-35" = [ "v5-32" ];
+          "v5-38" = [ "v5-35" ];
+          "v5-40" = [ "v5-38" ];
+        };
+        resolvedDefaultFeatures = [ "default" "v5-04" "v5-05" "v5-10" "v5-13" "v5-20" "v5-21" "v5-22" "v5-23" "v5-25" "v5-27" "v5-32" "v5-35" "v5-38" ];
+      };
+      "matchers" = rec {
+        crateName = "matchers";
+        version = "0.1.0";
+        edition = "2018";
+        sha256 = "0n2mbk7lg2vf962c8xwzdq96yrc9i0p8dbmm4wa1nnkcp1dhfqw2";
+        authors = [
+          "Eliza Weisman <eliza@buoyant.io>"
+        ];
+        dependencies = [
+          {
+            name = "regex-automata";
+            packageId = "regex-automata 0.1.10";
+          }
+        ];
+
+      };
+      "matchit" = rec {
+        crateName = "matchit";
+        version = "0.7.3";
+        edition = "2021";
+        sha256 = "156bgdmmlv4crib31qhgg49nsjk88dxkdqp80ha2pk2rk6n6ax0f";
+        authors = [
+          "Ibraheem Ahmed <ibraheem@ibraheem.ca>"
+        ];
+        features = { };
+        resolvedDefaultFeatures = [ "default" ];
+      };
+      "md-5" = rec {
+        crateName = "md-5";
+        version = "0.10.6";
+        edition = "2018";
+        sha256 = "1kvq5rnpm4fzwmyv5nmnxygdhhb2369888a06gdc9pxyrzh7x7nq";
+        libName = "md5";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        dependencies = [
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "digest";
+            packageId = "digest";
+          }
+        ];
+        devDependencies = [
+          {
+            name = "digest";
+            packageId = "digest";
+            features = [ "dev" ];
+          }
+        ];
+        features = {
+          "asm" = [ "md5-asm" ];
+          "default" = [ "std" ];
+          "md5-asm" = [ "dep:md5-asm" ];
+          "oid" = [ "digest/oid" ];
+          "std" = [ "digest/std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "memchr" = rec {
+        crateName = "memchr";
+        version = "2.7.1";
+        edition = "2021";
+        sha256 = "0jf1kicqa4vs9lyzj4v4y1p90q0dh87hvhsdd5xvhnp527sw8gaj";
+        authors = [
+          "Andrew Gallant <jamslam@gmail.com>"
+          "bluss"
+        ];
+        features = {
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "core" = [ "dep:core" ];
+          "default" = [ "std" ];
+          "logging" = [ "dep:log" ];
+          "rustc-dep-of-std" = [ "core" "compiler_builtins" ];
+          "std" = [ "alloc" ];
+          "use_std" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "std" ];
+      };
+      "memoffset 0.6.5" = rec {
+        crateName = "memoffset";
+        version = "0.6.5";
+        edition = "2015";
+        sha256 = "1kkrzll58a3ayn5zdyy9i1f1v3mx0xgl29x0chq614zazba638ss";
+        authors = [
+          "Gilad Naaman <gilad.naaman@gmail.com>"
+        ];
+        buildDependencies = [
+          {
+            name = "autocfg";
+            packageId = "autocfg";
+          }
+        ];
+        features = { };
+        resolvedDefaultFeatures = [ "default" ];
+      };
+      "memoffset 0.9.0" = rec {
+        crateName = "memoffset";
+        version = "0.9.0";
+        edition = "2015";
+        sha256 = "0v20ihhdzkfw1jx00a7zjpk2dcp5qjq6lz302nyqamd9c4f4nqss";
+        authors = [
+          "Gilad Naaman <gilad.naaman@gmail.com>"
+        ];
+        buildDependencies = [
+          {
+            name = "autocfg";
+            packageId = "autocfg";
+          }
+        ];
+        features = { };
+        resolvedDefaultFeatures = [ "default" ];
+      };
+      "mime" = rec {
+        crateName = "mime";
+        version = "0.3.17";
+        edition = "2015";
+        sha256 = "16hkibgvb9klh0w0jk5crr5xv90l3wlf77ggymzjmvl1818vnxv8";
+        authors = [
+          "Sean McArthur <sean@seanmonstar.com>"
+        ];
+
+      };
+      "minimal-lexical" = rec {
+        crateName = "minimal-lexical";
+        version = "0.2.1";
+        edition = "2018";
+        sha256 = "16ppc5g84aijpri4jzv14rvcnslvlpphbszc7zzp6vfkddf4qdb8";
+        authors = [
+          "Alex Huszagh <ahuszagh@gmail.com>"
+        ];
+        features = {
+          "default" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "std" ];
+      };
+      "miniz_oxide" = rec {
+        crateName = "miniz_oxide";
+        version = "0.7.1";
+        edition = "2018";
+        sha256 = "1ivl3rbbdm53bzscrd01g60l46lz5krl270487d8lhjvwl5hx0g7";
+        authors = [
+          "Frommi <daniil.liferenko@gmail.com>"
+          "oyvindln <oyvindln@users.noreply.github.com>"
+        ];
+        dependencies = [
+          {
+            name = "adler";
+            packageId = "adler";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "alloc" = [ "dep:alloc" ];
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "core" = [ "dep:core" ];
+          "default" = [ "with-alloc" ];
+          "rustc-dep-of-std" = [ "core" "alloc" "compiler_builtins" "adler/rustc-dep-of-std" ];
+          "simd" = [ "simd-adler32" ];
+          "simd-adler32" = [ "dep:simd-adler32" ];
+        };
+        resolvedDefaultFeatures = [ "with-alloc" ];
+      };
+      "mio" = rec {
+        crateName = "mio";
+        version = "0.8.10";
+        edition = "2018";
+        sha256 = "02gyaxvaia9zzi4drrw59k9s0j6pa5d1y2kv7iplwjipdqlhngcg";
+        authors = [
+          "Carl Lerche <me@carllerche.com>"
+          "Thomas de Zeeuw <thomasdezeeuw@gmail.com>"
+          "Tokio Contributors <team@tokio.rs>"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: ("wasi" == target."os" or null);
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (target."unix" or false);
+          }
+          {
+            name = "log";
+            packageId = "log";
+            optional = true;
+          }
+          {
+            name = "wasi";
+            packageId = "wasi";
+            target = { target, features }: ("wasi" == target."os" or null);
+          }
+          {
+            name = "windows-sys";
+            packageId = "windows-sys 0.48.0";
+            target = { target, features }: (target."windows" or false);
+            features = [ "Win32_Foundation" "Win32_Networking_WinSock" "Win32_Storage_FileSystem" "Win32_System_IO" "Win32_System_WindowsProgramming" ];
+          }
+        ];
+        features = {
+          "default" = [ "log" ];
+          "log" = [ "dep:log" ];
+          "os-ext" = [ "os-poll" "windows-sys/Win32_System_Pipes" "windows-sys/Win32_Security" ];
+        };
+        resolvedDefaultFeatures = [ "default" "log" "net" "os-ext" "os-poll" ];
+      };
+      "multimap" = rec {
+        crateName = "multimap";
+        version = "0.8.3";
+        edition = "2015";
+        sha256 = "0sicyz4n500vdhgcxn4g8jz97cp1ijir1rnbgph3pmx9ckz4dkp5";
+        authors = [
+          "Håvar Nøvik <havar.novik@gmail.com>"
+        ];
+        features = {
+          "default" = [ "serde_impl" ];
+          "serde" = [ "dep:serde" ];
+          "serde_impl" = [ "serde" ];
+        };
+      };
+      "nibble_vec" = rec {
+        crateName = "nibble_vec";
+        version = "0.1.0";
+        edition = "2018";
+        sha256 = "0hsdp3s724s30hkqz74ky6sqnadhp2xwcj1n1hzy4vzkz4yxi9bp";
+        authors = [
+          "Michael Sproul <micsproul@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "smallvec";
+            packageId = "smallvec";
+          }
+        ];
+
+      };
+      "nix 0.24.3" = rec {
+        crateName = "nix";
+        version = "0.24.3";
+        edition = "2018";
+        sha256 = "0sc0yzdl51b49bqd9l9cmimp1sw1hxb8iyv4d35ww6d7m5rfjlps";
+        authors = [
+          "The nix-rust Project Developers"
+        ];
+        dependencies = [
+          {
+            name = "bitflags";
+            packageId = "bitflags 1.3.2";
+          }
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            features = [ "extra_traits" ];
+          }
+          {
+            name = "memoffset";
+            packageId = "memoffset 0.6.5";
+            optional = true;
+            target = { target, features }: (!("redox" == target."os" or null));
+          }
+        ];
+        features = {
+          "default" = [ "acct" "aio" "dir" "env" "event" "feature" "fs" "hostname" "inotify" "ioctl" "kmod" "mman" "mount" "mqueue" "net" "personality" "poll" "process" "pthread" "ptrace" "quota" "reboot" "resource" "sched" "signal" "socket" "term" "time" "ucontext" "uio" "user" "zerocopy" ];
+          "dir" = [ "fs" ];
+          "memoffset" = [ "dep:memoffset" ];
+          "mount" = [ "uio" ];
+          "mqueue" = [ "fs" ];
+          "net" = [ "socket" ];
+          "ptrace" = [ "process" ];
+          "sched" = [ "process" ];
+          "signal" = [ "process" ];
+          "socket" = [ "memoffset" ];
+          "ucontext" = [ "signal" ];
+          "user" = [ "feature" ];
+          "zerocopy" = [ "fs" "uio" ];
+        };
+        resolvedDefaultFeatures = [ "acct" "aio" "default" "dir" "env" "event" "feature" "fs" "hostname" "inotify" "ioctl" "kmod" "memoffset" "mman" "mount" "mqueue" "net" "personality" "poll" "process" "pthread" "ptrace" "quota" "reboot" "resource" "sched" "signal" "socket" "term" "time" "ucontext" "uio" "user" "zerocopy" ];
+      };
+      "nix 0.25.1" = rec {
+        crateName = "nix";
+        version = "0.25.1";
+        edition = "2018";
+        sha256 = "1r4vyp5g1lxzpig31bkrhxdf2bggb4nvk405x5gngzfvwxqgyipk";
+        authors = [
+          "The nix-rust Project Developers"
+        ];
+        dependencies = [
+          {
+            name = "bitflags";
+            packageId = "bitflags 1.3.2";
+          }
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            features = [ "extra_traits" ];
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "autocfg";
+            packageId = "autocfg";
+          }
+        ];
+        features = {
+          "aio" = [ "pin-utils" ];
+          "default" = [ "acct" "aio" "dir" "env" "event" "feature" "fs" "hostname" "inotify" "ioctl" "kmod" "mman" "mount" "mqueue" "net" "personality" "poll" "process" "pthread" "ptrace" "quota" "reboot" "resource" "sched" "signal" "socket" "term" "time" "ucontext" "uio" "user" "zerocopy" ];
+          "dir" = [ "fs" ];
+          "memoffset" = [ "dep:memoffset" ];
+          "mount" = [ "uio" ];
+          "mqueue" = [ "fs" ];
+          "net" = [ "socket" ];
+          "pin-utils" = [ "dep:pin-utils" ];
+          "ptrace" = [ "process" ];
+          "sched" = [ "process" ];
+          "signal" = [ "process" ];
+          "socket" = [ "memoffset" ];
+          "ucontext" = [ "signal" ];
+          "user" = [ "feature" ];
+          "zerocopy" = [ "fs" "uio" ];
+        };
+        resolvedDefaultFeatures = [ "fs" "ioctl" "poll" "process" "signal" "term" ];
+      };
+      "nix 0.26.4" = rec {
+        crateName = "nix";
+        version = "0.26.4";
+        edition = "2018";
+        sha256 = "06xgl4ybb8pvjrbmc3xggbgk3kbs1j0c4c0nzdfrmpbgrkrym2sr";
+        authors = [
+          "The nix-rust Project Developers"
+        ];
+        dependencies = [
+          {
+            name = "bitflags";
+            packageId = "bitflags 1.3.2";
+          }
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            features = [ "extra_traits" ];
+          }
+        ];
+        features = {
+          "aio" = [ "pin-utils" ];
+          "default" = [ "acct" "aio" "dir" "env" "event" "feature" "fs" "hostname" "inotify" "ioctl" "kmod" "mman" "mount" "mqueue" "net" "personality" "poll" "process" "pthread" "ptrace" "quota" "reboot" "resource" "sched" "signal" "socket" "term" "time" "ucontext" "uio" "user" "zerocopy" ];
+          "dir" = [ "fs" ];
+          "memoffset" = [ "dep:memoffset" ];
+          "mount" = [ "uio" ];
+          "mqueue" = [ "fs" ];
+          "net" = [ "socket" ];
+          "pin-utils" = [ "dep:pin-utils" ];
+          "ptrace" = [ "process" ];
+          "sched" = [ "process" ];
+          "signal" = [ "process" ];
+          "socket" = [ "memoffset" ];
+          "ucontext" = [ "signal" ];
+          "user" = [ "feature" ];
+          "zerocopy" = [ "fs" "uio" ];
+        };
+        resolvedDefaultFeatures = [ "feature" "fs" "user" ];
+      };
+      "nix 0.27.1" = rec {
+        crateName = "nix";
+        version = "0.27.1";
+        edition = "2021";
+        sha256 = "0ly0kkmij5f0sqz35lx9czlbk6zpihb7yh1bsy4irzwfd2f4xc1f";
+        authors = [
+          "The nix-rust Project Developers"
+        ];
+        dependencies = [
+          {
+            name = "bitflags";
+            packageId = "bitflags 2.4.2";
+          }
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            features = [ "extra_traits" ];
+          }
+        ];
+        features = {
+          "aio" = [ "pin-utils" ];
+          "dir" = [ "fs" ];
+          "memoffset" = [ "dep:memoffset" ];
+          "mount" = [ "uio" ];
+          "mqueue" = [ "fs" ];
+          "net" = [ "socket" ];
+          "pin-utils" = [ "dep:pin-utils" ];
+          "ptrace" = [ "process" ];
+          "sched" = [ "process" ];
+          "signal" = [ "process" ];
+          "socket" = [ "memoffset" ];
+          "ucontext" = [ "signal" ];
+          "user" = [ "feature" ];
+          "zerocopy" = [ "fs" "uio" ];
+        };
+        resolvedDefaultFeatures = [ "default" "fs" ];
+      };
+      "nix-compat" = rec {
+        crateName = "nix-compat";
+        version = "0.1.0";
+        edition = "2021";
+        crateBin = [
+          {
+            name = "drvfmt";
+            path = "src/bin/drvfmt.rs";
+            requiredFeatures = [ ];
+          }
+        ];
+        # We can't filter paths with references in Nix 2.4
+        # See https://github.com/NixOS/nix/issues/5410
+        src =
+          if ((lib.versionOlder builtins.nixVersion "2.4pre20211007") || (lib.versionOlder "2.5" builtins.nixVersion))
+          then lib.cleanSourceWith { filter = sourceFilter; src = ./nix-compat; }
+          else ./nix-compat;
+        dependencies = [
+          {
+            name = "bitflags";
+            packageId = "bitflags 2.4.2";
+          }
+          {
+            name = "bstr";
+            packageId = "bstr";
+            features = [ "alloc" "unicode" "serde" ];
+          }
+          {
+            name = "data-encoding";
+            packageId = "data-encoding";
+          }
+          {
+            name = "ed25519";
+            packageId = "ed25519";
+          }
+          {
+            name = "ed25519-dalek";
+            packageId = "ed25519-dalek";
+          }
+          {
+            name = "enum-primitive-derive";
+            packageId = "enum-primitive-derive";
+          }
+          {
+            name = "glob";
+            packageId = "glob";
+          }
+          {
+            name = "nom";
+            packageId = "nom";
+          }
+          {
+            name = "num-traits";
+            packageId = "num-traits";
+          }
+          {
+            name = "pin-project-lite";
+            packageId = "pin-project-lite";
+            optional = true;
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+            features = [ "derive" ];
+          }
+          {
+            name = "serde_json";
+            packageId = "serde_json";
+          }
+          {
+            name = "sha2";
+            packageId = "sha2";
+          }
+          {
+            name = "thiserror";
+            packageId = "thiserror";
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+            optional = true;
+            features = [ "io-util" "macros" ];
+          }
+        ];
+        devDependencies = [
+          {
+            name = "criterion";
+            packageId = "criterion";
+            features = [ "html_reports" ];
+          }
+          {
+            name = "futures";
+            packageId = "futures";
+            usesDefaultFeatures = false;
+            features = [ "executor" ];
+          }
+          {
+            name = "hex-literal";
+            packageId = "hex-literal";
+          }
+          {
+            name = "lazy_static";
+            packageId = "lazy_static";
+          }
+          {
+            name = "pretty_assertions";
+            packageId = "pretty_assertions";
+          }
+          {
+            name = "rstest";
+            packageId = "rstest";
+          }
+          {
+            name = "serde_json";
+            packageId = "serde_json";
+          }
+          {
+            name = "tokio-test";
+            packageId = "tokio-test";
+          }
+          {
+            name = "zstd";
+            packageId = "zstd";
+          }
+        ];
+        features = {
+          "async" = [ "tokio" ];
+          "default" = [ "async" "wire" ];
+          "pin-project-lite" = [ "dep:pin-project-lite" ];
+          "tokio" = [ "dep:tokio" ];
+          "wire" = [ "tokio" "pin-project-lite" ];
+        };
+        resolvedDefaultFeatures = [ "async" "default" "pin-project-lite" "tokio" "wire" ];
+      };
+      "nom" = rec {
+        crateName = "nom";
+        version = "7.1.3";
+        edition = "2018";
+        sha256 = "0jha9901wxam390jcf5pfa0qqfrgh8li787jx2ip0yk5b8y9hwyj";
+        authors = [
+          "contact@geoffroycouprie.com"
+        ];
+        dependencies = [
+          {
+            name = "memchr";
+            packageId = "memchr";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "minimal-lexical";
+            packageId = "minimal-lexical";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "std" = [ "alloc" "memchr/std" "minimal-lexical/std" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "std" ];
+      };
+      "nom8" = rec {
+        crateName = "nom8";
+        version = "0.2.0";
+        edition = "2018";
+        sha256 = "1y6jzabxyrl05vxnh63r66ac2fh0symg5fnynxm4ii3zkif580df";
+        dependencies = [
+          {
+            name = "memchr";
+            packageId = "memchr";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "std" = [ "alloc" "memchr/std" ];
+          "unstable-doc" = [ "alloc" "std" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "std" ];
+      };
+      "nu-ansi-term" = rec {
+        crateName = "nu-ansi-term";
+        version = "0.46.0";
+        edition = "2018";
+        sha256 = "115sywxh53p190lyw97alm14nc004qj5jm5lvdj608z84rbida3p";
+        authors = [
+          "ogham@bsago.me"
+          "Ryan Scheel (Havvy) <ryan.havvy@gmail.com>"
+          "Josh Triplett <josh@joshtriplett.org>"
+          "The Nushell Project Developers"
+        ];
+        dependencies = [
+          {
+            name = "overload";
+            packageId = "overload";
+          }
+          {
+            name = "winapi";
+            packageId = "winapi";
+            target = { target, features }: ("windows" == target."os" or null);
+            features = [ "consoleapi" "errhandlingapi" "fileapi" "handleapi" "processenv" ];
+          }
+        ];
+        features = {
+          "derive_serde_style" = [ "serde" ];
+          "serde" = [ "dep:serde" ];
+        };
+      };
+      "num-conv" = rec {
+        crateName = "num-conv";
+        version = "0.1.0";
+        edition = "2021";
+        sha256 = "1ndiyg82q73783jq18isi71a7mjh56wxrk52rlvyx0mi5z9ibmai";
+        authors = [
+          "Jacob Pratt <jacob@jhpratt.dev>"
+        ];
+
+      };
+      "num-traits" = rec {
+        crateName = "num-traits";
+        version = "0.2.18";
+        edition = "2018";
+        sha256 = "0yjib8p2p9kzmaz48xwhs69w5dh1wipph9jgnillzd2x33jz03fs";
+        authors = [
+          "The Rust Project Developers"
+        ];
+        dependencies = [
+          {
+            name = "libm";
+            packageId = "libm";
+            optional = true;
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "autocfg";
+            packageId = "autocfg";
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "libm" = [ "dep:libm" ];
+        };
+        resolvedDefaultFeatures = [ "default" "libm" "std" ];
+      };
+      "num_cpus" = rec {
+        crateName = "num_cpus";
+        version = "1.16.0";
+        edition = "2015";
+        sha256 = "0hra6ihpnh06dvfvz9ipscys0xfqa9ca9hzp384d5m02ssvgqqa1";
+        authors = [
+          "Sean McArthur <sean@seanmonstar.com>"
+        ];
+        dependencies = [
+          {
+            name = "hermit-abi";
+            packageId = "hermit-abi";
+            target = { target, features }: ("hermit" == target."os" or null);
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (!(target."windows" or false));
+          }
+        ];
+
+      };
+      "object" = rec {
+        crateName = "object";
+        version = "0.32.2";
+        edition = "2018";
+        sha256 = "0hc4cjwyngiy6k51hlzrlsxgv5z25vv7c2cp0ky1lckfic0259m6";
+        dependencies = [
+          {
+            name = "memchr";
+            packageId = "memchr";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "all" = [ "read" "write" "std" "compression" "wasm" ];
+          "alloc" = [ "dep:alloc" ];
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "compression" = [ "dep:flate2" "dep:ruzstd" "std" ];
+          "core" = [ "dep:core" ];
+          "default" = [ "read" "compression" ];
+          "doc" = [ "read_core" "write_std" "std" "compression" "archive" "coff" "elf" "macho" "pe" "wasm" "xcoff" ];
+          "pe" = [ "coff" ];
+          "read" = [ "read_core" "archive" "coff" "elf" "macho" "pe" "xcoff" "unaligned" ];
+          "rustc-dep-of-std" = [ "core" "compiler_builtins" "alloc" "memchr/rustc-dep-of-std" ];
+          "std" = [ "memchr/std" ];
+          "unstable-all" = [ "all" "unstable" ];
+          "wasm" = [ "dep:wasmparser" ];
+          "write" = [ "write_std" "coff" "elf" "macho" "pe" "xcoff" ];
+          "write_core" = [ "dep:crc32fast" "dep:indexmap" "dep:hashbrown" ];
+          "write_std" = [ "write_core" "std" "indexmap?/std" "crc32fast?/std" ];
+        };
+        resolvedDefaultFeatures = [ "archive" "coff" "elf" "macho" "pe" "read_core" "unaligned" ];
+      };
+      "object_store" = rec {
+        crateName = "object_store";
+        version = "0.9.1";
+        edition = "2021";
+        sha256 = "1cwx0xg57cp3z6xjgrqwp0gxgxsagls4h5cd212pmxpxcn5qywdq";
+        dependencies = [
+          {
+            name = "async-trait";
+            packageId = "async-trait";
+          }
+          {
+            name = "base64";
+            packageId = "base64";
+            optional = true;
+            usesDefaultFeatures = false;
+            features = [ "std" ];
+          }
+          {
+            name = "bytes";
+            packageId = "bytes";
+          }
+          {
+            name = "chrono";
+            packageId = "chrono";
+            usesDefaultFeatures = false;
+            features = [ "clock" ];
+          }
+          {
+            name = "futures";
+            packageId = "futures";
+          }
+          {
+            name = "humantime";
+            packageId = "humantime";
+          }
+          {
+            name = "hyper";
+            packageId = "hyper 0.14.28";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "itertools";
+            packageId = "itertools 0.12.0";
+          }
+          {
+            name = "md-5";
+            packageId = "md-5";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "parking_lot";
+            packageId = "parking_lot 0.12.1";
+          }
+          {
+            name = "percent-encoding";
+            packageId = "percent-encoding";
+          }
+          {
+            name = "quick-xml";
+            packageId = "quick-xml";
+            optional = true;
+            features = [ "serialize" "overlapped-lists" ];
+          }
+          {
+            name = "rand";
+            packageId = "rand";
+            optional = true;
+            usesDefaultFeatures = false;
+            features = [ "std" "std_rng" ];
+          }
+          {
+            name = "reqwest";
+            packageId = "reqwest";
+            optional = true;
+            usesDefaultFeatures = false;
+            features = [ "rustls-tls-native-roots" ];
+          }
+          {
+            name = "ring";
+            packageId = "ring";
+            optional = true;
+            usesDefaultFeatures = false;
+            features = [ "std" ];
+          }
+          {
+            name = "rustls-pemfile";
+            packageId = "rustls-pemfile 2.1.0";
+            optional = true;
+            usesDefaultFeatures = false;
+            features = [ "std" ];
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+            optional = true;
+            usesDefaultFeatures = false;
+            features = [ "derive" ];
+          }
+          {
+            name = "serde_json";
+            packageId = "serde_json";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "snafu";
+            packageId = "snafu";
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "sync" "macros" "rt" "time" "io-util" ];
+          }
+          {
+            name = "tracing";
+            packageId = "tracing";
+          }
+          {
+            name = "url";
+            packageId = "url";
+          }
+          {
+            name = "walkdir";
+            packageId = "walkdir";
+          }
+        ];
+        devDependencies = [
+          {
+            name = "hyper";
+            packageId = "hyper 0.14.28";
+            features = [ "server" ];
+          }
+          {
+            name = "rand";
+            packageId = "rand";
+          }
+        ];
+        features = {
+          "aws" = [ "cloud" "md-5" ];
+          "azure" = [ "cloud" ];
+          "base64" = [ "dep:base64" ];
+          "cloud" = [ "serde" "serde_json" "quick-xml" "hyper" "reqwest" "reqwest/json" "reqwest/stream" "chrono/serde" "base64" "rand" "ring" ];
+          "gcp" = [ "cloud" "rustls-pemfile" ];
+          "http" = [ "cloud" ];
+          "hyper" = [ "dep:hyper" ];
+          "md-5" = [ "dep:md-5" ];
+          "quick-xml" = [ "dep:quick-xml" ];
+          "rand" = [ "dep:rand" ];
+          "reqwest" = [ "dep:reqwest" ];
+          "ring" = [ "dep:ring" ];
+          "rustls-pemfile" = [ "dep:rustls-pemfile" ];
+          "serde" = [ "dep:serde" ];
+          "serde_json" = [ "dep:serde_json" ];
+          "tls-webpki-roots" = [ "reqwest?/rustls-tls-webpki-roots" ];
+        };
+        resolvedDefaultFeatures = [ "aws" "azure" "base64" "cloud" "gcp" "http" "hyper" "md-5" "quick-xml" "rand" "reqwest" "ring" "rustls-pemfile" "serde" "serde_json" ];
+      };
+      "once_cell" = rec {
+        crateName = "once_cell";
+        version = "1.19.0";
+        edition = "2021";
+        sha256 = "14kvw7px5z96dk4dwdm1r9cqhhy2cyj1l5n5b29mynbb8yr15nrz";
+        authors = [
+          "Aleksey Kladov <aleksey.kladov@gmail.com>"
+        ];
+        features = {
+          "alloc" = [ "race" ];
+          "atomic-polyfill" = [ "critical-section" ];
+          "critical-section" = [ "dep:critical-section" "portable-atomic" ];
+          "default" = [ "std" ];
+          "parking_lot" = [ "dep:parking_lot_core" ];
+          "portable-atomic" = [ "dep:portable-atomic" ];
+          "std" = [ "alloc" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "race" "std" ];
+      };
+      "oorandom" = rec {
+        crateName = "oorandom";
+        version = "11.1.3";
+        edition = "2018";
+        sha256 = "0xdm4vd89aiwnrk1xjwzklnchjqvib4klcihlc2bsd4x50mbrc8a";
+        authors = [
+          "Simon Heath <icefox@dreamquest.io>"
+        ];
+
+      };
+      "openssl-probe" = rec {
+        crateName = "openssl-probe";
+        version = "0.1.5";
+        edition = "2015";
+        sha256 = "1kq18qm48rvkwgcggfkqq6pm948190czqc94d6bm2sir5hq1l0gz";
+        authors = [
+          "Alex Crichton <alex@alexcrichton.com>"
+        ];
+
+      };
+      "opentelemetry" = rec {
+        crateName = "opentelemetry";
+        version = "0.21.0";
+        edition = "2021";
+        sha256 = "12jfmyx8k9q2sjlx4wp76ddzaf94i7lnkliv1c9mj164bnd36chy";
+        dependencies = [
+          {
+            name = "futures-core";
+            packageId = "futures-core";
+          }
+          {
+            name = "futures-sink";
+            packageId = "futures-sink";
+          }
+          {
+            name = "indexmap";
+            packageId = "indexmap 2.1.0";
+          }
+          {
+            name = "js-sys";
+            packageId = "js-sys";
+            target = { target, features }: (("wasm32" == target."arch" or null) && (!("wasi" == target."os" or null)));
+          }
+          {
+            name = "once_cell";
+            packageId = "once_cell";
+          }
+          {
+            name = "pin-project-lite";
+            packageId = "pin-project-lite";
+            optional = true;
+          }
+          {
+            name = "thiserror";
+            packageId = "thiserror";
+          }
+          {
+            name = "urlencoding";
+            packageId = "urlencoding";
+          }
+        ];
+        features = {
+          "default" = [ "trace" ];
+          "logs_level_enabled" = [ "logs" ];
+          "pin-project-lite" = [ "dep:pin-project-lite" ];
+          "testing" = [ "trace" "metrics" ];
+          "trace" = [ "pin-project-lite" ];
+        };
+        resolvedDefaultFeatures = [ "default" "metrics" "pin-project-lite" "trace" ];
+      };
+      "opentelemetry-otlp" = rec {
+        crateName = "opentelemetry-otlp";
+        version = "0.14.0";
+        edition = "2021";
+        sha256 = "0c59bh4wa824mf89ayivsjqwipkg1y6r27r4d0y47lhfna1xlk7j";
+        dependencies = [
+          {
+            name = "async-trait";
+            packageId = "async-trait";
+          }
+          {
+            name = "futures-core";
+            packageId = "futures-core";
+          }
+          {
+            name = "http";
+            packageId = "http 0.2.11";
+            optional = true;
+          }
+          {
+            name = "opentelemetry";
+            packageId = "opentelemetry";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "opentelemetry-proto";
+            packageId = "opentelemetry-proto";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "opentelemetry-semantic-conventions";
+            packageId = "opentelemetry-semantic-conventions";
+          }
+          {
+            name = "opentelemetry_sdk";
+            packageId = "opentelemetry_sdk";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "prost";
+            packageId = "prost 0.11.9";
+            optional = true;
+          }
+          {
+            name = "thiserror";
+            packageId = "thiserror";
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+            optional = true;
+            features = [ "sync" "rt" ];
+          }
+          {
+            name = "tonic";
+            packageId = "tonic 0.9.2";
+            optional = true;
+          }
+        ];
+        devDependencies = [
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "macros" "rt-multi-thread" ];
+          }
+        ];
+        features = {
+          "default" = [ "grpc-tonic" "trace" ];
+          "grpc-sys" = [ "grpcio" "opentelemetry-proto/gen-grpcio" ];
+          "grpc-tonic" = [ "tonic" "prost" "http" "tokio" "opentelemetry-proto/gen-tonic" ];
+          "grpcio" = [ "dep:grpcio" ];
+          "gzip-tonic" = [ "tonic/gzip" ];
+          "http" = [ "dep:http" ];
+          "http-proto" = [ "prost" "opentelemetry-http" "opentelemetry-proto/gen-tonic-messages" "http" "trace" "metrics" ];
+          "integration-testing" = [ "tonic" "prost" "tokio/full" "trace" ];
+          "logs" = [ "opentelemetry/logs" "opentelemetry_sdk/logs" "opentelemetry-proto/logs" ];
+          "metrics" = [ "opentelemetry/metrics" "opentelemetry_sdk/metrics" "opentelemetry-proto/metrics" ];
+          "openssl" = [ "grpcio/openssl" ];
+          "openssl-vendored" = [ "grpcio/openssl-vendored" ];
+          "opentelemetry-http" = [ "dep:opentelemetry-http" ];
+          "prost" = [ "dep:prost" ];
+          "reqwest" = [ "dep:reqwest" ];
+          "reqwest-blocking-client" = [ "reqwest/blocking" "opentelemetry-http/reqwest" ];
+          "reqwest-client" = [ "reqwest" "opentelemetry-http/reqwest" ];
+          "reqwest-rustls" = [ "reqwest" "reqwest/rustls-tls-native-roots" ];
+          "serde" = [ "dep:serde" ];
+          "serialize" = [ "serde" ];
+          "surf" = [ "dep:surf" ];
+          "surf-client" = [ "surf" "opentelemetry-http/surf" ];
+          "tls" = [ "tonic/tls" ];
+          "tls-roots" = [ "tls" "tonic/tls-roots" ];
+          "tokio" = [ "dep:tokio" ];
+          "tonic" = [ "dep:tonic" ];
+          "trace" = [ "opentelemetry/trace" "opentelemetry_sdk/trace" "opentelemetry-proto/trace" ];
+        };
+        resolvedDefaultFeatures = [ "default" "grpc-tonic" "http" "prost" "tokio" "tonic" "trace" ];
+      };
+      "opentelemetry-proto" = rec {
+        crateName = "opentelemetry-proto";
+        version = "0.4.0";
+        edition = "2021";
+        sha256 = "1qblsq0hkksdw3k60bc8yi5xwlynmqwibggz3lyyl4n8bk75bqd2";
+        dependencies = [
+          {
+            name = "opentelemetry";
+            packageId = "opentelemetry";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "opentelemetry_sdk";
+            packageId = "opentelemetry_sdk";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "prost";
+            packageId = "prost 0.11.9";
+            optional = true;
+          }
+          {
+            name = "tonic";
+            packageId = "tonic 0.9.2";
+            optional = true;
+            usesDefaultFeatures = false;
+            features = [ "codegen" "prost" ];
+          }
+        ];
+        features = {
+          "full" = [ "gen-tonic" "gen-grpcio" "trace" "logs" "metrics" "zpages" "with-serde" ];
+          "gen-grpcio" = [ "grpcio" "prost" ];
+          "gen-tonic" = [ "gen-tonic-messages" "tonic/transport" ];
+          "gen-tonic-messages" = [ "tonic" "prost" ];
+          "grpcio" = [ "dep:grpcio" ];
+          "logs" = [ "opentelemetry/logs" "opentelemetry_sdk/logs" ];
+          "metrics" = [ "opentelemetry/metrics" "opentelemetry_sdk/metrics" ];
+          "prost" = [ "dep:prost" ];
+          "serde" = [ "dep:serde" ];
+          "tonic" = [ "dep:tonic" ];
+          "trace" = [ "opentelemetry/trace" "opentelemetry_sdk/trace" ];
+          "with-serde" = [ "serde" ];
+          "zpages" = [ "trace" ];
+        };
+        resolvedDefaultFeatures = [ "gen-tonic" "gen-tonic-messages" "prost" "tonic" "trace" ];
+      };
+      "opentelemetry-semantic-conventions" = rec {
+        crateName = "opentelemetry-semantic-conventions";
+        version = "0.13.0";
+        edition = "2021";
+        sha256 = "115wbgk840dklyhpg3lwp4x1m643qd7f0vkz8hmfz0pry4g4yxzm";
+        dependencies = [
+          {
+            name = "opentelemetry";
+            packageId = "opentelemetry";
+            usesDefaultFeatures = false;
+          }
+        ];
+
+      };
+      "opentelemetry_sdk" = rec {
+        crateName = "opentelemetry_sdk";
+        version = "0.21.2";
+        edition = "2021";
+        sha256 = "1r7gw2j2n800rd0vdnga32yhlfmc3c4y0sadcr97licam74aw5ig";
+        dependencies = [
+          {
+            name = "async-trait";
+            packageId = "async-trait";
+            optional = true;
+          }
+          {
+            name = "crossbeam-channel";
+            packageId = "crossbeam-channel";
+            optional = true;
+          }
+          {
+            name = "futures-channel";
+            packageId = "futures-channel";
+          }
+          {
+            name = "futures-executor";
+            packageId = "futures-executor";
+          }
+          {
+            name = "futures-util";
+            packageId = "futures-util";
+            usesDefaultFeatures = false;
+            features = [ "std" "sink" "async-await-macro" ];
+          }
+          {
+            name = "glob";
+            packageId = "glob";
+            optional = true;
+          }
+          {
+            name = "once_cell";
+            packageId = "once_cell";
+          }
+          {
+            name = "opentelemetry";
+            packageId = "opentelemetry";
+          }
+          {
+            name = "ordered-float";
+            packageId = "ordered-float";
+          }
+          {
+            name = "percent-encoding";
+            packageId = "percent-encoding";
+            optional = true;
+          }
+          {
+            name = "rand";
+            packageId = "rand";
+            optional = true;
+            usesDefaultFeatures = false;
+            features = [ "std" "std_rng" ];
+          }
+          {
+            name = "thiserror";
+            packageId = "thiserror";
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+            optional = true;
+            usesDefaultFeatures = false;
+            features = [ "rt" "time" ];
+          }
+          {
+            name = "tokio-stream";
+            packageId = "tokio-stream";
+            optional = true;
+          }
+        ];
+        features = {
+          "async-std" = [ "dep:async-std" ];
+          "async-trait" = [ "dep:async-trait" ];
+          "crossbeam-channel" = [ "dep:crossbeam-channel" ];
+          "default" = [ "trace" ];
+          "glob" = [ "dep:glob" ];
+          "http" = [ "dep:http" ];
+          "jaeger_remote_sampler" = [ "trace" "opentelemetry-http" "http" "serde" "serde_json" "url" ];
+          "logs" = [ "opentelemetry/logs" "crossbeam-channel" "async-trait" "serde_json" ];
+          "logs_level_enabled" = [ "logs" "opentelemetry/logs_level_enabled" ];
+          "metrics" = [ "opentelemetry/metrics" "glob" "async-trait" ];
+          "opentelemetry-http" = [ "dep:opentelemetry-http" ];
+          "percent-encoding" = [ "dep:percent-encoding" ];
+          "rand" = [ "dep:rand" ];
+          "rt-async-std" = [ "async-std" ];
+          "rt-tokio" = [ "tokio" "tokio-stream" ];
+          "rt-tokio-current-thread" = [ "tokio" "tokio-stream" ];
+          "serde" = [ "dep:serde" ];
+          "serde_json" = [ "dep:serde_json" ];
+          "testing" = [ "opentelemetry/testing" "trace" "metrics" "logs" "rt-async-std" "rt-tokio" "rt-tokio-current-thread" "tokio/macros" "tokio/rt-multi-thread" ];
+          "tokio" = [ "dep:tokio" ];
+          "tokio-stream" = [ "dep:tokio-stream" ];
+          "trace" = [ "opentelemetry/trace" "crossbeam-channel" "rand" "async-trait" "percent-encoding" ];
+          "url" = [ "dep:url" ];
+        };
+        resolvedDefaultFeatures = [ "async-trait" "crossbeam-channel" "default" "glob" "metrics" "percent-encoding" "rand" "rt-tokio" "tokio" "tokio-stream" "trace" ];
+      };
+      "ordered-float" = rec {
+        crateName = "ordered-float";
+        version = "4.2.0";
+        edition = "2021";
+        sha256 = "0kjqcvvbcsibbx3hnj7ag06bd9gv2zfi5ja6rgyh2kbxbh3zfvd7";
+        authors = [
+          "Jonathan Reem <jonathan.reem@gmail.com>"
+          "Matt Brubeck <mbrubeck@limpet.net>"
+        ];
+        dependencies = [
+          {
+            name = "num-traits";
+            packageId = "num-traits";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "arbitrary" = [ "dep:arbitrary" ];
+          "borsh" = [ "dep:borsh" ];
+          "bytemuck" = [ "dep:bytemuck" ];
+          "default" = [ "std" ];
+          "proptest" = [ "dep:proptest" ];
+          "rand" = [ "dep:rand" ];
+          "randtest" = [ "rand/std" "rand/std_rng" ];
+          "rkyv" = [ "rkyv_32" ];
+          "rkyv_16" = [ "dep:rkyv" "rkyv?/size_16" ];
+          "rkyv_32" = [ "dep:rkyv" "rkyv?/size_32" ];
+          "rkyv_64" = [ "dep:rkyv" "rkyv?/size_64" ];
+          "rkyv_ck" = [ "rkyv?/validation" ];
+          "schemars" = [ "dep:schemars" ];
+          "serde" = [ "dep:serde" "rand?/serde1" ];
+          "speedy" = [ "dep:speedy" ];
+          "std" = [ "num-traits/std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "os_str_bytes" = rec {
+        crateName = "os_str_bytes";
+        version = "6.6.1";
+        edition = "2021";
+        sha256 = "1885z1x4sm86v5p41ggrl49m58rbzzhd1kj72x46yy53p62msdg2";
+        authors = [
+          "dylni"
+        ];
+        dependencies = [
+          {
+            name = "memchr";
+            packageId = "memchr";
+            optional = true;
+          }
+        ];
+        features = {
+          "checked_conversions" = [ "conversions" ];
+          "default" = [ "memchr" "raw_os_str" ];
+          "memchr" = [ "dep:memchr" ];
+          "print_bytes" = [ "dep:print_bytes" ];
+          "uniquote" = [ "dep:uniquote" ];
+        };
+        resolvedDefaultFeatures = [ "conversions" "default" "memchr" "raw_os_str" ];
+      };
+      "overload" = rec {
+        crateName = "overload";
+        version = "0.1.1";
+        edition = "2018";
+        sha256 = "0fdgbaqwknillagy1xq7xfgv60qdbk010diwl7s1p0qx7hb16n5i";
+        authors = [
+          "Daniel Salvadori <danaugrs@gmail.com>"
+        ];
+
+      };
+      "parking" = rec {
+        crateName = "parking";
+        version = "2.2.0";
+        edition = "2018";
+        sha256 = "1blwbkq6im1hfxp5wlbr475mw98rsyc0bbr2d5n16m38z253p0dv";
+        authors = [
+          "Stjepan Glavina <stjepang@gmail.com>"
+          "The Rust Project Developers"
+        ];
+        features = {
+          "loom" = [ "dep:loom" ];
+        };
+      };
+      "parking_lot 0.11.2" = rec {
+        crateName = "parking_lot";
+        version = "0.11.2";
+        edition = "2018";
+        sha256 = "16gzf41bxmm10x82bla8d6wfppy9ym3fxsmdjyvn61m66s0bf5vx";
+        authors = [
+          "Amanieu d'Antras <amanieu@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "instant";
+            packageId = "instant";
+          }
+          {
+            name = "lock_api";
+            packageId = "lock_api";
+          }
+          {
+            name = "parking_lot_core";
+            packageId = "parking_lot_core 0.8.6";
+          }
+        ];
+        features = {
+          "arc_lock" = [ "lock_api/arc_lock" ];
+          "deadlock_detection" = [ "parking_lot_core/deadlock_detection" ];
+          "nightly" = [ "parking_lot_core/nightly" "lock_api/nightly" ];
+          "owning_ref" = [ "lock_api/owning_ref" ];
+          "serde" = [ "lock_api/serde" ];
+          "stdweb" = [ "instant/stdweb" ];
+          "wasm-bindgen" = [ "instant/wasm-bindgen" ];
+        };
+        resolvedDefaultFeatures = [ "default" ];
+      };
+      "parking_lot 0.12.1" = rec {
+        crateName = "parking_lot";
+        version = "0.12.1";
+        edition = "2018";
+        sha256 = "13r2xk7mnxfc5g0g6dkdxqdqad99j7s7z8zhzz4npw5r0g0v4hip";
+        authors = [
+          "Amanieu d'Antras <amanieu@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "lock_api";
+            packageId = "lock_api";
+          }
+          {
+            name = "parking_lot_core";
+            packageId = "parking_lot_core 0.9.9";
+          }
+        ];
+        features = {
+          "arc_lock" = [ "lock_api/arc_lock" ];
+          "deadlock_detection" = [ "parking_lot_core/deadlock_detection" ];
+          "nightly" = [ "parking_lot_core/nightly" "lock_api/nightly" ];
+          "owning_ref" = [ "lock_api/owning_ref" ];
+          "serde" = [ "lock_api/serde" ];
+        };
+        resolvedDefaultFeatures = [ "default" ];
+      };
+      "parking_lot_core 0.8.6" = rec {
+        crateName = "parking_lot_core";
+        version = "0.8.6";
+        edition = "2018";
+        sha256 = "1p2nfcbr0b9lm9rglgm28k6mwyjwgm4knipsmqbgqaxdy3kcz8k0";
+        authors = [
+          "Amanieu d'Antras <amanieu@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "instant";
+            packageId = "instant";
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (target."unix" or false);
+          }
+          {
+            name = "redox_syscall";
+            packageId = "redox_syscall 0.2.16";
+            target = { target, features }: ("redox" == target."os" or null);
+          }
+          {
+            name = "smallvec";
+            packageId = "smallvec";
+          }
+          {
+            name = "winapi";
+            packageId = "winapi";
+            target = { target, features }: (target."windows" or false);
+            features = [ "winnt" "ntstatus" "minwindef" "winerror" "winbase" "errhandlingapi" "handleapi" ];
+          }
+        ];
+        features = {
+          "backtrace" = [ "dep:backtrace" ];
+          "deadlock_detection" = [ "petgraph" "thread-id" "backtrace" ];
+          "petgraph" = [ "dep:petgraph" ];
+          "thread-id" = [ "dep:thread-id" ];
+        };
+      };
+      "parking_lot_core 0.9.9" = rec {
+        crateName = "parking_lot_core";
+        version = "0.9.9";
+        edition = "2018";
+        sha256 = "13h0imw1aq86wj28gxkblhkzx6z1gk8q18n0v76qmmj6cliajhjc";
+        authors = [
+          "Amanieu d'Antras <amanieu@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (target."unix" or false);
+          }
+          {
+            name = "redox_syscall";
+            packageId = "redox_syscall 0.4.1";
+            target = { target, features }: ("redox" == target."os" or null);
+          }
+          {
+            name = "smallvec";
+            packageId = "smallvec";
+          }
+          {
+            name = "windows-targets";
+            packageId = "windows-targets 0.48.5";
+            target = { target, features }: (target."windows" or false);
+          }
+        ];
+        features = {
+          "backtrace" = [ "dep:backtrace" ];
+          "deadlock_detection" = [ "petgraph" "thread-id" "backtrace" ];
+          "petgraph" = [ "dep:petgraph" ];
+          "thread-id" = [ "dep:thread-id" ];
+        };
+      };
+      "path-clean" = rec {
+        crateName = "path-clean";
+        version = "0.1.0";
+        edition = "2015";
+        sha256 = "1pcgqxw0mgg3ha5hi5xkjhyjf488bw5rw1g3qlr9awbq4szh3fpc";
+        authors = [
+          "Dan Reeves <hey@danreev.es>"
+        ];
+
+      };
+      "percent-encoding" = rec {
+        crateName = "percent-encoding";
+        version = "2.3.1";
+        edition = "2018";
+        sha256 = "0gi8wgx0dcy8rnv1kywdv98lwcx67hz0a0zwpib5v2i08r88y573";
+        authors = [
+          "The rust-url developers"
+        ];
+        features = {
+          "default" = [ "std" ];
+          "std" = [ "alloc" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "std" ];
+      };
+      "petgraph" = rec {
+        crateName = "petgraph";
+        version = "0.6.4";
+        edition = "2018";
+        sha256 = "1ac6wfq5f5pzcv0nvzzfgjbwg2kwslpnzsw5wcmxlscfcb9azlz1";
+        authors = [
+          "bluss"
+          "mitchmindtree"
+        ];
+        dependencies = [
+          {
+            name = "fixedbitset";
+            packageId = "fixedbitset";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "indexmap";
+            packageId = "indexmap 2.1.0";
+          }
+        ];
+        features = {
+          "all" = [ "unstable" "quickcheck" "matrix_graph" "stable_graph" "graphmap" ];
+          "default" = [ "graphmap" "stable_graph" "matrix_graph" ];
+          "quickcheck" = [ "dep:quickcheck" ];
+          "serde" = [ "dep:serde" ];
+          "serde-1" = [ "serde" "serde_derive" ];
+          "serde_derive" = [ "dep:serde_derive" ];
+          "unstable" = [ "generate" ];
+        };
+        resolvedDefaultFeatures = [ "default" "graphmap" "matrix_graph" "stable_graph" ];
+      };
+      "pin-project" = rec {
+        crateName = "pin-project";
+        version = "1.1.3";
+        edition = "2021";
+        sha256 = "08k4cpy8q3j93qqgnrbzkcgpn7g0a88l4a9nm33kyghpdhffv97x";
+        dependencies = [
+          {
+            name = "pin-project-internal";
+            packageId = "pin-project-internal";
+          }
+        ];
+
+      };
+      "pin-project-internal" = rec {
+        crateName = "pin-project-internal";
+        version = "1.1.3";
+        edition = "2021";
+        sha256 = "01a4l3vb84brv9v7wl71chzxra2kynm6yvcjca66xv3ij6fgsna3";
+        procMacro = true;
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn 2.0.48";
+            features = [ "full" "visit-mut" ];
+          }
+        ];
+
+      };
+      "pin-project-lite" = rec {
+        crateName = "pin-project-lite";
+        version = "0.2.13";
+        edition = "2018";
+        sha256 = "0n0bwr5qxlf0mhn2xkl36sy55118s9qmvx2yl5f3ixkb007lbywa";
+
+      };
+      "pin-utils" = rec {
+        crateName = "pin-utils";
+        version = "0.1.0";
+        edition = "2018";
+        sha256 = "117ir7vslsl2z1a7qzhws4pd01cg2d3338c47swjyvqv2n60v1wb";
+        authors = [
+          "Josef Brandl <mail@josefbrandl.de>"
+        ];
+
+      };
+      "piper" = rec {
+        crateName = "piper";
+        version = "0.2.1";
+        edition = "2018";
+        sha256 = "1m45fkdq7q5l9mv3b0ra10qwm0kb67rjp2q8y91958gbqjqk33b6";
+        authors = [
+          "Stjepan Glavina <stjepang@gmail.com>"
+          "John Nunley <dev@notgull.net>"
+        ];
+        dependencies = [
+          {
+            name = "atomic-waker";
+            packageId = "atomic-waker";
+          }
+          {
+            name = "fastrand";
+            packageId = "fastrand";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-io";
+            packageId = "futures-io";
+            optional = true;
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "futures-io" = [ "dep:futures-io" ];
+          "portable-atomic" = [ "atomic-waker/portable-atomic" "portable_atomic_crate" "portable-atomic-util" ];
+          "portable-atomic-util" = [ "dep:portable-atomic-util" ];
+          "portable_atomic_crate" = [ "dep:portable_atomic_crate" ];
+          "std" = [ "fastrand/std" "futures-io" ];
+        };
+        resolvedDefaultFeatures = [ "default" "futures-io" "std" ];
+      };
+      "pkcs8" = rec {
+        crateName = "pkcs8";
+        version = "0.10.2";
+        edition = "2021";
+        sha256 = "1dx7w21gvn07azszgqd3ryjhyphsrjrmq5mmz1fbxkj5g0vv4l7r";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        dependencies = [
+          {
+            name = "der";
+            packageId = "der";
+            features = [ "oid" ];
+          }
+          {
+            name = "spki";
+            packageId = "spki";
+          }
+        ];
+        features = {
+          "3des" = [ "encryption" "pkcs5/3des" ];
+          "alloc" = [ "der/alloc" "der/zeroize" "spki/alloc" ];
+          "des-insecure" = [ "encryption" "pkcs5/des-insecure" ];
+          "encryption" = [ "alloc" "pkcs5/alloc" "pkcs5/pbes2" "rand_core" ];
+          "getrandom" = [ "rand_core/getrandom" ];
+          "pem" = [ "alloc" "der/pem" "spki/pem" ];
+          "pkcs5" = [ "dep:pkcs5" ];
+          "rand_core" = [ "dep:rand_core" ];
+          "sha1-insecure" = [ "encryption" "pkcs5/sha1-insecure" ];
+          "std" = [ "alloc" "der/std" "spki/std" ];
+          "subtle" = [ "dep:subtle" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "std" ];
+      };
+      "pkg-config" = rec {
+        crateName = "pkg-config";
+        version = "0.3.29";
+        edition = "2015";
+        sha256 = "1jy6158v1316khkpmq2sjj1vgbnbnw51wffx7p0k0l9h9vlys019";
+        authors = [
+          "Alex Crichton <alex@alexcrichton.com>"
+        ];
+
+      };
+      "platforms" = rec {
+        crateName = "platforms";
+        version = "3.3.0";
+        edition = "2018";
+        sha256 = "0k7q6pigmnvgpfasvssb12m2pv3pc94zrhrfg9by3h3wmhyfqvb2";
+        authors = [
+          "Tony Arcieri <bascule@gmail.com>"
+          "Sergey \"Shnatsel\" Davidoff <shnatsel@gmail.com>"
+        ];
+        features = {
+          "default" = [ "std" ];
+          "serde" = [ "dep:serde" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "plotters" = rec {
+        crateName = "plotters";
+        version = "0.3.5";
+        edition = "2018";
+        sha256 = "0igxq58bx96gz58pqls6g3h80plf17rfl3b6bi6xvjnp02x29hnj";
+        authors = [
+          "Hao Hou <haohou302@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "num-traits";
+            packageId = "num-traits";
+          }
+          {
+            name = "plotters-backend";
+            packageId = "plotters-backend";
+          }
+          {
+            name = "plotters-svg";
+            packageId = "plotters-svg";
+            optional = true;
+          }
+          {
+            name = "wasm-bindgen";
+            packageId = "wasm-bindgen";
+            target = { target, features }: (("wasm32" == target."arch" or null) && (!("wasi" == target."os" or null)));
+          }
+          {
+            name = "web-sys";
+            packageId = "web-sys";
+            target = { target, features }: (("wasm32" == target."arch" or null) && (!("wasi" == target."os" or null)));
+            features = [ "Document" "DomRect" "Element" "HtmlElement" "Node" "Window" "HtmlCanvasElement" "CanvasRenderingContext2d" ];
+          }
+        ];
+        features = {
+          "ab_glyph" = [ "dep:ab_glyph" "once_cell" ];
+          "all_elements" = [ "errorbar" "candlestick" "boxplot" "histogram" ];
+          "all_series" = [ "area_series" "line_series" "point_series" "surface_series" ];
+          "bitmap_backend" = [ "plotters-bitmap" ];
+          "bitmap_encoder" = [ "plotters-bitmap/image_encoder" ];
+          "bitmap_gif" = [ "plotters-bitmap/gif_backend" ];
+          "chrono" = [ "dep:chrono" ];
+          "datetime" = [ "chrono" ];
+          "default" = [ "bitmap_backend" "bitmap_encoder" "bitmap_gif" "svg_backend" "chrono" "ttf" "image" "deprecated_items" "all_series" "all_elements" "full_palette" "colormaps" ];
+          "evcxr" = [ "svg_backend" ];
+          "evcxr_bitmap" = [ "evcxr" "bitmap_backend" "plotters-svg/bitmap_encoder" ];
+          "font-kit" = [ "dep:font-kit" ];
+          "fontconfig-dlopen" = [ "font-kit/source-fontconfig-dlopen" ];
+          "image" = [ "dep:image" ];
+          "lazy_static" = [ "dep:lazy_static" ];
+          "once_cell" = [ "dep:once_cell" ];
+          "pathfinder_geometry" = [ "dep:pathfinder_geometry" ];
+          "plotters-bitmap" = [ "dep:plotters-bitmap" ];
+          "plotters-svg" = [ "dep:plotters-svg" ];
+          "svg_backend" = [ "plotters-svg" ];
+          "ttf" = [ "font-kit" "ttf-parser" "lazy_static" "pathfinder_geometry" ];
+          "ttf-parser" = [ "dep:ttf-parser" ];
+        };
+        resolvedDefaultFeatures = [ "area_series" "line_series" "plotters-svg" "svg_backend" ];
+      };
+      "plotters-backend" = rec {
+        crateName = "plotters-backend";
+        version = "0.3.5";
+        edition = "2018";
+        sha256 = "02cn98gsj2i1bwrfsymifmyas1wn2gibdm9mk8w82x9s9n5n4xly";
+        authors = [
+          "Hao Hou <haohou302@gmail.com>"
+        ];
+
+      };
+      "plotters-svg" = rec {
+        crateName = "plotters-svg";
+        version = "0.3.5";
+        edition = "2018";
+        sha256 = "1axbw82frs5di4drbyzihr5j35wpy2a75hp3f49p186cjfcd7xiq";
+        authors = [
+          "Hao Hou <haohou302@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "plotters-backend";
+            packageId = "plotters-backend";
+          }
+        ];
+        features = {
+          "bitmap_encoder" = [ "image" ];
+          "image" = [ "dep:image" ];
+        };
+      };
+      "polling" = rec {
+        crateName = "polling";
+        version = "3.4.0";
+        edition = "2021";
+        sha256 = "052am20b5r03nwhpnjw86rv3dwsdabvb07anv3fqxfbs65r4w19h";
+        authors = [
+          "Stjepan Glavina <stjepang@gmail.com>"
+          "John Nunley <dev@notgull.net>"
+        ];
+        dependencies = [
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "concurrent-queue";
+            packageId = "concurrent-queue";
+            target = { target, features }: (target."windows" or false);
+          }
+          {
+            name = "pin-project-lite";
+            packageId = "pin-project-lite";
+            target = { target, features }: (target."windows" or false);
+          }
+          {
+            name = "rustix";
+            packageId = "rustix";
+            usesDefaultFeatures = false;
+            target = { target, features }: ((target."unix" or false) || ("fuchsia" == target."os" or null) || ("vxworks" == target."os" or null));
+            features = [ "event" "fs" "pipe" "process" "std" "time" ];
+          }
+          {
+            name = "tracing";
+            packageId = "tracing";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "windows-sys";
+            packageId = "windows-sys 0.52.0";
+            target = { target, features }: (target."windows" or false);
+            features = [ "Wdk_Foundation" "Wdk_Storage_FileSystem" "Win32_Foundation" "Win32_Networking_WinSock" "Win32_Security" "Win32_Storage_FileSystem" "Win32_System_IO" "Win32_System_LibraryLoader" "Win32_System_Threading" "Win32_System_WindowsProgramming" ];
+          }
+        ];
+
+      };
+      "powerfmt" = rec {
+        crateName = "powerfmt";
+        version = "0.2.0";
+        edition = "2021";
+        sha256 = "14ckj2xdpkhv3h6l5sdmb9f1d57z8hbfpdldjc2vl5givq2y77j3";
+        authors = [
+          "Jacob Pratt <jacob@jhpratt.dev>"
+        ];
+        features = {
+          "default" = [ "std" "macros" ];
+          "macros" = [ "dep:powerfmt-macros" ];
+          "std" = [ "alloc" ];
+        };
+      };
+      "ppv-lite86" = rec {
+        crateName = "ppv-lite86";
+        version = "0.2.17";
+        edition = "2018";
+        sha256 = "1pp6g52aw970adv3x2310n7glqnji96z0a9wiamzw89ibf0ayh2v";
+        authors = [
+          "The CryptoCorrosion Contributors"
+        ];
+        features = {
+          "default" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "simd" "std" ];
+      };
+      "pretty_assertions" = rec {
+        crateName = "pretty_assertions";
+        version = "1.4.0";
+        edition = "2018";
+        sha256 = "0rmsnqlpmpfjp5gyi31xgc48kdhc1kqn246bnc494nwadhdfwz5g";
+        authors = [
+          "Colin Kiegel <kiegel@gmx.de>"
+          "Florent Fayolle <florent.fayolle69@gmail.com>"
+          "Tom Milligan <code@tommilligan.net>"
+        ];
+        dependencies = [
+          {
+            name = "diff";
+            packageId = "diff";
+          }
+          {
+            name = "yansi";
+            packageId = "yansi";
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "prettyplease" = rec {
+        crateName = "prettyplease";
+        version = "0.2.16";
+        edition = "2021";
+        links = "prettyplease02";
+        sha256 = "1dfbq98rkq86l9g8w1l81bdvrz4spcfl48929n0pyz79clhzc754";
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "syn";
+            packageId = "syn 2.0.48";
+            usesDefaultFeatures = false;
+            features = [ "full" ];
+          }
+        ];
+        devDependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "syn";
+            packageId = "syn 2.0.48";
+            usesDefaultFeatures = false;
+            features = [ "parsing" ];
+          }
+        ];
+        features = {
+          "verbatim" = [ "syn/parsing" ];
+        };
+      };
+      "proc-macro2" = rec {
+        crateName = "proc-macro2";
+        version = "1.0.76";
+        edition = "2021";
+        sha256 = "136cp0fgl6rg5ljm3b1xpc0bn0lyvagzzmxvbxgk5hxml36mdz4m";
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+          "Alex Crichton <alex@alexcrichton.com>"
+        ];
+        dependencies = [
+          {
+            name = "unicode-ident";
+            packageId = "unicode-ident";
+          }
+        ];
+        features = {
+          "default" = [ "proc-macro" ];
+        };
+        resolvedDefaultFeatures = [ "default" "proc-macro" ];
+      };
+      "proptest" = rec {
+        crateName = "proptest";
+        version = "1.4.0";
+        edition = "2018";
+        sha256 = "1gzmw40pgmwzb7x6jsyr88z5w151snv5rp1g0dlcp1iw3h9pdd1i";
+        authors = [
+          "Jason Lingle"
+        ];
+        dependencies = [
+          {
+            name = "bit-set";
+            packageId = "bit-set";
+            optional = true;
+          }
+          {
+            name = "bit-vec";
+            packageId = "bit-vec";
+            optional = true;
+          }
+          {
+            name = "bitflags";
+            packageId = "bitflags 2.4.2";
+          }
+          {
+            name = "lazy_static";
+            packageId = "lazy_static";
+            optional = true;
+          }
+          {
+            name = "num-traits";
+            packageId = "num-traits";
+            usesDefaultFeatures = false;
+            features = [ "libm" ];
+          }
+          {
+            name = "rand";
+            packageId = "rand";
+            usesDefaultFeatures = false;
+            features = [ "alloc" ];
+          }
+          {
+            name = "rand_chacha";
+            packageId = "rand_chacha";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "rand_xorshift";
+            packageId = "rand_xorshift";
+          }
+          {
+            name = "regex-syntax";
+            packageId = "regex-syntax 0.8.2";
+            optional = true;
+          }
+          {
+            name = "rusty-fork";
+            packageId = "rusty-fork";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "tempfile";
+            packageId = "tempfile";
+            optional = true;
+          }
+          {
+            name = "unarray";
+            packageId = "unarray";
+          }
+        ];
+        features = {
+          "bit-set" = [ "dep:bit-set" "dep:bit-vec" ];
+          "default" = [ "std" "fork" "timeout" "bit-set" ];
+          "default-code-coverage" = [ "std" "fork" "timeout" "bit-set" ];
+          "fork" = [ "std" "rusty-fork" "tempfile" ];
+          "hardware-rng" = [ "x86" ];
+          "lazy_static" = [ "dep:lazy_static" ];
+          "regex-syntax" = [ "dep:regex-syntax" ];
+          "rusty-fork" = [ "dep:rusty-fork" ];
+          "std" = [ "rand/std" "lazy_static" "regex-syntax" "num-traits/std" ];
+          "tempfile" = [ "dep:tempfile" ];
+          "timeout" = [ "fork" "rusty-fork/timeout" ];
+          "x86" = [ "dep:x86" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "bit-set" "default" "fork" "lazy_static" "regex-syntax" "rusty-fork" "std" "tempfile" "timeout" ];
+      };
+      "prost 0.11.9" = rec {
+        crateName = "prost";
+        version = "0.11.9";
+        edition = "2021";
+        sha256 = "1kc1hva2h894hc0zf6r4r8fsxfpazf7xn5rj3jya9sbrsyhym0hb";
+        authors = [
+          "Dan Burkert <dan@danburkert.com>"
+          "Lucio Franco <luciofranco14@gmail.com"
+          "Tokio Contributors <team@tokio.rs>"
+        ];
+        dependencies = [
+          {
+            name = "bytes";
+            packageId = "bytes";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "prost-derive";
+            packageId = "prost-derive 0.11.9";
+            optional = true;
+          }
+        ];
+        features = {
+          "default" = [ "prost-derive" "std" ];
+          "prost-derive" = [ "dep:prost-derive" ];
+        };
+        resolvedDefaultFeatures = [ "default" "prost-derive" "std" ];
+      };
+      "prost 0.12.3" = rec {
+        crateName = "prost";
+        version = "0.12.3";
+        edition = "2021";
+        sha256 = "0jmrhlb4jkiylz72xb14vlkfbmlq0jwv7j20ini9harhvaf2hv0l";
+        authors = [
+          "Dan Burkert <dan@danburkert.com>"
+          "Lucio Franco <luciofranco14@gmail.com"
+          "Tokio Contributors <team@tokio.rs>"
+        ];
+        dependencies = [
+          {
+            name = "bytes";
+            packageId = "bytes";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "prost-derive";
+            packageId = "prost-derive 0.12.3";
+            optional = true;
+          }
+        ];
+        features = {
+          "default" = [ "prost-derive" "std" ];
+          "prost-derive" = [ "dep:prost-derive" ];
+        };
+        resolvedDefaultFeatures = [ "default" "prost-derive" "std" ];
+      };
+      "prost-build" = rec {
+        crateName = "prost-build";
+        version = "0.12.3";
+        edition = "2021";
+        sha256 = "1lp2l1l65l163yggk9nw5mjb2fqwzz12693af5phn1v0abih4pn5";
+        authors = [
+          "Dan Burkert <dan@danburkert.com>"
+          "Lucio Franco <luciofranco14@gmail.com>"
+          "Tokio Contributors <team@tokio.rs>"
+        ];
+        dependencies = [
+          {
+            name = "bytes";
+            packageId = "bytes";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "heck";
+            packageId = "heck";
+          }
+          {
+            name = "itertools";
+            packageId = "itertools 0.11.0";
+            usesDefaultFeatures = false;
+            features = [ "use_alloc" ];
+          }
+          {
+            name = "log";
+            packageId = "log";
+          }
+          {
+            name = "multimap";
+            packageId = "multimap";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "once_cell";
+            packageId = "once_cell";
+          }
+          {
+            name = "petgraph";
+            packageId = "petgraph";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "prettyplease";
+            packageId = "prettyplease";
+            optional = true;
+          }
+          {
+            name = "prost";
+            packageId = "prost 0.12.3";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "prost-types";
+            packageId = "prost-types";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "pulldown-cmark";
+            packageId = "pulldown-cmark";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "pulldown-cmark-to-cmark";
+            packageId = "pulldown-cmark-to-cmark";
+            optional = true;
+          }
+          {
+            name = "regex";
+            packageId = "regex";
+            usesDefaultFeatures = false;
+            features = [ "std" "unicode-bool" ];
+          }
+          {
+            name = "syn";
+            packageId = "syn 2.0.48";
+            optional = true;
+            features = [ "full" ];
+          }
+          {
+            name = "tempfile";
+            packageId = "tempfile";
+          }
+          {
+            name = "which";
+            packageId = "which 4.4.2";
+          }
+        ];
+        features = {
+          "cleanup-markdown" = [ "pulldown-cmark" "pulldown-cmark-to-cmark" ];
+          "default" = [ "format" ];
+          "format" = [ "prettyplease" "syn" ];
+          "prettyplease" = [ "dep:prettyplease" ];
+          "pulldown-cmark" = [ "dep:pulldown-cmark" ];
+          "pulldown-cmark-to-cmark" = [ "dep:pulldown-cmark-to-cmark" ];
+          "syn" = [ "dep:syn" ];
+        };
+        resolvedDefaultFeatures = [ "cleanup-markdown" "default" "format" "prettyplease" "pulldown-cmark" "pulldown-cmark-to-cmark" "syn" ];
+      };
+      "prost-derive 0.11.9" = rec {
+        crateName = "prost-derive";
+        version = "0.11.9";
+        edition = "2021";
+        sha256 = "1d3mw2s2jba1f7wcjmjd6ha2a255p2rmynxhm1nysv9w1z8xilp5";
+        procMacro = true;
+        authors = [
+          "Dan Burkert <dan@danburkert.com>"
+          "Lucio Franco <luciofranco14@gmail.com>"
+          "Tokio Contributors <team@tokio.rs>"
+        ];
+        dependencies = [
+          {
+            name = "anyhow";
+            packageId = "anyhow";
+          }
+          {
+            name = "itertools";
+            packageId = "itertools 0.10.5";
+            usesDefaultFeatures = false;
+            features = [ "use_alloc" ];
+          }
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn 1.0.109";
+            features = [ "extra-traits" ];
+          }
+        ];
+
+      };
+      "prost-derive 0.12.3" = rec {
+        crateName = "prost-derive";
+        version = "0.12.3";
+        edition = "2021";
+        sha256 = "03l4yf6pdjvc4sgbvln2srq1avzm1ai86zni4hhqxvqxvnhwkdpg";
+        procMacro = true;
+        authors = [
+          "Dan Burkert <dan@danburkert.com>"
+          "Lucio Franco <luciofranco14@gmail.com>"
+          "Tokio Contributors <team@tokio.rs>"
+        ];
+        dependencies = [
+          {
+            name = "anyhow";
+            packageId = "anyhow";
+          }
+          {
+            name = "itertools";
+            packageId = "itertools 0.11.0";
+            usesDefaultFeatures = false;
+            features = [ "use_alloc" ];
+          }
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn 2.0.48";
+            features = [ "extra-traits" ];
+          }
+        ];
+
+      };
+      "prost-types" = rec {
+        crateName = "prost-types";
+        version = "0.12.3";
+        edition = "2021";
+        sha256 = "03j73llzljdxv9cdxp4m3vb9j3gh4y24rkbx48k3rx6wkvsrhf0r";
+        authors = [
+          "Dan Burkert <dan@danburkert.com>"
+          "Lucio Franco <luciofranco14@gmail.com"
+          "Tokio Contributors <team@tokio.rs>"
+        ];
+        dependencies = [
+          {
+            name = "prost";
+            packageId = "prost 0.12.3";
+            usesDefaultFeatures = false;
+            features = [ "prost-derive" ];
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "std" = [ "prost/std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "prost-wkt" = rec {
+        crateName = "prost-wkt";
+        version = "0.5.0";
+        edition = "2021";
+        sha256 = "0h3c0jyfpg7f3s9a3mx6xcif28j3ir5c5qmps88bknpiy31zk3jd";
+        authors = [
+          "fdeantoni <fdeantoni@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "chrono";
+            packageId = "chrono";
+            usesDefaultFeatures = false;
+            features = [ "serde" ];
+          }
+          {
+            name = "inventory";
+            packageId = "inventory";
+          }
+          {
+            name = "prost";
+            packageId = "prost 0.12.3";
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+          }
+          {
+            name = "serde_derive";
+            packageId = "serde_derive";
+          }
+          {
+            name = "serde_json";
+            packageId = "serde_json";
+          }
+          {
+            name = "typetag";
+            packageId = "typetag";
+          }
+        ];
+
+      };
+      "prost-wkt-build" = rec {
+        crateName = "prost-wkt-build";
+        version = "0.5.0";
+        edition = "2021";
+        sha256 = "0883g26vrhx07kv0dq85559pj95zxs10lx042pp4za2clplwlcav";
+        authors = [
+          "fdeantoni <fdeantoni@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "heck";
+            packageId = "heck";
+          }
+          {
+            name = "prost";
+            packageId = "prost 0.12.3";
+          }
+          {
+            name = "prost-build";
+            packageId = "prost-build";
+          }
+          {
+            name = "prost-types";
+            packageId = "prost-types";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+        ];
+
+      };
+      "prost-wkt-types" = rec {
+        crateName = "prost-wkt-types";
+        version = "0.5.0";
+        edition = "2021";
+        sha256 = "1vipmgvqqzr3hn9z5v85mx9zznzjwyfpjy8xzg2v94a0f2lf8ns3";
+        authors = [
+          "fdeantoni <fdeantoni@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "chrono";
+            packageId = "chrono";
+            usesDefaultFeatures = false;
+            features = [ "serde" ];
+          }
+          {
+            name = "prost";
+            packageId = "prost 0.12.3";
+          }
+          {
+            name = "prost-wkt";
+            packageId = "prost-wkt";
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+          }
+          {
+            name = "serde_derive";
+            packageId = "serde_derive";
+          }
+          {
+            name = "serde_json";
+            packageId = "serde_json";
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "prost";
+            packageId = "prost 0.12.3";
+          }
+          {
+            name = "prost-build";
+            packageId = "prost-build";
+          }
+          {
+            name = "prost-types";
+            packageId = "prost-types";
+          }
+          {
+            name = "prost-wkt-build";
+            packageId = "prost-wkt-build";
+          }
+          {
+            name = "regex";
+            packageId = "regex";
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "protobuf-src" = [ "dep:protobuf-src" ];
+          "protox" = [ "dep:protox" ];
+          "vendored-protoc" = [ "protobuf-src" ];
+          "vendored-protox" = [ "protox" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "pulldown-cmark" = rec {
+        crateName = "pulldown-cmark";
+        version = "0.9.6";
+        edition = "2021";
+        crateBin = [ ];
+        sha256 = "0av876a31qvqhy7gzdg134zn4s10smlyi744mz9vrllkf906n82p";
+        authors = [
+          "Raph Levien <raph.levien@gmail.com>"
+          "Marcus Klaas de Vries <mail@marcusklaas.nl>"
+        ];
+        dependencies = [
+          {
+            name = "bitflags";
+            packageId = "bitflags 2.4.2";
+          }
+          {
+            name = "memchr";
+            packageId = "memchr";
+          }
+          {
+            name = "unicase";
+            packageId = "unicase";
+          }
+        ];
+        features = {
+          "default" = [ "getopts" ];
+          "getopts" = [ "dep:getopts" ];
+          "serde" = [ "dep:serde" ];
+        };
+      };
+      "pulldown-cmark-to-cmark" = rec {
+        crateName = "pulldown-cmark-to-cmark";
+        version = "10.0.4";
+        edition = "2018";
+        sha256 = "0gc366cmd5jxal9m95l17rvqsm4dn62lywc8v5gwq8vcjvhyd501";
+        authors = [
+          "Sebastian Thiel <byronimo@gmail.com>"
+          "Dylan Owen <dyltotheo@gmail.com>"
+          "Alessandro Ogier <alessandro.ogier@gmail.com>"
+          "Zixian Cai <2891235+caizixian@users.noreply.github.com>"
+        ];
+        dependencies = [
+          {
+            name = "pulldown-cmark";
+            packageId = "pulldown-cmark";
+            usesDefaultFeatures = false;
+          }
+        ];
+
+      };
+      "quick-error" = rec {
+        crateName = "quick-error";
+        version = "1.2.3";
+        edition = "2015";
+        sha256 = "1q6za3v78hsspisc197bg3g7rpc989qycy8ypr8ap8igv10ikl51";
+        authors = [
+          "Paul Colomiets <paul@colomiets.name>"
+          "Colin Kiegel <kiegel@gmx.de>"
+        ];
+
+      };
+      "quick-xml" = rec {
+        crateName = "quick-xml";
+        version = "0.31.0";
+        edition = "2021";
+        sha256 = "0cravqanylzh5cq2v6hzlfqgxcid5nrp2snnb3pf4m0and2a610h";
+        dependencies = [
+          {
+            name = "memchr";
+            packageId = "memchr";
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+            optional = true;
+          }
+        ];
+        features = {
+          "arbitrary" = [ "dep:arbitrary" ];
+          "async-tokio" = [ "tokio" ];
+          "document-features" = [ "dep:document-features" ];
+          "encoding" = [ "encoding_rs" ];
+          "encoding_rs" = [ "dep:encoding_rs" ];
+          "serde" = [ "dep:serde" ];
+          "serde-types" = [ "serde/derive" ];
+          "serialize" = [ "serde" ];
+          "tokio" = [ "dep:tokio" ];
+        };
+        resolvedDefaultFeatures = [ "default" "overlapped-lists" "serde" "serialize" ];
+      };
+      "quote" = rec {
+        crateName = "quote";
+        version = "1.0.35";
+        edition = "2018";
+        sha256 = "1vv8r2ncaz4pqdr78x7f138ka595sp2ncr1sa2plm4zxbsmwj7i9";
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "default" = [ "proc-macro" ];
+          "proc-macro" = [ "proc-macro2/proc-macro" ];
+        };
+        resolvedDefaultFeatures = [ "default" "proc-macro" ];
+      };
+      "radix_trie" = rec {
+        crateName = "radix_trie";
+        version = "0.2.1";
+        edition = "2018";
+        sha256 = "1zaq3im5ss03w91ij11cj97vvzc5y1f3064d9pi2ysnwziww2sf0";
+        authors = [
+          "Michael Sproul <micsproul@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "endian-type";
+            packageId = "endian-type";
+          }
+          {
+            name = "nibble_vec";
+            packageId = "nibble_vec";
+          }
+        ];
+        features = {
+          "serde" = [ "dep:serde" ];
+        };
+      };
+      "rand" = rec {
+        crateName = "rand";
+        version = "0.8.5";
+        edition = "2018";
+        sha256 = "013l6931nn7gkc23jz5mm3qdhf93jjf0fg64nz2lp4i51qd8vbrl";
+        authors = [
+          "The Rand Project Developers"
+          "The Rust Project Developers"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+            optional = true;
+            usesDefaultFeatures = false;
+            target = { target, features }: (target."unix" or false);
+          }
+          {
+            name = "rand_chacha";
+            packageId = "rand_chacha";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "rand_core";
+            packageId = "rand_core";
+          }
+        ];
+        features = {
+          "alloc" = [ "rand_core/alloc" ];
+          "default" = [ "std" "std_rng" ];
+          "getrandom" = [ "rand_core/getrandom" ];
+          "libc" = [ "dep:libc" ];
+          "log" = [ "dep:log" ];
+          "packed_simd" = [ "dep:packed_simd" ];
+          "rand_chacha" = [ "dep:rand_chacha" ];
+          "serde" = [ "dep:serde" ];
+          "serde1" = [ "serde" "rand_core/serde1" ];
+          "simd_support" = [ "packed_simd" ];
+          "std" = [ "rand_core/std" "rand_chacha/std" "alloc" "getrandom" "libc" ];
+          "std_rng" = [ "rand_chacha" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "getrandom" "libc" "rand_chacha" "small_rng" "std" "std_rng" ];
+      };
+      "rand_chacha" = rec {
+        crateName = "rand_chacha";
+        version = "0.3.1";
+        edition = "2018";
+        sha256 = "123x2adin558xbhvqb8w4f6syjsdkmqff8cxwhmjacpsl1ihmhg6";
+        authors = [
+          "The Rand Project Developers"
+          "The Rust Project Developers"
+          "The CryptoCorrosion Contributors"
+        ];
+        dependencies = [
+          {
+            name = "ppv-lite86";
+            packageId = "ppv-lite86";
+            usesDefaultFeatures = false;
+            features = [ "simd" ];
+          }
+          {
+            name = "rand_core";
+            packageId = "rand_core";
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "serde" = [ "dep:serde" ];
+          "serde1" = [ "serde" ];
+          "std" = [ "ppv-lite86/std" ];
+        };
+        resolvedDefaultFeatures = [ "std" ];
+      };
+      "rand_core" = rec {
+        crateName = "rand_core";
+        version = "0.6.4";
+        edition = "2018";
+        sha256 = "0b4j2v4cb5krak1pv6kakv4sz6xcwbrmy2zckc32hsigbrwy82zc";
+        authors = [
+          "The Rand Project Developers"
+          "The Rust Project Developers"
+        ];
+        dependencies = [
+          {
+            name = "getrandom";
+            packageId = "getrandom";
+            optional = true;
+          }
+        ];
+        features = {
+          "getrandom" = [ "dep:getrandom" ];
+          "serde" = [ "dep:serde" ];
+          "serde1" = [ "serde" ];
+          "std" = [ "alloc" "getrandom" "getrandom/std" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "getrandom" "std" ];
+      };
+      "rand_xorshift" = rec {
+        crateName = "rand_xorshift";
+        version = "0.3.0";
+        edition = "2018";
+        sha256 = "13vcag7gmqspzyabfl1gr9ykvxd2142q2agrj8dkyjmfqmgg4nyj";
+        authors = [
+          "The Rand Project Developers"
+          "The Rust Project Developers"
+        ];
+        dependencies = [
+          {
+            name = "rand_core";
+            packageId = "rand_core";
+          }
+        ];
+        features = {
+          "serde" = [ "dep:serde" ];
+          "serde1" = [ "serde" ];
+        };
+      };
+      "rand_xoshiro" = rec {
+        crateName = "rand_xoshiro";
+        version = "0.6.0";
+        edition = "2018";
+        sha256 = "1ajsic84rzwz5qr0mzlay8vi17swqi684bqvwqyiim3flfrcv5vg";
+        authors = [
+          "The Rand Project Developers"
+        ];
+        dependencies = [
+          {
+            name = "rand_core";
+            packageId = "rand_core";
+          }
+        ];
+        features = {
+          "serde" = [ "dep:serde" ];
+          "serde1" = [ "serde" ];
+        };
+      };
+      "rayon" = rec {
+        crateName = "rayon";
+        version = "1.8.1";
+        edition = "2021";
+        sha256 = "0lg0488xwpj5jsfz2gfczcrpclbjl8221mj5vdrhg8bp3883fwps";
+        authors = [
+          "Niko Matsakis <niko@alum.mit.edu>"
+          "Josh Stone <cuviper@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "either";
+            packageId = "either";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "rayon-core";
+            packageId = "rayon-core";
+          }
+        ];
+        features = {
+          "web_spin_lock" = [ "dep:wasm_sync" "rayon-core/web_spin_lock" ];
+        };
+      };
+      "rayon-core" = rec {
+        crateName = "rayon-core";
+        version = "1.12.1";
+        edition = "2021";
+        links = "rayon-core";
+        sha256 = "1qpwim68ai5h0j7axa8ai8z0payaawv3id0lrgkqmapx7lx8fr8l";
+        authors = [
+          "Niko Matsakis <niko@alum.mit.edu>"
+          "Josh Stone <cuviper@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "crossbeam-deque";
+            packageId = "crossbeam-deque";
+          }
+          {
+            name = "crossbeam-utils";
+            packageId = "crossbeam-utils";
+          }
+        ];
+        features = {
+          "web_spin_lock" = [ "dep:wasm_sync" ];
+        };
+      };
+      "redox_syscall 0.2.16" = rec {
+        crateName = "redox_syscall";
+        version = "0.2.16";
+        edition = "2018";
+        sha256 = "16jicm96kjyzm802cxdd1k9jmcph0db1a4lhslcnhjsvhp0mhnpv";
+        libName = "syscall";
+        authors = [
+          "Jeremy Soller <jackpot51@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "bitflags";
+            packageId = "bitflags 1.3.2";
+          }
+        ];
+
+      };
+      "redox_syscall 0.3.5" = rec {
+        crateName = "redox_syscall";
+        version = "0.3.5";
+        edition = "2018";
+        sha256 = "0acgiy2lc1m2vr8cr33l5s7k9wzby8dybyab1a9p753hcbr68xjn";
+        libName = "syscall";
+        authors = [
+          "Jeremy Soller <jackpot51@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "bitflags";
+            packageId = "bitflags 1.3.2";
+          }
+        ];
+        features = {
+          "core" = [ "dep:core" ];
+          "rustc-dep-of-std" = [ "core" "bitflags/rustc-dep-of-std" ];
+        };
+      };
+      "redox_syscall 0.4.1" = rec {
+        crateName = "redox_syscall";
+        version = "0.4.1";
+        edition = "2018";
+        sha256 = "1aiifyz5dnybfvkk4cdab9p2kmphag1yad6iknc7aszlxxldf8j7";
+        libName = "syscall";
+        authors = [
+          "Jeremy Soller <jackpot51@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "bitflags";
+            packageId = "bitflags 1.3.2";
+          }
+        ];
+        features = {
+          "core" = [ "dep:core" ];
+          "rustc-dep-of-std" = [ "core" "bitflags/rustc-dep-of-std" ];
+        };
+      };
+      "redox_users" = rec {
+        crateName = "redox_users";
+        version = "0.4.4";
+        edition = "2021";
+        sha256 = "1d1c7dhbb62sh8jrq9dhvqcyxqsh3wg8qknsi94iwq3r0wh7k151";
+        authors = [
+          "Jose Narvaez <goyox86@gmail.com>"
+          "Wesley Hershberger <mggmugginsmc@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "getrandom";
+            packageId = "getrandom";
+            features = [ "std" ];
+          }
+          {
+            name = "libredox";
+            packageId = "libredox";
+            usesDefaultFeatures = false;
+            features = [ "call" ];
+          }
+          {
+            name = "thiserror";
+            packageId = "thiserror";
+          }
+        ];
+        features = {
+          "auth" = [ "rust-argon2" "zeroize" ];
+          "default" = [ "auth" ];
+          "rust-argon2" = [ "dep:rust-argon2" ];
+          "zeroize" = [ "dep:zeroize" ];
+        };
+      };
+      "regex" = rec {
+        crateName = "regex";
+        version = "1.10.2";
+        edition = "2021";
+        sha256 = "0hxkd814n4irind8im5c9am221ri6bprx49nc7yxv02ykhd9a2rq";
+        authors = [
+          "The Rust Project Developers"
+          "Andrew Gallant <jamslam@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "aho-corasick";
+            packageId = "aho-corasick";
+            optional = true;
+          }
+          {
+            name = "memchr";
+            packageId = "memchr";
+            optional = true;
+          }
+          {
+            name = "regex-automata";
+            packageId = "regex-automata 0.4.3";
+            usesDefaultFeatures = false;
+            features = [ "alloc" "syntax" "meta" "nfa-pikevm" ];
+          }
+          {
+            name = "regex-syntax";
+            packageId = "regex-syntax 0.8.2";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "default" = [ "std" "perf" "unicode" "regex-syntax/default" ];
+          "logging" = [ "aho-corasick?/logging" "memchr?/logging" "regex-automata/logging" ];
+          "perf" = [ "perf-cache" "perf-dfa" "perf-onepass" "perf-backtrack" "perf-inline" "perf-literal" ];
+          "perf-backtrack" = [ "regex-automata/nfa-backtrack" ];
+          "perf-dfa" = [ "regex-automata/hybrid" ];
+          "perf-dfa-full" = [ "regex-automata/dfa-build" "regex-automata/dfa-search" ];
+          "perf-inline" = [ "regex-automata/perf-inline" ];
+          "perf-literal" = [ "dep:aho-corasick" "dep:memchr" "regex-automata/perf-literal" ];
+          "perf-onepass" = [ "regex-automata/dfa-onepass" ];
+          "std" = [ "aho-corasick?/std" "memchr?/std" "regex-automata/std" "regex-syntax/std" ];
+          "unicode" = [ "unicode-age" "unicode-bool" "unicode-case" "unicode-gencat" "unicode-perl" "unicode-script" "unicode-segment" "regex-automata/unicode" "regex-syntax/unicode" ];
+          "unicode-age" = [ "regex-automata/unicode-age" "regex-syntax/unicode-age" ];
+          "unicode-bool" = [ "regex-automata/unicode-bool" "regex-syntax/unicode-bool" ];
+          "unicode-case" = [ "regex-automata/unicode-case" "regex-syntax/unicode-case" ];
+          "unicode-gencat" = [ "regex-automata/unicode-gencat" "regex-syntax/unicode-gencat" ];
+          "unicode-perl" = [ "regex-automata/unicode-perl" "regex-automata/unicode-word-boundary" "regex-syntax/unicode-perl" ];
+          "unicode-script" = [ "regex-automata/unicode-script" "regex-syntax/unicode-script" ];
+          "unicode-segment" = [ "regex-automata/unicode-segment" "regex-syntax/unicode-segment" ];
+          "unstable" = [ "pattern" ];
+          "use_std" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "perf" "perf-backtrack" "perf-cache" "perf-dfa" "perf-inline" "perf-literal" "perf-onepass" "std" "unicode" "unicode-age" "unicode-bool" "unicode-case" "unicode-gencat" "unicode-perl" "unicode-script" "unicode-segment" ];
+      };
+      "regex-automata 0.1.10" = rec {
+        crateName = "regex-automata";
+        version = "0.1.10";
+        edition = "2015";
+        sha256 = "0ci1hvbzhrfby5fdpf4ganhf7kla58acad9i1ff1p34dzdrhs8vc";
+        authors = [
+          "Andrew Gallant <jamslam@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "regex-syntax";
+            packageId = "regex-syntax 0.6.29";
+            optional = true;
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "fst" = [ "dep:fst" ];
+          "regex-syntax" = [ "dep:regex-syntax" ];
+          "std" = [ "regex-syntax" ];
+          "transducer" = [ "std" "fst" ];
+        };
+        resolvedDefaultFeatures = [ "default" "regex-syntax" "std" ];
+      };
+      "regex-automata 0.4.3" = rec {
+        crateName = "regex-automata";
+        version = "0.4.3";
+        edition = "2021";
+        sha256 = "0gs8q9yhd3kcg4pr00ag4viqxnh5l7jpyb9fsfr8hzh451w4r02z";
+        authors = [
+          "The Rust Project Developers"
+          "Andrew Gallant <jamslam@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "aho-corasick";
+            packageId = "aho-corasick";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "memchr";
+            packageId = "memchr";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "regex-syntax";
+            packageId = "regex-syntax 0.8.2";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "default" = [ "std" "syntax" "perf" "unicode" "meta" "nfa" "dfa" "hybrid" ];
+          "dfa" = [ "dfa-build" "dfa-search" "dfa-onepass" ];
+          "dfa-build" = [ "nfa-thompson" "dfa-search" ];
+          "dfa-onepass" = [ "nfa-thompson" ];
+          "hybrid" = [ "alloc" "nfa-thompson" ];
+          "internal-instrument" = [ "internal-instrument-pikevm" ];
+          "internal-instrument-pikevm" = [ "logging" "std" ];
+          "logging" = [ "dep:log" "aho-corasick?/logging" "memchr?/logging" ];
+          "meta" = [ "syntax" "nfa-pikevm" ];
+          "nfa" = [ "nfa-thompson" "nfa-pikevm" "nfa-backtrack" ];
+          "nfa-backtrack" = [ "nfa-thompson" ];
+          "nfa-pikevm" = [ "nfa-thompson" ];
+          "nfa-thompson" = [ "alloc" ];
+          "perf" = [ "perf-inline" "perf-literal" ];
+          "perf-literal" = [ "perf-literal-substring" "perf-literal-multisubstring" ];
+          "perf-literal-multisubstring" = [ "std" "dep:aho-corasick" ];
+          "perf-literal-substring" = [ "aho-corasick?/perf-literal" "dep:memchr" ];
+          "std" = [ "regex-syntax?/std" "memchr?/std" "aho-corasick?/std" "alloc" ];
+          "syntax" = [ "dep:regex-syntax" "alloc" ];
+          "unicode" = [ "unicode-age" "unicode-bool" "unicode-case" "unicode-gencat" "unicode-perl" "unicode-script" "unicode-segment" "unicode-word-boundary" "regex-syntax?/unicode" ];
+          "unicode-age" = [ "regex-syntax?/unicode-age" ];
+          "unicode-bool" = [ "regex-syntax?/unicode-bool" ];
+          "unicode-case" = [ "regex-syntax?/unicode-case" ];
+          "unicode-gencat" = [ "regex-syntax?/unicode-gencat" ];
+          "unicode-perl" = [ "regex-syntax?/unicode-perl" ];
+          "unicode-script" = [ "regex-syntax?/unicode-script" ];
+          "unicode-segment" = [ "regex-syntax?/unicode-segment" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "dfa-onepass" "dfa-search" "hybrid" "meta" "nfa-backtrack" "nfa-pikevm" "nfa-thompson" "perf-inline" "perf-literal" "perf-literal-multisubstring" "perf-literal-substring" "std" "syntax" "unicode" "unicode-age" "unicode-bool" "unicode-case" "unicode-gencat" "unicode-perl" "unicode-script" "unicode-segment" "unicode-word-boundary" ];
+      };
+      "regex-syntax 0.6.29" = rec {
+        crateName = "regex-syntax";
+        version = "0.6.29";
+        edition = "2018";
+        sha256 = "1qgj49vm6y3zn1hi09x91jvgkl2b1fiaq402skj83280ggfwcqpi";
+        authors = [
+          "The Rust Project Developers"
+        ];
+        features = {
+          "default" = [ "unicode" ];
+          "unicode" = [ "unicode-age" "unicode-bool" "unicode-case" "unicode-gencat" "unicode-perl" "unicode-script" "unicode-segment" ];
+        };
+        resolvedDefaultFeatures = [ "default" "unicode" "unicode-age" "unicode-bool" "unicode-case" "unicode-gencat" "unicode-perl" "unicode-script" "unicode-segment" ];
+      };
+      "regex-syntax 0.8.2" = rec {
+        crateName = "regex-syntax";
+        version = "0.8.2";
+        edition = "2021";
+        sha256 = "17rd2s8xbiyf6lb4aj2nfi44zqlj98g2ays8zzj2vfs743k79360";
+        authors = [
+          "The Rust Project Developers"
+          "Andrew Gallant <jamslam@gmail.com>"
+        ];
+        features = {
+          "arbitrary" = [ "dep:arbitrary" ];
+          "default" = [ "std" "unicode" ];
+          "unicode" = [ "unicode-age" "unicode-bool" "unicode-case" "unicode-gencat" "unicode-perl" "unicode-script" "unicode-segment" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" "unicode" "unicode-age" "unicode-bool" "unicode-case" "unicode-gencat" "unicode-perl" "unicode-script" "unicode-segment" ];
+      };
+      "relative-path" = rec {
+        crateName = "relative-path";
+        version = "1.9.2";
+        edition = "2021";
+        sha256 = "1g0gc604zwm73gvpcicn8si25j9j5agqz50r0x1bkmgx6f7mi678";
+        authors = [
+          "John-John Tedro <udoprog@tedro.se>"
+        ];
+        features = {
+          "serde" = [ "dep:serde" ];
+        };
+        resolvedDefaultFeatures = [ "default" ];
+      };
+      "reqwest" = rec {
+        crateName = "reqwest";
+        version = "0.11.23";
+        edition = "2018";
+        sha256 = "0hgvzb7r46656r9vqhl5qk1kbr2xzjb91yr2cb321160ka6sxc9p";
+        authors = [
+          "Sean McArthur <sean@seanmonstar.com>"
+        ];
+        dependencies = [
+          {
+            name = "base64";
+            packageId = "base64";
+          }
+          {
+            name = "bytes";
+            packageId = "bytes";
+          }
+          {
+            name = "encoding_rs";
+            packageId = "encoding_rs";
+            target = { target, features }: (!("wasm32" == target."arch" or null));
+          }
+          {
+            name = "futures-core";
+            packageId = "futures-core";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-util";
+            packageId = "futures-util";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "h2";
+            packageId = "h2 0.3.24";
+            target = { target, features }: (!("wasm32" == target."arch" or null));
+          }
+          {
+            name = "http";
+            packageId = "http 0.2.11";
+          }
+          {
+            name = "http-body";
+            packageId = "http-body 0.4.6";
+            target = { target, features }: (!("wasm32" == target."arch" or null));
+          }
+          {
+            name = "hyper";
+            packageId = "hyper 0.14.28";
+            usesDefaultFeatures = false;
+            target = { target, features }: (!("wasm32" == target."arch" or null));
+            features = [ "tcp" "http1" "http2" "client" "runtime" ];
+          }
+          {
+            name = "hyper-rustls";
+            packageId = "hyper-rustls";
+            optional = true;
+            usesDefaultFeatures = false;
+            target = { target, features }: (!("wasm32" == target."arch" or null));
+          }
+          {
+            name = "ipnet";
+            packageId = "ipnet";
+            target = { target, features }: (!("wasm32" == target."arch" or null));
+          }
+          {
+            name = "js-sys";
+            packageId = "js-sys";
+            target = { target, features }: ("wasm32" == target."arch" or null);
+          }
+          {
+            name = "log";
+            packageId = "log";
+            target = { target, features }: (!("wasm32" == target."arch" or null));
+          }
+          {
+            name = "mime";
+            packageId = "mime";
+            target = { target, features }: (!("wasm32" == target."arch" or null));
+          }
+          {
+            name = "once_cell";
+            packageId = "once_cell";
+            target = { target, features }: (!("wasm32" == target."arch" or null));
+          }
+          {
+            name = "percent-encoding";
+            packageId = "percent-encoding";
+            target = { target, features }: (!("wasm32" == target."arch" or null));
+          }
+          {
+            name = "pin-project-lite";
+            packageId = "pin-project-lite";
+            target = { target, features }: (!("wasm32" == target."arch" or null));
+          }
+          {
+            name = "rustls";
+            packageId = "rustls 0.21.10";
+            optional = true;
+            target = { target, features }: (!("wasm32" == target."arch" or null));
+            features = [ "dangerous_configuration" ];
+          }
+          {
+            name = "rustls-native-certs";
+            packageId = "rustls-native-certs 0.6.3";
+            optional = true;
+            target = { target, features }: (!("wasm32" == target."arch" or null));
+          }
+          {
+            name = "rustls-pemfile";
+            packageId = "rustls-pemfile 1.0.4";
+            optional = true;
+            target = { target, features }: (!("wasm32" == target."arch" or null));
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+          }
+          {
+            name = "serde_json";
+            packageId = "serde_json";
+            optional = true;
+          }
+          {
+            name = "serde_json";
+            packageId = "serde_json";
+            target = { target, features }: ("wasm32" == target."arch" or null);
+          }
+          {
+            name = "serde_urlencoded";
+            packageId = "serde_urlencoded";
+          }
+          {
+            name = "system-configuration";
+            packageId = "system-configuration";
+            target = { target, features }: ("macos" == target."os" or null);
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+            usesDefaultFeatures = false;
+            target = { target, features }: (!("wasm32" == target."arch" or null));
+            features = [ "net" "time" ];
+          }
+          {
+            name = "tokio-rustls";
+            packageId = "tokio-rustls 0.24.1";
+            optional = true;
+            target = { target, features }: (!("wasm32" == target."arch" or null));
+          }
+          {
+            name = "tokio-util";
+            packageId = "tokio-util";
+            optional = true;
+            usesDefaultFeatures = false;
+            target = { target, features }: (!("wasm32" == target."arch" or null));
+            features = [ "codec" "io" ];
+          }
+          {
+            name = "tower-service";
+            packageId = "tower-service";
+          }
+          {
+            name = "url";
+            packageId = "url";
+          }
+          {
+            name = "wasm-bindgen";
+            packageId = "wasm-bindgen";
+            target = { target, features }: ("wasm32" == target."arch" or null);
+          }
+          {
+            name = "wasm-bindgen-futures";
+            packageId = "wasm-bindgen-futures";
+            target = { target, features }: ("wasm32" == target."arch" or null);
+          }
+          {
+            name = "wasm-streams";
+            packageId = "wasm-streams";
+            optional = true;
+            target = { target, features }: ("wasm32" == target."arch" or null);
+          }
+          {
+            name = "web-sys";
+            packageId = "web-sys";
+            target = { target, features }: ("wasm32" == target."arch" or null);
+            features = [ "AbortController" "AbortSignal" "Headers" "Request" "RequestInit" "RequestMode" "Response" "Window" "FormData" "Blob" "BlobPropertyBag" "ServiceWorkerGlobalScope" "RequestCredentials" "File" "ReadableStream" ];
+          }
+          {
+            name = "winreg";
+            packageId = "winreg";
+            target = { target, features }: (target."windows" or false);
+          }
+        ];
+        devDependencies = [
+          {
+            name = "hyper";
+            packageId = "hyper 0.14.28";
+            usesDefaultFeatures = false;
+            target = { target, features }: (!("wasm32" == target."arch" or null));
+            features = [ "tcp" "stream" "http1" "http2" "client" "server" "runtime" ];
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+            target = { target, features }: (!("wasm32" == target."arch" or null));
+            features = [ "derive" ];
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+            usesDefaultFeatures = false;
+            target = { target, features }: (!("wasm32" == target."arch" or null));
+            features = [ "macros" "rt-multi-thread" ];
+          }
+          {
+            name = "wasm-bindgen";
+            packageId = "wasm-bindgen";
+            target = { target, features }: ("wasm32" == target."arch" or null);
+            features = [ "serde-serialize" ];
+          }
+        ];
+        features = {
+          "__rustls" = [ "hyper-rustls" "tokio-rustls" "rustls" "__tls" "rustls-pemfile" ];
+          "async-compression" = [ "dep:async-compression" ];
+          "blocking" = [ "futures-util/io" "tokio/rt-multi-thread" "tokio/sync" ];
+          "brotli" = [ "async-compression" "async-compression/brotli" "tokio-util" ];
+          "cookie_crate" = [ "dep:cookie_crate" ];
+          "cookie_store" = [ "dep:cookie_store" ];
+          "cookies" = [ "cookie_crate" "cookie_store" ];
+          "default" = [ "default-tls" ];
+          "default-tls" = [ "hyper-tls" "native-tls-crate" "__tls" "tokio-native-tls" ];
+          "deflate" = [ "async-compression" "async-compression/zlib" "tokio-util" ];
+          "futures-channel" = [ "dep:futures-channel" ];
+          "gzip" = [ "async-compression" "async-compression/gzip" "tokio-util" ];
+          "h3" = [ "dep:h3" ];
+          "h3-quinn" = [ "dep:h3-quinn" ];
+          "http3" = [ "rustls-tls-manual-roots" "h3" "h3-quinn" "quinn" "futures-channel" ];
+          "hyper-rustls" = [ "dep:hyper-rustls" ];
+          "hyper-tls" = [ "dep:hyper-tls" ];
+          "json" = [ "serde_json" ];
+          "mime_guess" = [ "dep:mime_guess" ];
+          "multipart" = [ "mime_guess" ];
+          "native-tls" = [ "default-tls" ];
+          "native-tls-alpn" = [ "native-tls" "native-tls-crate/alpn" ];
+          "native-tls-crate" = [ "dep:native-tls-crate" ];
+          "native-tls-vendored" = [ "native-tls" "native-tls-crate/vendored" ];
+          "quinn" = [ "dep:quinn" ];
+          "rustls" = [ "dep:rustls" ];
+          "rustls-native-certs" = [ "dep:rustls-native-certs" ];
+          "rustls-pemfile" = [ "dep:rustls-pemfile" ];
+          "rustls-tls" = [ "rustls-tls-webpki-roots" ];
+          "rustls-tls-manual-roots" = [ "__rustls" ];
+          "rustls-tls-native-roots" = [ "rustls-native-certs" "__rustls" ];
+          "rustls-tls-webpki-roots" = [ "webpki-roots" "__rustls" ];
+          "serde_json" = [ "dep:serde_json" ];
+          "socks" = [ "tokio-socks" ];
+          "stream" = [ "tokio/fs" "tokio-util" "wasm-streams" ];
+          "tokio-native-tls" = [ "dep:tokio-native-tls" ];
+          "tokio-rustls" = [ "dep:tokio-rustls" ];
+          "tokio-socks" = [ "dep:tokio-socks" ];
+          "tokio-util" = [ "dep:tokio-util" ];
+          "trust-dns" = [ "trust-dns-resolver" ];
+          "trust-dns-resolver" = [ "dep:trust-dns-resolver" ];
+          "wasm-streams" = [ "dep:wasm-streams" ];
+          "webpki-roots" = [ "dep:webpki-roots" ];
+        };
+        resolvedDefaultFeatures = [ "__rustls" "__tls" "hyper-rustls" "json" "rustls" "rustls-native-certs" "rustls-pemfile" "rustls-tls-native-roots" "serde_json" "stream" "tokio-rustls" "tokio-util" "wasm-streams" ];
+      };
+      "ring" = rec {
+        crateName = "ring";
+        version = "0.17.7";
+        edition = "2021";
+        links = "ring_core_0_17_7";
+        sha256 = "0x5vvsp2424vll571xx085qf4hzljmwpz4x8n9l0j1c3akb67338";
+        authors = [
+          "Brian Smith <brian@briansmith.org>"
+        ];
+        dependencies = [
+          {
+            name = "getrandom";
+            packageId = "getrandom";
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            usesDefaultFeatures = false;
+            target = { target, features }: ((("android" == target."os" or null) || ("linux" == target."os" or null)) && (("aarch64" == target."arch" or null) || ("arm" == target."arch" or null)));
+          }
+          {
+            name = "spin";
+            packageId = "spin";
+            usesDefaultFeatures = false;
+            target = { target, features }: (("aarch64" == target."arch" or null) || ("arm" == target."arch" or null) || ("x86" == target."arch" or null) || ("x86_64" == target."arch" or null));
+            features = [ "once" ];
+          }
+          {
+            name = "untrusted";
+            packageId = "untrusted";
+          }
+          {
+            name = "windows-sys";
+            packageId = "windows-sys 0.48.0";
+            target = { target, features }: (("aarch64" == target."arch" or null) && ("windows" == target."os" or null));
+            features = [ "Win32_Foundation" "Win32_System_Threading" ];
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "cc";
+            packageId = "cc";
+            usesDefaultFeatures = false;
+          }
+        ];
+        devDependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+            usesDefaultFeatures = false;
+            target = { target, features }: ((target."unix" or false) || (target."windows" or false) || ("wasi" == target."os" or null));
+          }
+        ];
+        features = {
+          "default" = [ "alloc" "dev_urandom_fallback" ];
+          "std" = [ "alloc" ];
+          "wasm32_unknown_unknown_js" = [ "getrandom/js" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "dev_urandom_fallback" "std" ];
+      };
+      "rnix" = rec {
+        crateName = "rnix";
+        version = "0.11.0";
+        edition = "2021";
+        sha256 = "0pybq9gp4b7lp0066236jpqi9lgb1bzvqc9axymwrq3hxgdwwddv";
+        authors = [
+          "jD91mZM2 <me@krake.one>"
+        ];
+        dependencies = [
+          {
+            name = "rowan";
+            packageId = "rowan";
+          }
+        ];
+
+      };
+      "rowan" = rec {
+        crateName = "rowan";
+        version = "0.15.15";
+        edition = "2021";
+        sha256 = "0j9b340gsyf2h7v1q9xb4mqyqp4qbyzlbk1r9zn2mzyclyl8z99j";
+        authors = [
+          "Aleksey Kladov <aleksey.kladov@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "countme";
+            packageId = "countme";
+          }
+          {
+            name = "hashbrown";
+            packageId = "hashbrown 0.14.3";
+            usesDefaultFeatures = false;
+            features = [ "inline-more" ];
+          }
+          {
+            name = "memoffset";
+            packageId = "memoffset 0.9.0";
+          }
+          {
+            name = "rustc-hash";
+            packageId = "rustc-hash";
+          }
+          {
+            name = "text-size";
+            packageId = "text-size";
+          }
+        ];
+        features = {
+          "serde" = [ "dep:serde" ];
+          "serde1" = [ "serde" "text-size/serde" ];
+        };
+      };
+      "rstest" = rec {
+        crateName = "rstest";
+        version = "0.19.0";
+        edition = "2021";
+        sha256 = "0c43nsxpm1b74jxc73xwg94is6bwqvfzkrr1xbqyx7j7l791clwx";
+        authors = [
+          "Michele d'Amico <michele.damico@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "futures";
+            packageId = "futures";
+            optional = true;
+          }
+          {
+            name = "futures-timer";
+            packageId = "futures-timer";
+            optional = true;
+          }
+          {
+            name = "rstest_macros";
+            packageId = "rstest_macros";
+            usesDefaultFeatures = false;
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "rustc_version";
+            packageId = "rustc_version";
+          }
+        ];
+        features = {
+          "async-timeout" = [ "dep:futures" "dep:futures-timer" "rstest_macros/async-timeout" ];
+          "default" = [ "async-timeout" ];
+        };
+        resolvedDefaultFeatures = [ "async-timeout" "default" ];
+      };
+      "rstest_macros" = rec {
+        crateName = "rstest_macros";
+        version = "0.19.0";
+        edition = "2021";
+        sha256 = "09ackagv8kc2v4xy0s7blyg4agij9bz9pbb31l5h4rqzrirdza84";
+        procMacro = true;
+        authors = [
+          "Michele d'Amico <michele.damico@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "glob";
+            packageId = "glob";
+          }
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "regex";
+            packageId = "regex";
+          }
+          {
+            name = "relative-path";
+            packageId = "relative-path";
+          }
+          {
+            name = "syn";
+            packageId = "syn 2.0.48";
+            features = [ "full" "parsing" "extra-traits" "visit" "visit-mut" ];
+          }
+          {
+            name = "unicode-ident";
+            packageId = "unicode-ident";
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "rustc_version";
+            packageId = "rustc_version";
+          }
+        ];
+        features = {
+          "default" = [ "async-timeout" ];
+        };
+        resolvedDefaultFeatures = [ "async-timeout" ];
+      };
+      "rstest_reuse" = rec {
+        crateName = "rstest_reuse";
+        version = "0.6.0";
+        edition = "2021";
+        sha256 = "191l5gfwx9rmkqd48s85fkh21b73f38838fc896r4rxy39l0nlw8";
+        procMacro = true;
+        authors = [
+          "Michele d'Amico <michele.damico@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "rand";
+            packageId = "rand";
+          }
+          {
+            name = "syn";
+            packageId = "syn 2.0.48";
+            features = [ "full" "extra-traits" ];
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "rustc_version";
+            packageId = "rustc_version";
+          }
+        ];
+
+      };
+      "rustc-demangle" = rec {
+        crateName = "rustc-demangle";
+        version = "0.1.23";
+        edition = "2015";
+        sha256 = "0xnbk2bmyzshacjm2g1kd4zzv2y2az14bw3sjccq5qkpmsfvn9nn";
+        authors = [
+          "Alex Crichton <alex@alexcrichton.com>"
+        ];
+        features = {
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "core" = [ "dep:core" ];
+          "rustc-dep-of-std" = [ "core" "compiler_builtins" ];
+        };
+      };
+      "rustc-hash" = rec {
+        crateName = "rustc-hash";
+        version = "1.1.0";
+        edition = "2015";
+        sha256 = "1qkc5khrmv5pqi5l5ca9p5nl5hs742cagrndhbrlk3dhlrx3zm08";
+        authors = [
+          "The Rust Project Developers"
+        ];
+        features = {
+          "default" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "rustc_version" = rec {
+        crateName = "rustc_version";
+        version = "0.4.0";
+        edition = "2018";
+        sha256 = "0rpk9rcdk405xhbmgclsh4pai0svn49x35aggl4nhbkd4a2zb85z";
+        authors = [
+          "Dirkjan Ochtman <dirkjan@ochtman.nl>"
+          "Marvin Löbel <loebel.marvin@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "semver";
+            packageId = "semver";
+          }
+        ];
+
+      };
+      "rustix" = rec {
+        crateName = "rustix";
+        version = "0.38.30";
+        edition = "2021";
+        sha256 = "1jkb6bzrj2w9ffy35aw4q04mqk1yxqw35fz80x0c4cxgi9c988rj";
+        authors = [
+          "Dan Gohman <dev@sunfishcode.online>"
+          "Jakub Konka <kubkon@jakubkonka.com>"
+        ];
+        dependencies = [
+          {
+            name = "bitflags";
+            packageId = "bitflags 2.4.2";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "errno";
+            packageId = "errno";
+            rename = "libc_errno";
+            optional = true;
+            usesDefaultFeatures = false;
+            target = { target, features }: ((!(target."rustix_use_libc" or false)) && (!(target."miri" or false)) && ("linux" == target."os" or null) && ("little" == target."endian" or null) && (("arm" == target."arch" or null) || (("aarch64" == target."arch" or null) && ("64" == target."pointer_width" or null)) || ("riscv64" == target."arch" or null) || ((target."rustix_use_experimental_asm" or false) && ("powerpc64" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips32r6" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips64" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips64r6" == target."arch" or null)) || ("x86" == target."arch" or null) || (("x86_64" == target."arch" or null) && ("64" == target."pointer_width" or null))));
+          }
+          {
+            name = "errno";
+            packageId = "errno";
+            rename = "libc_errno";
+            usesDefaultFeatures = false;
+            target = { target, features }: ((!(target."windows" or false)) && ((target."rustix_use_libc" or false) || (target."miri" or false) || (!(("linux" == target."os" or null) && ("little" == target."endian" or null) && (("arm" == target."arch" or null) || (("aarch64" == target."arch" or null) && ("64" == target."pointer_width" or null)) || ("riscv64" == target."arch" or null) || ((target."rustix_use_experimental_asm" or false) && ("powerpc64" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips32r6" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips64" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips64r6" == target."arch" or null)) || ("x86" == target."arch" or null) || (("x86_64" == target."arch" or null) && ("64" == target."pointer_width" or null)))))));
+          }
+          {
+            name = "errno";
+            packageId = "errno";
+            rename = "libc_errno";
+            usesDefaultFeatures = false;
+            target = { target, features }: (target."windows" or false);
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            optional = true;
+            usesDefaultFeatures = false;
+            target = { target, features }: ((!(target."rustix_use_libc" or false)) && (!(target."miri" or false)) && ("linux" == target."os" or null) && ("little" == target."endian" or null) && (("arm" == target."arch" or null) || (("aarch64" == target."arch" or null) && ("64" == target."pointer_width" or null)) || ("riscv64" == target."arch" or null) || ((target."rustix_use_experimental_asm" or false) && ("powerpc64" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips32r6" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips64" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips64r6" == target."arch" or null)) || ("x86" == target."arch" or null) || (("x86_64" == target."arch" or null) && ("64" == target."pointer_width" or null))));
+            features = [ "extra_traits" ];
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            usesDefaultFeatures = false;
+            target = { target, features }: ((!(target."windows" or false)) && ((target."rustix_use_libc" or false) || (target."miri" or false) || (!(("linux" == target."os" or null) && ("little" == target."endian" or null) && (("arm" == target."arch" or null) || (("aarch64" == target."arch" or null) && ("64" == target."pointer_width" or null)) || ("riscv64" == target."arch" or null) || ((target."rustix_use_experimental_asm" or false) && ("powerpc64" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips32r6" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips64" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips64r6" == target."arch" or null)) || ("x86" == target."arch" or null) || (("x86_64" == target."arch" or null) && ("64" == target."pointer_width" or null)))))));
+            features = [ "extra_traits" ];
+          }
+          {
+            name = "linux-raw-sys";
+            packageId = "linux-raw-sys";
+            usesDefaultFeatures = false;
+            target = { target, features }: ((("android" == target."os" or null) || ("linux" == target."os" or null)) && ((target."rustix_use_libc" or false) || (target."miri" or false) || (!(("linux" == target."os" or null) && ("little" == target."endian" or null) && (("arm" == target."arch" or null) || (("aarch64" == target."arch" or null) && ("64" == target."pointer_width" or null)) || ("riscv64" == target."arch" or null) || ((target."rustix_use_experimental_asm" or false) && ("powerpc64" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips32r6" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips64" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips64r6" == target."arch" or null)) || ("x86" == target."arch" or null) || (("x86_64" == target."arch" or null) && ("64" == target."pointer_width" or null)))))));
+            features = [ "general" "ioctl" "no_std" ];
+          }
+          {
+            name = "linux-raw-sys";
+            packageId = "linux-raw-sys";
+            usesDefaultFeatures = false;
+            target = { target, features }: ((!(target."rustix_use_libc" or false)) && (!(target."miri" or false)) && ("linux" == target."os" or null) && ("little" == target."endian" or null) && (("arm" == target."arch" or null) || (("aarch64" == target."arch" or null) && ("64" == target."pointer_width" or null)) || ("riscv64" == target."arch" or null) || ((target."rustix_use_experimental_asm" or false) && ("powerpc64" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips32r6" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips64" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips64r6" == target."arch" or null)) || ("x86" == target."arch" or null) || (("x86_64" == target."arch" or null) && ("64" == target."pointer_width" or null))));
+            features = [ "general" "errno" "ioctl" "no_std" "elf" ];
+          }
+          {
+            name = "windows-sys";
+            packageId = "windows-sys 0.52.0";
+            target = { target, features }: (target."windows" or false);
+            features = [ "Win32_Foundation" "Win32_Networking_WinSock" "Win32_NetworkManagement_IpHelper" "Win32_System_Threading" ];
+          }
+        ];
+        devDependencies = [
+          {
+            name = "errno";
+            packageId = "errno";
+            rename = "libc_errno";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+          }
+        ];
+        features = {
+          "all-apis" = [ "event" "fs" "io_uring" "mm" "mount" "net" "param" "pipe" "process" "procfs" "pty" "rand" "runtime" "shm" "stdio" "system" "termios" "thread" "time" ];
+          "default" = [ "std" "use-libc-auxv" ];
+          "io_uring" = [ "event" "fs" "net" "linux-raw-sys/io_uring" ];
+          "itoa" = [ "dep:itoa" ];
+          "libc" = [ "dep:libc" ];
+          "libc_errno" = [ "dep:libc_errno" ];
+          "linux_latest" = [ "linux_4_11" ];
+          "net" = [ "linux-raw-sys/net" "linux-raw-sys/netlink" "linux-raw-sys/if_ether" "linux-raw-sys/xdp" ];
+          "once_cell" = [ "dep:once_cell" ];
+          "param" = [ "fs" ];
+          "process" = [ "linux-raw-sys/prctl" ];
+          "procfs" = [ "once_cell" "itoa" "fs" ];
+          "pty" = [ "itoa" "fs" ];
+          "runtime" = [ "linux-raw-sys/prctl" ];
+          "rustc-dep-of-std" = [ "dep:core" "dep:alloc" "dep:compiler_builtins" "linux-raw-sys/rustc-dep-of-std" "bitflags/rustc-dep-of-std" "compiler_builtins?/rustc-dep-of-std" ];
+          "shm" = [ "fs" ];
+          "std" = [ "bitflags/std" "alloc" "libc?/std" "libc_errno?/std" ];
+          "system" = [ "linux-raw-sys/system" ];
+          "thread" = [ "linux-raw-sys/prctl" ];
+          "use-libc" = [ "libc_errno" "libc" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "event" "fs" "net" "pipe" "process" "std" "termios" "time" "use-libc-auxv" ];
+      };
+      "rustls 0.21.10" = rec {
+        crateName = "rustls";
+        version = "0.21.10";
+        edition = "2021";
+        sha256 = "1fmpzk3axnhkd99saqkvraifdfms4pkyi56lkihf8n877j0sdmgr";
+        dependencies = [
+          {
+            name = "log";
+            packageId = "log";
+            optional = true;
+          }
+          {
+            name = "ring";
+            packageId = "ring";
+          }
+          {
+            name = "rustls-webpki";
+            packageId = "rustls-webpki 0.101.7";
+            rename = "webpki";
+            features = [ "alloc" "std" ];
+          }
+          {
+            name = "sct";
+            packageId = "sct";
+          }
+        ];
+        devDependencies = [
+          {
+            name = "log";
+            packageId = "log";
+          }
+        ];
+        features = {
+          "default" = [ "logging" "tls12" ];
+          "log" = [ "dep:log" ];
+          "logging" = [ "log" ];
+          "read_buf" = [ "rustversion" ];
+          "rustversion" = [ "dep:rustversion" ];
+        };
+        resolvedDefaultFeatures = [ "dangerous_configuration" "default" "log" "logging" "tls12" ];
+      };
+      "rustls 0.22.2" = rec {
+        crateName = "rustls";
+        version = "0.22.2";
+        edition = "2021";
+        sha256 = "0hcxyhq6ynvws9v5b2h81s1nwmijmya7a3vyyyhsy1wqpmb9jz78";
+        dependencies = [
+          {
+            name = "log";
+            packageId = "log";
+            optional = true;
+          }
+          {
+            name = "ring";
+            packageId = "ring";
+            optional = true;
+          }
+          {
+            name = "rustls-pki-types";
+            packageId = "rustls-pki-types";
+            rename = "pki-types";
+            features = [ "std" ];
+          }
+          {
+            name = "rustls-webpki";
+            packageId = "rustls-webpki 0.102.2";
+            rename = "webpki";
+            usesDefaultFeatures = false;
+            features = [ "std" ];
+          }
+          {
+            name = "subtle";
+            packageId = "subtle";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "zeroize";
+            packageId = "zeroize";
+          }
+        ];
+        devDependencies = [
+          {
+            name = "log";
+            packageId = "log";
+          }
+        ];
+        features = {
+          "aws_lc_rs" = [ "dep:aws-lc-rs" "webpki/aws_lc_rs" ];
+          "default" = [ "logging" "ring" "tls12" ];
+          "log" = [ "dep:log" ];
+          "logging" = [ "log" ];
+          "read_buf" = [ "rustversion" ];
+          "ring" = [ "dep:ring" "webpki/ring" ];
+          "rustversion" = [ "dep:rustversion" ];
+        };
+        resolvedDefaultFeatures = [ "log" "logging" "ring" "tls12" ];
+      };
+      "rustls-native-certs 0.6.3" = rec {
+        crateName = "rustls-native-certs";
+        version = "0.6.3";
+        edition = "2021";
+        sha256 = "007zind70rd5rfsrkdcfm8vn09j8sg02phg9334kark6rdscxam9";
+        dependencies = [
+          {
+            name = "openssl-probe";
+            packageId = "openssl-probe";
+            target = { target, features }: ((target."unix" or false) && (!("macos" == target."os" or null)));
+          }
+          {
+            name = "rustls-pemfile";
+            packageId = "rustls-pemfile 1.0.4";
+          }
+          {
+            name = "schannel";
+            packageId = "schannel";
+            target = { target, features }: (target."windows" or false);
+          }
+          {
+            name = "security-framework";
+            packageId = "security-framework";
+            target = { target, features }: ("macos" == target."os" or null);
+          }
+        ];
+
+      };
+      "rustls-native-certs 0.7.0" = rec {
+        crateName = "rustls-native-certs";
+        version = "0.7.0";
+        edition = "2021";
+        sha256 = "14ip15dcr6fmjzi12lla9cpln7mmkdid4a7wsp344v4kz9gbh7wg";
+        dependencies = [
+          {
+            name = "openssl-probe";
+            packageId = "openssl-probe";
+            target = { target, features }: ((target."unix" or false) && (!("macos" == target."os" or null)));
+          }
+          {
+            name = "rustls-pemfile";
+            packageId = "rustls-pemfile 2.1.0";
+          }
+          {
+            name = "rustls-pki-types";
+            packageId = "rustls-pki-types";
+            rename = "pki-types";
+          }
+          {
+            name = "schannel";
+            packageId = "schannel";
+            target = { target, features }: (target."windows" or false);
+          }
+          {
+            name = "security-framework";
+            packageId = "security-framework";
+            target = { target, features }: ("macos" == target."os" or null);
+          }
+        ];
+
+      };
+      "rustls-pemfile 1.0.4" = rec {
+        crateName = "rustls-pemfile";
+        version = "1.0.4";
+        edition = "2018";
+        sha256 = "1324n5bcns0rnw6vywr5agff3rwfvzphi7rmbyzwnv6glkhclx0w";
+        dependencies = [
+          {
+            name = "base64";
+            packageId = "base64";
+          }
+        ];
+
+      };
+      "rustls-pemfile 2.1.0" = rec {
+        crateName = "rustls-pemfile";
+        version = "2.1.0";
+        edition = "2018";
+        sha256 = "02y7qn9d93ri4hrm72yw4zqlbxch6ma045nyazmdrppw6jvkncrw";
+        dependencies = [
+          {
+            name = "base64";
+            packageId = "base64";
+            usesDefaultFeatures = false;
+            features = [ "alloc" ];
+          }
+          {
+            name = "rustls-pki-types";
+            packageId = "rustls-pki-types";
+            rename = "pki-types";
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "std" = [ "base64/std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "rustls-pki-types" = rec {
+        crateName = "rustls-pki-types";
+        version = "1.3.1";
+        edition = "2021";
+        sha256 = "1a0g7453h07701vyxjj05gv903a0shi43mf7hl3cdd08hsr6gpjy";
+        features = {
+          "default" = [ "alloc" ];
+          "std" = [ "alloc" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "std" ];
+      };
+      "rustls-webpki 0.101.7" = rec {
+        crateName = "rustls-webpki";
+        version = "0.101.7";
+        edition = "2021";
+        sha256 = "0rapfhpkqp75552i8r0y7f4vq7csb4k7gjjans0df73sxv8paqlb";
+        libName = "webpki";
+        dependencies = [
+          {
+            name = "ring";
+            packageId = "ring";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "untrusted";
+            packageId = "untrusted";
+          }
+        ];
+        features = {
+          "alloc" = [ "ring/alloc" ];
+          "default" = [ "std" ];
+          "std" = [ "alloc" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "std" ];
+      };
+      "rustls-webpki 0.102.2" = rec {
+        crateName = "rustls-webpki";
+        version = "0.102.2";
+        edition = "2021";
+        sha256 = "041ncshpw8wsvi8p74a3yw9c0r17lhyk1yjsxyrbkv8bfii0maps";
+        libName = "webpki";
+        dependencies = [
+          {
+            name = "ring";
+            packageId = "ring";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "rustls-pki-types";
+            packageId = "rustls-pki-types";
+            rename = "pki-types";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "untrusted";
+            packageId = "untrusted";
+          }
+        ];
+        features = {
+          "alloc" = [ "ring?/alloc" "pki-types/alloc" ];
+          "aws_lc_rs" = [ "dep:aws-lc-rs" ];
+          "default" = [ "std" "ring" ];
+          "ring" = [ "dep:ring" ];
+          "std" = [ "alloc" "pki-types/std" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "ring" "std" ];
+      };
+      "rustversion" = rec {
+        crateName = "rustversion";
+        version = "1.0.14";
+        edition = "2018";
+        sha256 = "1x1pz1yynk5xzzrazk2svmidj69jhz89dz5vrc28sixl20x1iz3z";
+        procMacro = true;
+        build = "build/build.rs";
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+
+      };
+      "rusty-fork" = rec {
+        crateName = "rusty-fork";
+        version = "0.3.0";
+        edition = "2018";
+        sha256 = "0kxwq5c480gg6q0j3bg4zzyfh2kwmc3v2ba94jw8ncjc8mpcqgfb";
+        authors = [
+          "Jason Lingle"
+        ];
+        dependencies = [
+          {
+            name = "fnv";
+            packageId = "fnv";
+          }
+          {
+            name = "quick-error";
+            packageId = "quick-error";
+          }
+          {
+            name = "tempfile";
+            packageId = "tempfile";
+          }
+          {
+            name = "wait-timeout";
+            packageId = "wait-timeout";
+            optional = true;
+          }
+        ];
+        features = {
+          "default" = [ "timeout" ];
+          "timeout" = [ "wait-timeout" ];
+          "wait-timeout" = [ "dep:wait-timeout" ];
+        };
+        resolvedDefaultFeatures = [ "timeout" "wait-timeout" ];
+      };
+      "rustyline" = rec {
+        crateName = "rustyline";
+        version = "10.1.1";
+        edition = "2018";
+        sha256 = "1vvsd68cch0lpcg6mcwfvfdd6r4cxbwis3bf9443phzkqcr3rs61";
+        authors = [
+          "Katsu Kawakami <kkawa1570@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "bitflags";
+            packageId = "bitflags 1.3.2";
+          }
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "clipboard-win";
+            packageId = "clipboard-win";
+            target = { target, features }: (target."windows" or false);
+          }
+          {
+            name = "dirs-next";
+            packageId = "dirs-next";
+            optional = true;
+          }
+          {
+            name = "fd-lock";
+            packageId = "fd-lock";
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+          }
+          {
+            name = "log";
+            packageId = "log";
+          }
+          {
+            name = "memchr";
+            packageId = "memchr";
+          }
+          {
+            name = "nix";
+            packageId = "nix 0.25.1";
+            usesDefaultFeatures = false;
+            target = { target, features }: (target."unix" or false);
+            features = [ "fs" "ioctl" "poll" "signal" "term" ];
+          }
+          {
+            name = "radix_trie";
+            packageId = "radix_trie";
+            optional = true;
+          }
+          {
+            name = "scopeguard";
+            packageId = "scopeguard";
+            target = { target, features }: (target."windows" or false);
+          }
+          {
+            name = "unicode-segmentation";
+            packageId = "unicode-segmentation";
+          }
+          {
+            name = "unicode-width";
+            packageId = "unicode-width";
+          }
+          {
+            name = "utf8parse";
+            packageId = "utf8parse";
+            target = { target, features }: (target."unix" or false);
+          }
+          {
+            name = "winapi";
+            packageId = "winapi";
+            target = { target, features }: (target."windows" or false);
+            features = [ "consoleapi" "handleapi" "synchapi" "minwindef" "processenv" "std" "winbase" "wincon" "winuser" ];
+          }
+        ];
+        features = {
+          "case_insensitive_history_search" = [ "regex" ];
+          "custom-bindings" = [ "radix_trie" ];
+          "default" = [ "custom-bindings" "with-dirs" ];
+          "dirs-next" = [ "dep:dirs-next" ];
+          "radix_trie" = [ "dep:radix_trie" ];
+          "regex" = [ "dep:regex" ];
+          "signal-hook" = [ "dep:signal-hook" ];
+          "skim" = [ "dep:skim" ];
+          "with-dirs" = [ "dirs-next" ];
+          "with-fuzzy" = [ "skim" ];
+        };
+        resolvedDefaultFeatures = [ "custom-bindings" "default" "dirs-next" "radix_trie" "with-dirs" ];
+      };
+      "ryu" = rec {
+        crateName = "ryu";
+        version = "1.0.16";
+        edition = "2018";
+        sha256 = "0k7b90xr48ag5bzmfjp82rljasw2fx28xr3bg1lrpx7b5sljm3gr";
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        features = {
+          "no-panic" = [ "dep:no-panic" ];
+        };
+      };
+      "same-file" = rec {
+        crateName = "same-file";
+        version = "1.0.6";
+        edition = "2018";
+        sha256 = "00h5j1w87dmhnvbv9l8bic3y7xxsnjmssvifw2ayvgx9mb1ivz4k";
+        authors = [
+          "Andrew Gallant <jamslam@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "winapi-util";
+            packageId = "winapi-util";
+            target = { target, features }: (target."windows" or false);
+          }
+        ];
+
+      };
+      "schannel" = rec {
+        crateName = "schannel";
+        version = "0.1.23";
+        edition = "2018";
+        sha256 = "0d1m156bsjrws6xzzr1wyfyih9i22mb2csb5pc5kmkrvci2ibjgv";
+        authors = [
+          "Steven Fackler <sfackler@gmail.com>"
+          "Steffen Butzer <steffen.butzer@outlook.com>"
+        ];
+        dependencies = [
+          {
+            name = "windows-sys";
+            packageId = "windows-sys 0.52.0";
+            features = [ "Win32_Foundation" "Win32_Security_Cryptography" "Win32_Security_Authentication_Identity" "Win32_Security_Credentials" "Win32_System_Memory" ];
+          }
+        ];
+        devDependencies = [
+          {
+            name = "windows-sys";
+            packageId = "windows-sys 0.52.0";
+            features = [ "Win32_System_SystemInformation" "Win32_System_Time" ];
+          }
+        ];
+
+      };
+      "scopeguard" = rec {
+        crateName = "scopeguard";
+        version = "1.2.0";
+        edition = "2015";
+        sha256 = "0jcz9sd47zlsgcnm1hdw0664krxwb5gczlif4qngj2aif8vky54l";
+        authors = [
+          "bluss"
+        ];
+        features = {
+          "default" = [ "use_std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "use_std" ];
+      };
+      "sct" = rec {
+        crateName = "sct";
+        version = "0.7.1";
+        edition = "2021";
+        sha256 = "056lmi2xkzdg1dbai6ha3n57s18cbip4pnmpdhyljli3m99n216s";
+        authors = [
+          "Joseph Birr-Pixton <jpixton@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "ring";
+            packageId = "ring";
+          }
+          {
+            name = "untrusted";
+            packageId = "untrusted";
+          }
+        ];
+
+      };
+      "security-framework" = rec {
+        crateName = "security-framework";
+        version = "2.9.2";
+        edition = "2021";
+        sha256 = "1pplxk15s5yxvi2m1sz5xfmjibp96cscdcl432w9jzbk0frlzdh5";
+        authors = [
+          "Steven Fackler <sfackler@gmail.com>"
+          "Kornel <kornel@geekhood.net>"
+        ];
+        dependencies = [
+          {
+            name = "bitflags";
+            packageId = "bitflags 1.3.2";
+          }
+          {
+            name = "core-foundation";
+            packageId = "core-foundation";
+          }
+          {
+            name = "core-foundation-sys";
+            packageId = "core-foundation-sys";
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+          }
+          {
+            name = "security-framework-sys";
+            packageId = "security-framework-sys";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "OSX_10_10" = [ "OSX_10_9" "security-framework-sys/OSX_10_10" ];
+          "OSX_10_11" = [ "OSX_10_10" "security-framework-sys/OSX_10_11" ];
+          "OSX_10_12" = [ "OSX_10_11" "security-framework-sys/OSX_10_12" ];
+          "OSX_10_13" = [ "OSX_10_12" "security-framework-sys/OSX_10_13" "alpn" "session-tickets" "serial-number-bigint" ];
+          "OSX_10_14" = [ "OSX_10_13" "security-framework-sys/OSX_10_14" ];
+          "OSX_10_15" = [ "OSX_10_14" "security-framework-sys/OSX_10_15" ];
+          "OSX_10_9" = [ "security-framework-sys/OSX_10_9" ];
+          "default" = [ "OSX_10_9" ];
+          "log" = [ "dep:log" ];
+          "serial-number-bigint" = [ "dep:num-bigint" ];
+        };
+        resolvedDefaultFeatures = [ "OSX_10_9" "default" ];
+      };
+      "security-framework-sys" = rec {
+        crateName = "security-framework-sys";
+        version = "2.9.1";
+        edition = "2021";
+        sha256 = "0yhciwlsy9dh0ps1gw3197kvyqx1bvc4knrhiznhid6kax196cp9";
+        authors = [
+          "Steven Fackler <sfackler@gmail.com>"
+          "Kornel <kornel@geekhood.net>"
+        ];
+        dependencies = [
+          {
+            name = "core-foundation-sys";
+            packageId = "core-foundation-sys";
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+          }
+        ];
+        features = {
+          "OSX_10_10" = [ "OSX_10_9" ];
+          "OSX_10_11" = [ "OSX_10_10" ];
+          "OSX_10_12" = [ "OSX_10_11" ];
+          "OSX_10_13" = [ "OSX_10_12" ];
+          "OSX_10_14" = [ "OSX_10_13" ];
+          "OSX_10_15" = [ "OSX_10_14" ];
+          "default" = [ "OSX_10_9" ];
+        };
+        resolvedDefaultFeatures = [ "OSX_10_9" ];
+      };
+      "semver" = rec {
+        crateName = "semver";
+        version = "1.0.21";
+        edition = "2018";
+        sha256 = "1c49snqlfcx93xym1cgwx8zcspmyyxm37xa2fyfgjx1vhalxfzmr";
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        features = {
+          "default" = [ "std" ];
+          "serde" = [ "dep:serde" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "serde" = rec {
+        crateName = "serde";
+        version = "1.0.197";
+        edition = "2018";
+        sha256 = "1qjcxqd3p4yh5cmmax9q4ics1zy34j5ij32cvjj5dc5rw5rwic9z";
+        authors = [
+          "Erick Tryzelaar <erick.tryzelaar@gmail.com>"
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "serde_derive";
+            packageId = "serde_derive";
+            optional = true;
+          }
+          {
+            name = "serde_derive";
+            packageId = "serde_derive";
+            target = { target, features }: false;
+          }
+        ];
+        devDependencies = [
+          {
+            name = "serde_derive";
+            packageId = "serde_derive";
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "derive" = [ "serde_derive" ];
+          "serde_derive" = [ "dep:serde_derive" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "derive" "rc" "serde_derive" "std" ];
+      };
+      "serde_derive" = rec {
+        crateName = "serde_derive";
+        version = "1.0.197";
+        edition = "2015";
+        sha256 = "02v1x0sdv8qy06lpr6by4ar1n3jz3hmab15cgimpzhgd895v7c3y";
+        procMacro = true;
+        authors = [
+          "Erick Tryzelaar <erick.tryzelaar@gmail.com>"
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+            usesDefaultFeatures = false;
+            features = [ "proc-macro" ];
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+            usesDefaultFeatures = false;
+            features = [ "proc-macro" ];
+          }
+          {
+            name = "syn";
+            packageId = "syn 2.0.48";
+            usesDefaultFeatures = false;
+            features = [ "clone-impls" "derive" "parsing" "printing" "proc-macro" ];
+          }
+        ];
+        features = { };
+        resolvedDefaultFeatures = [ "default" ];
+      };
+      "serde_json" = rec {
+        crateName = "serde_json";
+        version = "1.0.111";
+        edition = "2021";
+        sha256 = "1x441azvvdy6x8am4bvkxhswhzw5cr8ml0cqspnihvri8bx4cvhp";
+        authors = [
+          "Erick Tryzelaar <erick.tryzelaar@gmail.com>"
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "itoa";
+            packageId = "itoa";
+          }
+          {
+            name = "ryu";
+            packageId = "ryu";
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+            usesDefaultFeatures = false;
+          }
+        ];
+        devDependencies = [
+          {
+            name = "serde";
+            packageId = "serde";
+            features = [ "derive" ];
+          }
+        ];
+        features = {
+          "alloc" = [ "serde/alloc" ];
+          "default" = [ "std" ];
+          "indexmap" = [ "dep:indexmap" ];
+          "preserve_order" = [ "indexmap" "std" ];
+          "std" = [ "serde/std" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "raw_value" "std" ];
+      };
+      "serde_path_to_error" = rec {
+        crateName = "serde_path_to_error";
+        version = "0.1.16";
+        edition = "2021";
+        sha256 = "19hlz2359l37ifirskpcds7sxg0gzpqvfilibs7whdys0128i6dg";
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "itoa";
+            packageId = "itoa";
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+          }
+        ];
+
+      };
+      "serde_qs" = rec {
+        crateName = "serde_qs";
+        version = "0.12.0";
+        edition = "2018";
+        sha256 = "031kgpxbqkkxnql0k7sd80lyp98x7jc92311chrkc7k5d1as6c84";
+        authors = [
+          "Sam Scott <sam@osohq.com>"
+        ];
+        dependencies = [
+          {
+            name = "percent-encoding";
+            packageId = "percent-encoding";
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+          }
+          {
+            name = "thiserror";
+            packageId = "thiserror";
+          }
+        ];
+        features = {
+          "actix-web2" = [ "dep:actix-web2" ];
+          "actix-web3" = [ "dep:actix-web3" ];
+          "actix-web4" = [ "dep:actix-web4" ];
+          "actix2" = [ "actix-web2" "futures" ];
+          "actix3" = [ "actix-web3" "futures" ];
+          "actix4" = [ "actix-web4" "futures" ];
+          "axum" = [ "axum-framework" "futures" ];
+          "axum-framework" = [ "dep:axum-framework" ];
+          "futures" = [ "dep:futures" ];
+          "tracing" = [ "dep:tracing" ];
+          "warp" = [ "futures" "tracing" "warp-framework" ];
+          "warp-framework" = [ "dep:warp-framework" ];
+        };
+        resolvedDefaultFeatures = [ "default" ];
+      };
+      "serde_spanned" = rec {
+        crateName = "serde_spanned";
+        version = "0.6.5";
+        edition = "2021";
+        sha256 = "1hgh6s3jjwyzhfk3xwb6pnnr1misq9nflwq0f026jafi37s24dpb";
+        dependencies = [
+          {
+            name = "serde";
+            packageId = "serde";
+            optional = true;
+          }
+        ];
+        devDependencies = [
+          {
+            name = "serde";
+            packageId = "serde";
+          }
+        ];
+        features = {
+          "serde" = [ "dep:serde" ];
+        };
+        resolvedDefaultFeatures = [ "serde" ];
+      };
+      "serde_urlencoded" = rec {
+        crateName = "serde_urlencoded";
+        version = "0.7.1";
+        edition = "2018";
+        sha256 = "1zgklbdaysj3230xivihs30qi5vkhigg323a9m62k8jwf4a1qjfk";
+        authors = [
+          "Anthony Ramine <n.oxyde@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "form_urlencoded";
+            packageId = "form_urlencoded";
+          }
+          {
+            name = "itoa";
+            packageId = "itoa";
+          }
+          {
+            name = "ryu";
+            packageId = "ryu";
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+          }
+        ];
+
+      };
+      "serde_with" = rec {
+        crateName = "serde_with";
+        version = "3.7.0";
+        edition = "2021";
+        sha256 = "16jn72cij27fxjafcsma1z5p587xkk8wqhp2yv98zy5vc7iv107f";
+        authors = [
+          "Jonas Bushart"
+          "Marcin Kaźmierczak"
+        ];
+        dependencies = [
+          {
+            name = "base64";
+            packageId = "base64";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "chrono";
+            packageId = "chrono";
+            rename = "chrono_0_4";
+            optional = true;
+            usesDefaultFeatures = false;
+            features = [ "serde" ];
+          }
+          {
+            name = "hex";
+            packageId = "hex";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "indexmap";
+            packageId = "indexmap 1.9.3";
+            rename = "indexmap_1";
+            optional = true;
+            usesDefaultFeatures = false;
+            features = [ "serde-1" ];
+          }
+          {
+            name = "indexmap";
+            packageId = "indexmap 2.1.0";
+            rename = "indexmap_2";
+            optional = true;
+            usesDefaultFeatures = false;
+            features = [ "serde" ];
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "serde_derive";
+            packageId = "serde_derive";
+          }
+          {
+            name = "serde_json";
+            packageId = "serde_json";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "serde_with_macros";
+            packageId = "serde_with_macros";
+            optional = true;
+          }
+          {
+            name = "time";
+            packageId = "time";
+            rename = "time_0_3";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+        ];
+        devDependencies = [
+          {
+            name = "serde";
+            packageId = "serde";
+            usesDefaultFeatures = false;
+            features = [ "derive" ];
+          }
+          {
+            name = "serde_json";
+            packageId = "serde_json";
+            features = [ "preserve_order" ];
+          }
+        ];
+        features = {
+          "alloc" = [ "serde/alloc" "base64?/alloc" "chrono_0_4?/alloc" "hex?/alloc" "serde_json?/alloc" "time_0_3?/alloc" ];
+          "base64" = [ "dep:base64" "alloc" ];
+          "chrono" = [ "chrono_0_4" ];
+          "chrono_0_4" = [ "dep:chrono_0_4" ];
+          "default" = [ "std" "macros" ];
+          "guide" = [ "dep:doc-comment" "dep:document-features" "macros" "std" ];
+          "hashbrown_0_14" = [ "dep:hashbrown_0_14" "alloc" ];
+          "hex" = [ "dep:hex" "alloc" ];
+          "indexmap" = [ "indexmap_1" ];
+          "indexmap_1" = [ "dep:indexmap_1" "alloc" ];
+          "indexmap_2" = [ "dep:indexmap_2" "alloc" ];
+          "json" = [ "dep:serde_json" "alloc" ];
+          "macros" = [ "dep:serde_with_macros" ];
+          "schemars_0_8" = [ "dep:schemars_0_8" "std" "serde_with_macros?/schemars_0_8" ];
+          "std" = [ "alloc" "serde/std" "chrono_0_4?/clock" "chrono_0_4?/std" "indexmap_1?/std" "indexmap_2?/std" "time_0_3?/serde-well-known" "time_0_3?/std" ];
+          "time_0_3" = [ "dep:time_0_3" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "base64" "default" "macros" "std" ];
+      };
+      "serde_with_macros" = rec {
+        crateName = "serde_with_macros";
+        version = "3.7.0";
+        edition = "2021";
+        sha256 = "0mbnika5bw1mvgnl50rs7wfzj7dwxzgwqxnq6656694j38bdqqb5";
+        procMacro = true;
+        authors = [
+          "Jonas Bushart"
+        ];
+        dependencies = [
+          {
+            name = "darling";
+            packageId = "darling";
+          }
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn 2.0.48";
+            features = [ "extra-traits" "full" "parsing" ];
+          }
+        ];
+        features = { };
+      };
+      "sha1" = rec {
+        crateName = "sha1";
+        version = "0.10.6";
+        edition = "2018";
+        sha256 = "1fnnxlfg08xhkmwf2ahv634as30l1i3xhlhkvxflmasi5nd85gz3";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        dependencies = [
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "cpufeatures";
+            packageId = "cpufeatures";
+            target = { target, features }: (("aarch64" == target."arch" or null) || ("x86" == target."arch" or null) || ("x86_64" == target."arch" or null));
+          }
+          {
+            name = "digest";
+            packageId = "digest";
+          }
+        ];
+        devDependencies = [
+          {
+            name = "digest";
+            packageId = "digest";
+            features = [ "dev" ];
+          }
+        ];
+        features = {
+          "asm" = [ "sha1-asm" ];
+          "default" = [ "std" ];
+          "oid" = [ "digest/oid" ];
+          "sha1-asm" = [ "dep:sha1-asm" ];
+          "std" = [ "digest/std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "sha2" = rec {
+        crateName = "sha2";
+        version = "0.10.8";
+        edition = "2018";
+        sha256 = "1j1x78zk9il95w9iv46dh9wm73r6xrgj32y6lzzw7bxws9dbfgbr";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        dependencies = [
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "cpufeatures";
+            packageId = "cpufeatures";
+            target = { target, features }: (("aarch64" == target."arch" or null) || ("x86_64" == target."arch" or null) || ("x86" == target."arch" or null));
+          }
+          {
+            name = "digest";
+            packageId = "digest";
+          }
+        ];
+        devDependencies = [
+          {
+            name = "digest";
+            packageId = "digest";
+            features = [ "dev" ];
+          }
+        ];
+        features = {
+          "asm" = [ "sha2-asm" ];
+          "asm-aarch64" = [ "asm" ];
+          "default" = [ "std" ];
+          "oid" = [ "digest/oid" ];
+          "sha2-asm" = [ "dep:sha2-asm" ];
+          "std" = [ "digest/std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "sharded-slab" = rec {
+        crateName = "sharded-slab";
+        version = "0.1.7";
+        edition = "2018";
+        sha256 = "1xipjr4nqsgw34k7a2cgj9zaasl2ds6jwn89886kww93d32a637l";
+        authors = [
+          "Eliza Weisman <eliza@buoyant.io>"
+        ];
+        dependencies = [
+          {
+            name = "lazy_static";
+            packageId = "lazy_static";
+          }
+        ];
+        features = {
+          "loom" = [ "dep:loom" ];
+        };
+      };
+      "signal-hook-registry" = rec {
+        crateName = "signal-hook-registry";
+        version = "1.4.1";
+        edition = "2015";
+        sha256 = "18crkkw5k82bvcx088xlf5g4n3772m24qhzgfan80nda7d3rn8nq";
+        authors = [
+          "Michal 'vorner' Vaner <vorner@vorner.cz>"
+          "Masaki Hara <ackie.h.gmai@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+          }
+        ];
+
+      };
+      "signature" = rec {
+        crateName = "signature";
+        version = "2.2.0";
+        edition = "2021";
+        sha256 = "1pi9hd5vqfr3q3k49k37z06p7gs5si0in32qia4mmr1dancr6m3p";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        dependencies = [
+          {
+            name = "rand_core";
+            packageId = "rand_core";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "derive" = [ "dep:derive" ];
+          "digest" = [ "dep:digest" ];
+          "rand_core" = [ "dep:rand_core" ];
+          "std" = [ "alloc" "rand_core?/std" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "std" ];
+      };
+      "slab" = rec {
+        crateName = "slab";
+        version = "0.4.9";
+        edition = "2018";
+        sha256 = "0rxvsgir0qw5lkycrqgb1cxsvxzjv9bmx73bk5y42svnzfba94lg";
+        authors = [
+          "Carl Lerche <me@carllerche.com>"
+        ];
+        buildDependencies = [
+          {
+            name = "autocfg";
+            packageId = "autocfg";
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "serde" = [ "dep:serde" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "sled" = rec {
+        crateName = "sled";
+        version = "0.34.7";
+        edition = "2018";
+        sha256 = "0dcr2s7cylj5mb33ci3kpx7fz797jwvysnl5airrir9cgirv95kz";
+        authors = [
+          "Tyler Neely <t@jujit.su>"
+        ];
+        dependencies = [
+          {
+            name = "crc32fast";
+            packageId = "crc32fast";
+          }
+          {
+            name = "crossbeam-epoch";
+            packageId = "crossbeam-epoch";
+          }
+          {
+            name = "crossbeam-utils";
+            packageId = "crossbeam-utils";
+          }
+          {
+            name = "fs2";
+            packageId = "fs2";
+            target = { target, features }: (("linux" == target."os" or null) || ("macos" == target."os" or null) || ("windows" == target."os" or null));
+          }
+          {
+            name = "fxhash";
+            packageId = "fxhash";
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+          }
+          {
+            name = "log";
+            packageId = "log";
+          }
+          {
+            name = "parking_lot";
+            packageId = "parking_lot 0.11.2";
+          }
+        ];
+        devDependencies = [
+          {
+            name = "log";
+            packageId = "log";
+          }
+        ];
+        features = {
+          "backtrace" = [ "dep:backtrace" ];
+          "color-backtrace" = [ "dep:color-backtrace" ];
+          "compression" = [ "zstd" ];
+          "default" = [ "no_metrics" ];
+          "io_uring" = [ "rio" ];
+          "no_logs" = [ "log/max_level_off" ];
+          "pretty_backtrace" = [ "color-backtrace" ];
+          "rio" = [ "dep:rio" ];
+          "testing" = [ "event_log" "lock_free_delays" "compression" "failpoints" "backtrace" ];
+          "zstd" = [ "dep:zstd" ];
+        };
+        resolvedDefaultFeatures = [ "default" "no_metrics" ];
+      };
+      "smallvec" = rec {
+        crateName = "smallvec";
+        version = "1.13.1";
+        edition = "2018";
+        sha256 = "1mzk9j117pn3k1gabys0b7nz8cdjsx5xc6q7fwnm8r0an62d7v76";
+        authors = [
+          "The Servo Project Developers"
+        ];
+        features = {
+          "arbitrary" = [ "dep:arbitrary" ];
+          "const_new" = [ "const_generics" ];
+          "drain_keep_rest" = [ "drain_filter" ];
+          "serde" = [ "dep:serde" ];
+        };
+        resolvedDefaultFeatures = [ "const_generics" "const_new" ];
+      };
+      "smol_str" = rec {
+        crateName = "smol_str";
+        version = "0.2.1";
+        edition = "2018";
+        sha256 = "0jca0hyrwnv428q5gxhn2s8jsvrrkyrb0fyla9x37056mmimb176";
+        authors = [
+          "Aleksey Kladov <aleksey.kladov@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "serde";
+            packageId = "serde";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+        ];
+        devDependencies = [
+          {
+            name = "serde";
+            packageId = "serde";
+            features = [ "derive" ];
+          }
+        ];
+        features = {
+          "arbitrary" = [ "dep:arbitrary" ];
+          "default" = [ "std" ];
+          "serde" = [ "dep:serde" ];
+          "std" = [ "serde?/std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "snafu" = rec {
+        crateName = "snafu";
+        version = "0.7.5";
+        edition = "2018";
+        sha256 = "1mj2j2gfbf8mm1hr02zrbrqrh2zp01f61xgkx0lpln2w0ankgpp4";
+        authors = [
+          "Jake Goulding <jake.goulding@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "doc-comment";
+            packageId = "doc-comment";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "snafu-derive";
+            packageId = "snafu-derive";
+          }
+        ];
+        features = {
+          "backtrace" = [ "dep:backtrace" ];
+          "backtraces" = [ "std" "backtrace" ];
+          "backtraces-impl-backtrace-crate" = [ "backtraces" ];
+          "default" = [ "std" "rust_1_46" ];
+          "futures" = [ "futures-core-crate" "pin-project" ];
+          "futures-core-crate" = [ "dep:futures-core-crate" ];
+          "futures-crate" = [ "dep:futures-crate" ];
+          "internal-dev-dependencies" = [ "futures-crate" ];
+          "pin-project" = [ "dep:pin-project" ];
+          "rust_1_39" = [ "snafu-derive/rust_1_39" ];
+          "rust_1_46" = [ "rust_1_39" "snafu-derive/rust_1_46" ];
+          "rust_1_61" = [ "rust_1_46" "snafu-derive/rust_1_61" ];
+          "unstable-backtraces-impl-std" = [ "backtraces-impl-std" "snafu-derive/unstable-backtraces-impl-std" ];
+          "unstable-provider-api" = [ "snafu-derive/unstable-provider-api" ];
+        };
+        resolvedDefaultFeatures = [ "default" "rust_1_39" "rust_1_46" "std" ];
+      };
+      "snafu-derive" = rec {
+        crateName = "snafu-derive";
+        version = "0.7.5";
+        edition = "2018";
+        sha256 = "1gzy9rzggs090zf7hfvgp4lm1glrmg9qzh796686jnq7bxk7j04r";
+        procMacro = true;
+        authors = [
+          "Jake Goulding <jake.goulding@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "heck";
+            packageId = "heck";
+          }
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn 1.0.109";
+            features = [ "full" ];
+          }
+        ];
+        features = { };
+        resolvedDefaultFeatures = [ "rust_1_39" "rust_1_46" ];
+      };
+      "socket2" = rec {
+        crateName = "socket2";
+        version = "0.5.5";
+        edition = "2021";
+        sha256 = "1sgq315f1njky114ip7wcy83qlphv9qclprfjwvxcpfblmcsqpvv";
+        authors = [
+          "Alex Crichton <alex@alexcrichton.com>"
+          "Thomas de Zeeuw <thomasdezeeuw@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (target."unix" or false);
+          }
+          {
+            name = "windows-sys";
+            packageId = "windows-sys 0.48.0";
+            target = { target, features }: (target."windows" or false);
+            features = [ "Win32_Foundation" "Win32_Networking_WinSock" "Win32_System_IO" "Win32_System_Threading" "Win32_System_WindowsProgramming" ];
+          }
+        ];
+        features = { };
+        resolvedDefaultFeatures = [ "all" ];
+      };
+      "spin" = rec {
+        crateName = "spin";
+        version = "0.9.8";
+        edition = "2015";
+        sha256 = "0rvam5r0p3a6qhc18scqpvpgb3ckzyqxpgdfyjnghh8ja7byi039";
+        authors = [
+          "Mathijs van de Nes <git@mathijs.vd-nes.nl>"
+          "John Ericson <git@JohnEricson.me>"
+          "Joshua Barretto <joshua.s.barretto@gmail.com>"
+        ];
+        features = {
+          "barrier" = [ "mutex" ];
+          "default" = [ "lock_api" "mutex" "spin_mutex" "rwlock" "once" "lazy" "barrier" ];
+          "fair_mutex" = [ "mutex" ];
+          "lazy" = [ "once" ];
+          "lock_api" = [ "lock_api_crate" ];
+          "lock_api_crate" = [ "dep:lock_api_crate" ];
+          "portable-atomic" = [ "dep:portable-atomic" ];
+          "portable_atomic" = [ "portable-atomic" ];
+          "spin_mutex" = [ "mutex" ];
+          "ticket_mutex" = [ "mutex" ];
+          "use_ticket_mutex" = [ "mutex" "ticket_mutex" ];
+        };
+        resolvedDefaultFeatures = [ "once" ];
+      };
+      "spki" = rec {
+        crateName = "spki";
+        version = "0.7.3";
+        edition = "2021";
+        sha256 = "17fj8k5fmx4w9mp27l970clrh5qa7r5sjdvbsln987xhb34dc7nr";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        dependencies = [
+          {
+            name = "base64ct";
+            packageId = "base64ct";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "der";
+            packageId = "der";
+            features = [ "oid" ];
+          }
+        ];
+        features = {
+          "alloc" = [ "base64ct?/alloc" "der/alloc" ];
+          "arbitrary" = [ "std" "dep:arbitrary" "der/arbitrary" ];
+          "base64" = [ "dep:base64ct" ];
+          "fingerprint" = [ "sha2" ];
+          "pem" = [ "alloc" "der/pem" ];
+          "sha2" = [ "dep:sha2" ];
+          "std" = [ "der/std" "alloc" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "std" ];
+      };
+      "static_assertions" = rec {
+        crateName = "static_assertions";
+        version = "1.1.0";
+        edition = "2015";
+        sha256 = "0gsl6xmw10gvn3zs1rv99laj5ig7ylffnh71f9l34js4nr4r7sx2";
+        authors = [
+          "Nikolai Vazquez"
+        ];
+        features = { };
+      };
+      "str-buf" = rec {
+        crateName = "str-buf";
+        version = "1.0.6";
+        edition = "2018";
+        sha256 = "1l7q4nha7wpsr0970bfqm773vhmpwr9l6rr8r4gwgrh46wvdh24y";
+        authors = [
+          "Douman <douman@gmx.se>"
+        ];
+        features = {
+          "serde" = [ "dep:serde" ];
+        };
+      };
+      "strsim" = rec {
+        crateName = "strsim";
+        version = "0.10.0";
+        edition = "2015";
+        sha256 = "08s69r4rcrahwnickvi0kq49z524ci50capybln83mg6b473qivk";
+        authors = [
+          "Danny Guo <danny@dannyguo.com>"
+        ];
+
+      };
+      "structmeta" = rec {
+        crateName = "structmeta";
+        version = "0.1.6";
+        edition = "2021";
+        sha256 = "0alyl12b7fab8izrpliil73sxs1ivr5vm0pisallmxlb4zb44j0h";
+        authors = [
+          "frozenlib"
+        ];
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "structmeta-derive";
+            packageId = "structmeta-derive";
+          }
+          {
+            name = "syn";
+            packageId = "syn 1.0.109";
+          }
+        ];
+        devDependencies = [
+          {
+            name = "syn";
+            packageId = "syn 1.0.109";
+            features = [ "extra-traits" "full" ];
+          }
+        ];
+
+      };
+      "structmeta-derive" = rec {
+        crateName = "structmeta-derive";
+        version = "0.1.6";
+        edition = "2021";
+        sha256 = "14vxik2m3dm7bwx016qfz062fwznkbq02fyq8vby545m0pj0nhi4";
+        procMacro = true;
+        authors = [
+          "frozenlib"
+        ];
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn 1.0.109";
+          }
+        ];
+        devDependencies = [
+          {
+            name = "syn";
+            packageId = "syn 1.0.109";
+            features = [ "extra-traits" ];
+          }
+        ];
+
+      };
+      "subtle" = rec {
+        crateName = "subtle";
+        version = "2.5.0";
+        edition = "2018";
+        sha256 = "1g2yjs7gffgmdvkkq0wrrh0pxds3q0dv6dhkw9cdpbib656xdkc1";
+        authors = [
+          "Isis Lovecruft <isis@patternsinthevoid.net>"
+          "Henry de Valence <hdevalence@hdevalence.ca>"
+        ];
+        features = {
+          "default" = [ "std" "i128" ];
+        };
+      };
+      "syn 1.0.109" = rec {
+        crateName = "syn";
+        version = "1.0.109";
+        edition = "2018";
+        sha256 = "0ds2if4600bd59wsv7jjgfkayfzy3hnazs394kz6zdkmna8l3dkj";
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "unicode-ident";
+            packageId = "unicode-ident";
+          }
+        ];
+        features = {
+          "default" = [ "derive" "parsing" "printing" "clone-impls" "proc-macro" ];
+          "printing" = [ "quote" ];
+          "proc-macro" = [ "proc-macro2/proc-macro" "quote/proc-macro" ];
+          "quote" = [ "dep:quote" ];
+          "test" = [ "syn-test-suite/all-features" ];
+        };
+        resolvedDefaultFeatures = [ "clone-impls" "default" "derive" "extra-traits" "full" "parsing" "printing" "proc-macro" "quote" "visit" "visit-mut" ];
+      };
+      "syn 2.0.48" = rec {
+        crateName = "syn";
+        version = "2.0.48";
+        edition = "2021";
+        sha256 = "0gqgfygmrxmp8q32lia9p294kdd501ybn6kn2h4gqza0irik2d8g";
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "unicode-ident";
+            packageId = "unicode-ident";
+          }
+        ];
+        features = {
+          "default" = [ "derive" "parsing" "printing" "clone-impls" "proc-macro" ];
+          "printing" = [ "quote" ];
+          "proc-macro" = [ "proc-macro2/proc-macro" "quote/proc-macro" ];
+          "quote" = [ "dep:quote" ];
+          "test" = [ "syn-test-suite/all-features" ];
+        };
+        resolvedDefaultFeatures = [ "clone-impls" "default" "derive" "extra-traits" "full" "parsing" "printing" "proc-macro" "quote" "visit" "visit-mut" ];
+      };
+      "sync_wrapper" = rec {
+        crateName = "sync_wrapper";
+        version = "0.1.2";
+        edition = "2018";
+        sha256 = "0q01lyj0gr9a93n10nxsn8lwbzq97jqd6b768x17c8f7v7gccir0";
+        authors = [
+          "Actyx AG <developer@actyx.io>"
+        ];
+        features = {
+          "futures" = [ "futures-core" ];
+          "futures-core" = [ "dep:futures-core" ];
+        };
+      };
+      "system-configuration" = rec {
+        crateName = "system-configuration";
+        version = "0.5.1";
+        edition = "2021";
+        sha256 = "1rz0r30xn7fiyqay2dvzfy56cvaa3km74hnbz2d72p97bkf3lfms";
+        authors = [
+          "Mullvad VPN"
+        ];
+        dependencies = [
+          {
+            name = "bitflags";
+            packageId = "bitflags 1.3.2";
+          }
+          {
+            name = "core-foundation";
+            packageId = "core-foundation";
+          }
+          {
+            name = "system-configuration-sys";
+            packageId = "system-configuration-sys";
+          }
+        ];
+
+      };
+      "system-configuration-sys" = rec {
+        crateName = "system-configuration-sys";
+        version = "0.5.0";
+        edition = "2021";
+        sha256 = "1jckxvdr37bay3i9v52izgy52dg690x5xfg3hd394sv2xf4b2px7";
+        authors = [
+          "Mullvad VPN"
+        ];
+        dependencies = [
+          {
+            name = "core-foundation-sys";
+            packageId = "core-foundation-sys";
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+          }
+        ];
+
+      };
+      "tabwriter" = rec {
+        crateName = "tabwriter";
+        version = "1.4.0";
+        edition = "2021";
+        sha256 = "1xp5j7v8jsk92zcmiyh4ya9akhrchjvc595vwcvxrxk49wn2h9x3";
+        authors = [
+          "Andrew Gallant <jamslam@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "unicode-width";
+            packageId = "unicode-width";
+          }
+        ];
+        features = { };
+        resolvedDefaultFeatures = [ "default" ];
+      };
+      "tempfile" = rec {
+        crateName = "tempfile";
+        version = "3.9.0";
+        edition = "2018";
+        sha256 = "1ypkl7rvv57n16q28psxpb61rnyhmfaif12ascdnsyljm90l3kh1";
+        authors = [
+          "Steven Allen <steven@stebalien.com>"
+          "The Rust Project Developers"
+          "Ashley Mannix <ashleymannix@live.com.au>"
+          "Jason White <me@jasonwhite.io>"
+        ];
+        dependencies = [
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "fastrand";
+            packageId = "fastrand";
+          }
+          {
+            name = "redox_syscall";
+            packageId = "redox_syscall 0.4.1";
+            target = { target, features }: ("redox" == target."os" or null);
+          }
+          {
+            name = "rustix";
+            packageId = "rustix";
+            target = { target, features }: ((target."unix" or false) || ("wasi" == target."os" or null));
+            features = [ "fs" ];
+          }
+          {
+            name = "windows-sys";
+            packageId = "windows-sys 0.52.0";
+            target = { target, features }: (target."windows" or false);
+            features = [ "Win32_Storage_FileSystem" "Win32_Foundation" ];
+          }
+        ];
+        features = { };
+      };
+      "termcolor" = rec {
+        crateName = "termcolor";
+        version = "1.4.1";
+        edition = "2018";
+        sha256 = "0mappjh3fj3p2nmrg4y7qv94rchwi9mzmgmfflr8p2awdj7lyy86";
+        authors = [
+          "Andrew Gallant <jamslam@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "winapi-util";
+            packageId = "winapi-util";
+            target = { target, features }: (target."windows" or false);
+          }
+        ];
+
+      };
+      "test-strategy" = rec {
+        crateName = "test-strategy";
+        version = "0.2.1";
+        edition = "2021";
+        sha256 = "105lxqs0vnqff5821sgns8q1scvrwfx1yw6iz7i7nr862j6l1mk2";
+        procMacro = true;
+        authors = [
+          "frozenlib"
+        ];
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "structmeta";
+            packageId = "structmeta";
+          }
+          {
+            name = "syn";
+            packageId = "syn 1.0.109";
+            features = [ "visit" "full" ];
+          }
+        ];
+
+      };
+      "text-size" = rec {
+        crateName = "text-size";
+        version = "1.1.1";
+        edition = "2018";
+        sha256 = "0cwjbkl7w3xc8mnkhg1nwij6p5y2qkcfldgss8ddnawvhf3s32pi";
+        authors = [
+          "Aleksey Kladov <aleksey.kladov@gmail.com>"
+          "Christopher Durham (CAD97) <cad97@cad97.com>"
+        ];
+        features = {
+          "serde" = [ "dep:serde" ];
+        };
+      };
+      "thiserror" = rec {
+        crateName = "thiserror";
+        version = "1.0.56";
+        edition = "2021";
+        sha256 = "1b9hnzngjan4d89zjs16i01bcpcnvdwklyh73lj16xk28p37hhym";
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "thiserror-impl";
+            packageId = "thiserror-impl";
+          }
+        ];
+
+      };
+      "thiserror-impl" = rec {
+        crateName = "thiserror-impl";
+        version = "1.0.56";
+        edition = "2021";
+        sha256 = "0w9ldp8fa574ilz4dn7y7scpcq66vdjy59qal8qdpwsh7faal3zs";
+        procMacro = true;
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn 2.0.48";
+          }
+        ];
+
+      };
+      "thread_local" = rec {
+        crateName = "thread_local";
+        version = "1.1.7";
+        edition = "2021";
+        sha256 = "0lp19jdgvp5m4l60cgxdnl00yw1hlqy8gcywg9bddwng9h36zp9z";
+        authors = [
+          "Amanieu d'Antras <amanieu@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "once_cell";
+            packageId = "once_cell";
+          }
+        ];
+        features = { };
+      };
+      "time" = rec {
+        crateName = "time";
+        version = "0.3.34";
+        edition = "2021";
+        sha256 = "0jc7wgprzqjhzd0nqkbmdlnjwyddnswmjw86ni2vq55v45jqn968";
+        authors = [
+          "Jacob Pratt <open-source@jhpratt.dev>"
+          "Time contributors"
+        ];
+        dependencies = [
+          {
+            name = "deranged";
+            packageId = "deranged";
+            usesDefaultFeatures = false;
+            features = [ "powerfmt" ];
+          }
+          {
+            name = "itoa";
+            packageId = "itoa";
+            optional = true;
+          }
+          {
+            name = "num-conv";
+            packageId = "num-conv";
+          }
+          {
+            name = "powerfmt";
+            packageId = "powerfmt";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "time-core";
+            packageId = "time-core";
+          }
+          {
+            name = "time-macros";
+            packageId = "time-macros";
+            optional = true;
+          }
+        ];
+        devDependencies = [
+          {
+            name = "num-conv";
+            packageId = "num-conv";
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+            usesDefaultFeatures = false;
+            features = [ "derive" ];
+          }
+          {
+            name = "time-macros";
+            packageId = "time-macros";
+          }
+        ];
+        features = {
+          "alloc" = [ "serde?/alloc" ];
+          "default" = [ "std" ];
+          "formatting" = [ "dep:itoa" "std" "time-macros?/formatting" ];
+          "large-dates" = [ "time-macros?/large-dates" ];
+          "local-offset" = [ "std" "dep:libc" "dep:num_threads" ];
+          "macros" = [ "dep:time-macros" ];
+          "parsing" = [ "time-macros?/parsing" ];
+          "quickcheck" = [ "dep:quickcheck" "alloc" "deranged/quickcheck" ];
+          "rand" = [ "dep:rand" "deranged/rand" ];
+          "serde" = [ "dep:serde" "time-macros?/serde" "deranged/serde" ];
+          "serde-human-readable" = [ "serde" "formatting" "parsing" ];
+          "serde-well-known" = [ "serde" "formatting" "parsing" ];
+          "std" = [ "alloc" "deranged/std" ];
+          "wasm-bindgen" = [ "dep:js-sys" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "formatting" "parsing" "serde" "serde-well-known" "std" ];
+      };
+      "time-core" = rec {
+        crateName = "time-core";
+        version = "0.1.2";
+        edition = "2021";
+        sha256 = "1wx3qizcihw6z151hywfzzyd1y5dl804ydyxci6qm07vbakpr4pg";
+        authors = [
+          "Jacob Pratt <open-source@jhpratt.dev>"
+          "Time contributors"
+        ];
+
+      };
+      "time-macros" = rec {
+        crateName = "time-macros";
+        version = "0.2.17";
+        edition = "2021";
+        sha256 = "0x3pahhk2751c6kqqq9dk6lz0gydbnxr44q01wpjlrz687ps78vv";
+        procMacro = true;
+        authors = [
+          "Jacob Pratt <open-source@jhpratt.dev>"
+          "Time contributors"
+        ];
+        dependencies = [
+          {
+            name = "num-conv";
+            packageId = "num-conv";
+          }
+          {
+            name = "time-core";
+            packageId = "time-core";
+          }
+        ];
+        features = { };
+        resolvedDefaultFeatures = [ "formatting" "parsing" "serde" ];
+      };
+      "tinytemplate" = rec {
+        crateName = "tinytemplate";
+        version = "1.2.1";
+        edition = "2015";
+        sha256 = "1g5n77cqkdh9hy75zdb01adxn45mkh9y40wdr7l68xpz35gnnkdy";
+        authors = [
+          "Brook Heisler <brookheisler@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "serde";
+            packageId = "serde";
+          }
+          {
+            name = "serde_json";
+            packageId = "serde_json";
+          }
+        ];
+
+      };
+      "tinyvec" = rec {
+        crateName = "tinyvec";
+        version = "1.6.0";
+        edition = "2018";
+        sha256 = "0l6bl2h62a5m44jdnpn7lmj14rd44via8180i7121fvm73mmrk47";
+        authors = [
+          "Lokathor <zefria@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "tinyvec_macros";
+            packageId = "tinyvec_macros";
+            optional = true;
+          }
+        ];
+        features = {
+          "alloc" = [ "tinyvec_macros" ];
+          "arbitrary" = [ "dep:arbitrary" ];
+          "real_blackbox" = [ "criterion/real_blackbox" ];
+          "rustc_1_55" = [ "rustc_1_40" ];
+          "rustc_1_57" = [ "rustc_1_55" ];
+          "serde" = [ "dep:serde" ];
+          "std" = [ "alloc" ];
+          "tinyvec_macros" = [ "dep:tinyvec_macros" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "tinyvec_macros" ];
+      };
+      "tinyvec_macros" = rec {
+        crateName = "tinyvec_macros";
+        version = "0.1.1";
+        edition = "2018";
+        sha256 = "081gag86208sc3y6sdkshgw3vysm5d34p431dzw0bshz66ncng0z";
+        authors = [
+          "Soveu <marx.tomasz@gmail.com>"
+        ];
+
+      };
+      "tokio" = rec {
+        crateName = "tokio";
+        version = "1.35.1";
+        edition = "2021";
+        sha256 = "01613rkziqp812a288ga65aqygs254wgajdi57v8brivjkx4x6y8";
+        authors = [
+          "Tokio Contributors <team@tokio.rs>"
+        ];
+        dependencies = [
+          {
+            name = "backtrace";
+            packageId = "backtrace";
+            target = { target, features }: (target."tokio_taskdump" or false);
+          }
+          {
+            name = "bytes";
+            packageId = "bytes";
+            optional = true;
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            optional = true;
+            target = { target, features }: (target."unix" or false);
+          }
+          {
+            name = "mio";
+            packageId = "mio";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "num_cpus";
+            packageId = "num_cpus";
+            optional = true;
+          }
+          {
+            name = "pin-project-lite";
+            packageId = "pin-project-lite";
+          }
+          {
+            name = "signal-hook-registry";
+            packageId = "signal-hook-registry";
+            optional = true;
+            target = { target, features }: (target."unix" or false);
+          }
+          {
+            name = "socket2";
+            packageId = "socket2";
+            optional = true;
+            target = { target, features }: (!(builtins.elem "wasm" target."family"));
+            features = [ "all" ];
+          }
+          {
+            name = "tokio-macros";
+            packageId = "tokio-macros";
+            optional = true;
+          }
+          {
+            name = "windows-sys";
+            packageId = "windows-sys 0.48.0";
+            optional = true;
+            target = { target, features }: (target."windows" or false);
+          }
+        ];
+        devDependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (target."unix" or false);
+          }
+          {
+            name = "socket2";
+            packageId = "socket2";
+            target = { target, features }: (!(builtins.elem "wasm" target."family"));
+          }
+          {
+            name = "windows-sys";
+            packageId = "windows-sys 0.48.0";
+            target = { target, features }: (target."windows" or false);
+            features = [ "Win32_Foundation" "Win32_Security_Authorization" ];
+          }
+        ];
+        features = {
+          "bytes" = [ "dep:bytes" ];
+          "full" = [ "fs" "io-util" "io-std" "macros" "net" "parking_lot" "process" "rt" "rt-multi-thread" "signal" "sync" "time" ];
+          "io-util" = [ "bytes" ];
+          "libc" = [ "dep:libc" ];
+          "macros" = [ "tokio-macros" ];
+          "mio" = [ "dep:mio" ];
+          "net" = [ "libc" "mio/os-poll" "mio/os-ext" "mio/net" "socket2" "windows-sys/Win32_Foundation" "windows-sys/Win32_Security" "windows-sys/Win32_Storage_FileSystem" "windows-sys/Win32_System_Pipes" "windows-sys/Win32_System_SystemServices" ];
+          "num_cpus" = [ "dep:num_cpus" ];
+          "parking_lot" = [ "dep:parking_lot" ];
+          "process" = [ "bytes" "libc" "mio/os-poll" "mio/os-ext" "mio/net" "signal-hook-registry" "windows-sys/Win32_Foundation" "windows-sys/Win32_System_Threading" "windows-sys/Win32_System_WindowsProgramming" ];
+          "rt-multi-thread" = [ "num_cpus" "rt" ];
+          "signal" = [ "libc" "mio/os-poll" "mio/net" "mio/os-ext" "signal-hook-registry" "windows-sys/Win32_Foundation" "windows-sys/Win32_System_Console" ];
+          "signal-hook-registry" = [ "dep:signal-hook-registry" ];
+          "socket2" = [ "dep:socket2" ];
+          "test-util" = [ "rt" "sync" "time" ];
+          "tokio-macros" = [ "dep:tokio-macros" ];
+          "tracing" = [ "dep:tracing" ];
+          "windows-sys" = [ "dep:windows-sys" ];
+        };
+        resolvedDefaultFeatures = [ "bytes" "default" "fs" "io-std" "io-util" "libc" "macros" "mio" "net" "num_cpus" "rt" "rt-multi-thread" "signal" "signal-hook-registry" "socket2" "sync" "test-util" "time" "tokio-macros" "windows-sys" ];
+      };
+      "tokio-io-timeout" = rec {
+        crateName = "tokio-io-timeout";
+        version = "1.2.0";
+        edition = "2018";
+        sha256 = "1gx84f92q1491vj4pkn81j8pz1s3pgwnbrsdhfsa2556mli41drh";
+        authors = [
+          "Steven Fackler <sfackler@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "pin-project-lite";
+            packageId = "pin-project-lite";
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "time" ];
+          }
+        ];
+        devDependencies = [
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "full" ];
+          }
+        ];
+
+      };
+      "tokio-listener" = rec {
+        crateName = "tokio-listener";
+        version = "0.3.2";
+        edition = "2021";
+        sha256 = "00vkr1cywd2agn8jbkzwwf7y4ps3cfjm8l9ab697px2cgc97wdln";
+        dependencies = [
+          {
+            name = "axum";
+            packageId = "axum 0.7.4";
+            rename = "axum07";
+          }
+          {
+            name = "document-features";
+            packageId = "document-features";
+          }
+          {
+            name = "futures-core";
+            packageId = "futures-core";
+          }
+          {
+            name = "futures-util";
+            packageId = "futures-util";
+            optional = true;
+          }
+          {
+            name = "nix";
+            packageId = "nix 0.26.4";
+            optional = true;
+            usesDefaultFeatures = false;
+            target = { target, features }: (target."unix" or false);
+            features = [ "user" "fs" ];
+          }
+          {
+            name = "pin-project";
+            packageId = "pin-project";
+          }
+          {
+            name = "socket2";
+            packageId = "socket2";
+            optional = true;
+            features = [ "all" ];
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "net" "io-std" "time" "sync" ];
+          }
+          {
+            name = "tokio-util";
+            packageId = "tokio-util";
+            optional = true;
+            features = [ "net" "codec" ];
+          }
+          {
+            name = "tonic";
+            packageId = "tonic 0.11.0";
+            rename = "tonic";
+            optional = true;
+          }
+          {
+            name = "tracing";
+            packageId = "tracing";
+          }
+        ];
+        devDependencies = [
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "macros" "rt" "io-util" ];
+          }
+        ];
+        features = {
+          "axum07" = [ "dep:hyper1" "dep:hyper-util" "dep:futures-util" "dep:tower-service" "dep:tower" ];
+          "clap" = [ "dep:clap" ];
+          "default" = [ "user_facing_default" "tokio-util" ];
+          "hyper014" = [ "dep:hyper014" ];
+          "inetd" = [ "dep:futures-util" ];
+          "nix" = [ "dep:nix" ];
+          "serde" = [ "dep:serde" "serde_with" ];
+          "serde_with" = [ "dep:serde_with" ];
+          "socket2" = [ "dep:socket2" ];
+          "socket_options" = [ "socket2" ];
+          "tokio-util" = [ "dep:tokio-util" ];
+          "tonic010" = [ "dep:tonic_010" ];
+          "tonic011" = [ "dep:tonic" ];
+          "unix_path_tools" = [ "nix" ];
+          "user_facing_default" = [ "inetd" "unix" "unix_path_tools" "sd_listen" "socket_options" ];
+        };
+        resolvedDefaultFeatures = [ "default" "inetd" "nix" "sd_listen" "socket2" "socket_options" "tokio-util" "tonic011" "unix" "unix_path_tools" "user_facing_default" ];
+      };
+      "tokio-macros" = rec {
+        crateName = "tokio-macros";
+        version = "2.2.0";
+        edition = "2021";
+        sha256 = "0fwjy4vdx1h9pi4g2nml72wi0fr27b5m954p13ji9anyy8l1x2jv";
+        procMacro = true;
+        authors = [
+          "Tokio Contributors <team@tokio.rs>"
+        ];
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn 2.0.48";
+            features = [ "full" ];
+          }
+        ];
+
+      };
+      "tokio-retry" = rec {
+        crateName = "tokio-retry";
+        version = "0.3.0";
+        edition = "2018";
+        sha256 = "0kr1hnm5dmb9gfkby88yg2xj8g6x4i4gipva0c8ca3xyxhvfnmvz";
+        authors = [
+          "Sam Rijs <srijs@airpost.net>"
+        ];
+        dependencies = [
+          {
+            name = "pin-project";
+            packageId = "pin-project";
+          }
+          {
+            name = "rand";
+            packageId = "rand";
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "time" ];
+          }
+        ];
+        devDependencies = [
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "full" ];
+          }
+        ];
+
+      };
+      "tokio-rustls 0.24.1" = rec {
+        crateName = "tokio-rustls";
+        version = "0.24.1";
+        edition = "2018";
+        sha256 = "10bhibg57mqir7xjhb2xmf24xgfpx6fzpyw720a4ih8a737jg0y2";
+        dependencies = [
+          {
+            name = "rustls";
+            packageId = "rustls 0.21.10";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+          }
+        ];
+        devDependencies = [
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "full" ];
+          }
+        ];
+        features = {
+          "dangerous_configuration" = [ "rustls/dangerous_configuration" ];
+          "default" = [ "logging" "tls12" ];
+          "logging" = [ "rustls/logging" ];
+          "secret_extraction" = [ "rustls/secret_extraction" ];
+          "tls12" = [ "rustls/tls12" ];
+        };
+        resolvedDefaultFeatures = [ "default" "logging" "tls12" ];
+      };
+      "tokio-rustls 0.25.0" = rec {
+        crateName = "tokio-rustls";
+        version = "0.25.0";
+        edition = "2021";
+        sha256 = "03w6d5aqqf084rmcmrsyq5grhydl53blaiqcl0i2yfnv187hqpkp";
+        dependencies = [
+          {
+            name = "rustls";
+            packageId = "rustls 0.22.2";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "rustls-pki-types";
+            packageId = "rustls-pki-types";
+            rename = "pki-types";
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+          }
+        ];
+        devDependencies = [
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "full" ];
+          }
+        ];
+        features = {
+          "default" = [ "logging" "tls12" "ring" ];
+          "logging" = [ "rustls/logging" ];
+          "ring" = [ "rustls/ring" ];
+          "tls12" = [ "rustls/tls12" ];
+        };
+        resolvedDefaultFeatures = [ "default" "logging" "ring" "tls12" ];
+      };
+      "tokio-stream" = rec {
+        crateName = "tokio-stream";
+        version = "0.1.14";
+        edition = "2021";
+        sha256 = "0hi8hcwavh5sdi1ivc9qc4yvyr32f153c212dpd7sb366y6rhz1r";
+        authors = [
+          "Tokio Contributors <team@tokio.rs>"
+        ];
+        dependencies = [
+          {
+            name = "futures-core";
+            packageId = "futures-core";
+          }
+          {
+            name = "pin-project-lite";
+            packageId = "pin-project-lite";
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "sync" ];
+          }
+        ];
+        devDependencies = [
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "full" "test-util" ];
+          }
+        ];
+        features = {
+          "default" = [ "time" ];
+          "fs" = [ "tokio/fs" ];
+          "full" = [ "time" "net" "io-util" "fs" "sync" "signal" ];
+          "io-util" = [ "tokio/io-util" ];
+          "net" = [ "tokio/net" ];
+          "signal" = [ "tokio/signal" ];
+          "sync" = [ "tokio/sync" "tokio-util" ];
+          "time" = [ "tokio/time" ];
+          "tokio-util" = [ "dep:tokio-util" ];
+        };
+        resolvedDefaultFeatures = [ "default" "fs" "net" "time" ];
+      };
+      "tokio-tar" = rec {
+        crateName = "tokio-tar";
+        version = "0.3.1";
+        edition = "2018";
+        sha256 = "0xffvap4g7hlswk5daklk3jaqha6s6wxw72c24kmqgna23018mwx";
+        authors = [
+          "Alex Crichton <alex@alexcrichton.com>"
+          "dignifiedquire <me@dignifiequire.com>"
+          "Artem Vorotnikov <artem@vorotnikov.me>"
+          "Aiden McClelland <me@drbonez.dev>"
+        ];
+        dependencies = [
+          {
+            name = "filetime";
+            packageId = "filetime";
+          }
+          {
+            name = "futures-core";
+            packageId = "futures-core";
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (target."unix" or false);
+          }
+          {
+            name = "redox_syscall";
+            packageId = "redox_syscall 0.3.5";
+            target = { target, features }: ("redox" == target."os" or null);
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "fs" "io-util" "rt" ];
+          }
+          {
+            name = "tokio-stream";
+            packageId = "tokio-stream";
+          }
+          {
+            name = "xattr";
+            packageId = "xattr";
+            optional = true;
+            target = { target, features }: (target."unix" or false);
+          }
+        ];
+        devDependencies = [
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "full" ];
+          }
+        ];
+        features = {
+          "default" = [ "xattr" ];
+          "xattr" = [ "dep:xattr" ];
+        };
+        resolvedDefaultFeatures = [ "default" "xattr" ];
+      };
+      "tokio-test" = rec {
+        crateName = "tokio-test";
+        version = "0.4.3";
+        edition = "2021";
+        sha256 = "06fplzcc2ymahfzykd2ickw2qn7g3lz47bll00865s1spnx3r6z8";
+        authors = [
+          "Tokio Contributors <team@tokio.rs>"
+        ];
+        dependencies = [
+          {
+            name = "async-stream";
+            packageId = "async-stream";
+          }
+          {
+            name = "bytes";
+            packageId = "bytes";
+          }
+          {
+            name = "futures-core";
+            packageId = "futures-core";
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "rt" "sync" "time" "test-util" ];
+          }
+          {
+            name = "tokio-stream";
+            packageId = "tokio-stream";
+          }
+        ];
+        devDependencies = [
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "full" ];
+          }
+        ];
+
+      };
+      "tokio-util" = rec {
+        crateName = "tokio-util";
+        version = "0.7.10";
+        edition = "2021";
+        sha256 = "058y6x4mf0fsqji9rfyb77qbfyc50y4pk2spqgj6xsyr693z66al";
+        authors = [
+          "Tokio Contributors <team@tokio.rs>"
+        ];
+        dependencies = [
+          {
+            name = "bytes";
+            packageId = "bytes";
+          }
+          {
+            name = "futures-core";
+            packageId = "futures-core";
+          }
+          {
+            name = "futures-io";
+            packageId = "futures-io";
+            optional = true;
+          }
+          {
+            name = "futures-sink";
+            packageId = "futures-sink";
+          }
+          {
+            name = "pin-project-lite";
+            packageId = "pin-project-lite";
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "sync" ];
+          }
+          {
+            name = "tracing";
+            packageId = "tracing";
+            optional = true;
+            usesDefaultFeatures = false;
+            features = [ "std" ];
+          }
+        ];
+        devDependencies = [
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "full" ];
+          }
+        ];
+        features = {
+          "__docs_rs" = [ "futures-util" ];
+          "codec" = [ "tracing" ];
+          "compat" = [ "futures-io" ];
+          "full" = [ "codec" "compat" "io-util" "time" "net" "rt" ];
+          "futures-io" = [ "dep:futures-io" ];
+          "futures-util" = [ "dep:futures-util" ];
+          "hashbrown" = [ "dep:hashbrown" ];
+          "io-util" = [ "io" "tokio/rt" "tokio/io-util" ];
+          "net" = [ "tokio/net" ];
+          "rt" = [ "tokio/rt" "tokio/sync" "futures-util" "hashbrown" ];
+          "slab" = [ "dep:slab" ];
+          "time" = [ "tokio/time" "slab" ];
+          "tracing" = [ "dep:tracing" ];
+        };
+        resolvedDefaultFeatures = [ "codec" "compat" "default" "futures-io" "io" "io-util" "net" "tracing" ];
+      };
+      "toml" = rec {
+        crateName = "toml";
+        version = "0.6.0";
+        edition = "2021";
+        sha256 = "05zjz69wjymp9yrgccg5vhvxpf855rgn23vl1yvri4nwwj8difag";
+        authors = [
+          "Alex Crichton <alex@alexcrichton.com>"
+        ];
+        dependencies = [
+          {
+            name = "serde";
+            packageId = "serde";
+          }
+          {
+            name = "serde_spanned";
+            packageId = "serde_spanned";
+            features = [ "serde" ];
+          }
+          {
+            name = "toml_datetime";
+            packageId = "toml_datetime";
+            features = [ "serde" ];
+          }
+          {
+            name = "toml_edit";
+            packageId = "toml_edit";
+            optional = true;
+            features = [ "serde" ];
+          }
+        ];
+        devDependencies = [
+          {
+            name = "serde";
+            packageId = "serde";
+            features = [ "derive" ];
+          }
+        ];
+        features = {
+          "default" = [ "parse" "display" ];
+          "display" = [ "dep:toml_edit" ];
+          "indexmap" = [ "dep:indexmap" ];
+          "parse" = [ "dep:toml_edit" ];
+          "preserve_order" = [ "indexmap" ];
+        };
+        resolvedDefaultFeatures = [ "default" "display" "parse" ];
+      };
+      "toml_datetime" = rec {
+        crateName = "toml_datetime";
+        version = "0.5.1";
+        edition = "2021";
+        sha256 = "1xcw3kyklh3s2gxp65ma26rgkl7505la4xx1r55kfgcfmikz8ls5";
+        authors = [
+          "Alex Crichton <alex@alexcrichton.com>"
+        ];
+        dependencies = [
+          {
+            name = "serde";
+            packageId = "serde";
+            optional = true;
+          }
+        ];
+        features = {
+          "serde" = [ "dep:serde" ];
+        };
+        resolvedDefaultFeatures = [ "serde" ];
+      };
+      "toml_edit" = rec {
+        crateName = "toml_edit";
+        version = "0.18.1";
+        edition = "2021";
+        sha256 = "0ax1bwzd4xclpids3b69nd1nxqi3x3qa4ymz51jbrp6hsy6rvian";
+        authors = [
+          "Andronik Ordian <write@reusable.software>"
+          "Ed Page <eopage@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "indexmap";
+            packageId = "indexmap 1.9.3";
+          }
+          {
+            name = "nom8";
+            packageId = "nom8";
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+            optional = true;
+          }
+          {
+            name = "serde_spanned";
+            packageId = "serde_spanned";
+            optional = true;
+            features = [ "serde" ];
+          }
+          {
+            name = "toml_datetime";
+            packageId = "toml_datetime";
+          }
+        ];
+        features = {
+          "easy" = [ "serde" ];
+          "perf" = [ "dep:kstring" ];
+          "serde" = [ "dep:serde" "toml_datetime/serde" "dep:serde_spanned" ];
+        };
+        resolvedDefaultFeatures = [ "default" "serde" ];
+      };
+      "tonic 0.11.0" = rec {
+        crateName = "tonic";
+        version = "0.11.0";
+        edition = "2021";
+        sha256 = "04qsr527i256i3dk9dp1g2jr42q7yl91y5h06rvd9ycy9rxfpi3n";
+        authors = [
+          "Lucio Franco <luciofranco14@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "async-stream";
+            packageId = "async-stream";
+            optional = true;
+          }
+          {
+            name = "async-trait";
+            packageId = "async-trait";
+            optional = true;
+          }
+          {
+            name = "axum";
+            packageId = "axum 0.6.20";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "base64";
+            packageId = "base64";
+          }
+          {
+            name = "bytes";
+            packageId = "bytes";
+          }
+          {
+            name = "h2";
+            packageId = "h2 0.3.24";
+            optional = true;
+          }
+          {
+            name = "http";
+            packageId = "http 0.2.11";
+          }
+          {
+            name = "http-body";
+            packageId = "http-body 0.4.6";
+          }
+          {
+            name = "hyper";
+            packageId = "hyper 0.14.28";
+            optional = true;
+            features = [ "full" ];
+          }
+          {
+            name = "hyper-timeout";
+            packageId = "hyper-timeout";
+            optional = true;
+          }
+          {
+            name = "percent-encoding";
+            packageId = "percent-encoding";
+          }
+          {
+            name = "pin-project";
+            packageId = "pin-project";
+          }
+          {
+            name = "prost";
+            packageId = "prost 0.12.3";
+            optional = true;
+            usesDefaultFeatures = false;
+            features = [ "std" ];
+          }
+          {
+            name = "rustls-native-certs";
+            packageId = "rustls-native-certs 0.7.0";
+            optional = true;
+          }
+          {
+            name = "rustls-pemfile";
+            packageId = "rustls-pemfile 2.1.0";
+            optional = true;
+          }
+          {
+            name = "rustls-pki-types";
+            packageId = "rustls-pki-types";
+            optional = true;
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+          }
+          {
+            name = "tokio-rustls";
+            packageId = "tokio-rustls 0.25.0";
+            optional = true;
+          }
+          {
+            name = "tokio-stream";
+            packageId = "tokio-stream";
+          }
+          {
+            name = "tower";
+            packageId = "tower";
+            optional = true;
+            usesDefaultFeatures = false;
+            features = [ "balance" "buffer" "discover" "limit" "load" "make" "timeout" "util" ];
+          }
+          {
+            name = "tower-layer";
+            packageId = "tower-layer";
+          }
+          {
+            name = "tower-service";
+            packageId = "tower-service";
+          }
+          {
+            name = "tracing";
+            packageId = "tracing";
+          }
+        ];
+        devDependencies = [
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "rt" "macros" ];
+          }
+          {
+            name = "tower";
+            packageId = "tower";
+            features = [ "full" ];
+          }
+        ];
+        features = {
+          "codegen" = [ "dep:async-trait" ];
+          "default" = [ "transport" "codegen" "prost" ];
+          "gzip" = [ "dep:flate2" ];
+          "prost" = [ "dep:prost" ];
+          "tls" = [ "dep:rustls-pki-types" "dep:rustls-pemfile" "transport" "dep:tokio-rustls" "tokio/rt" "tokio/macros" ];
+          "tls-roots" = [ "tls-roots-common" "dep:rustls-native-certs" ];
+          "tls-roots-common" = [ "tls" ];
+          "tls-webpki-roots" = [ "tls-roots-common" "dep:webpki-roots" ];
+          "transport" = [ "dep:async-stream" "dep:axum" "channel" "dep:h2" "dep:hyper" "tokio/net" "tokio/time" "dep:tower" "dep:hyper-timeout" ];
+          "zstd" = [ "dep:zstd" ];
+        };
+        resolvedDefaultFeatures = [ "channel" "codegen" "default" "prost" "tls" "tls-roots" "tls-roots-common" "transport" ];
+      };
+      "tonic 0.9.2" = rec {
+        crateName = "tonic";
+        version = "0.9.2";
+        edition = "2021";
+        sha256 = "0nlx35lvah5hdcp6lg1d6dlprq0zz8ijj6f727szfcv479m6d0ih";
+        authors = [
+          "Lucio Franco <luciofranco14@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "async-trait";
+            packageId = "async-trait";
+            optional = true;
+          }
+          {
+            name = "axum";
+            packageId = "axum 0.6.20";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "base64";
+            packageId = "base64";
+          }
+          {
+            name = "bytes";
+            packageId = "bytes";
+          }
+          {
+            name = "futures-core";
+            packageId = "futures-core";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-util";
+            packageId = "futures-util";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "h2";
+            packageId = "h2 0.3.24";
+            optional = true;
+          }
+          {
+            name = "http";
+            packageId = "http 0.2.11";
+          }
+          {
+            name = "http-body";
+            packageId = "http-body 0.4.6";
+          }
+          {
+            name = "hyper";
+            packageId = "hyper 0.14.28";
+            optional = true;
+            features = [ "full" ];
+          }
+          {
+            name = "hyper-timeout";
+            packageId = "hyper-timeout";
+            optional = true;
+          }
+          {
+            name = "percent-encoding";
+            packageId = "percent-encoding";
+          }
+          {
+            name = "pin-project";
+            packageId = "pin-project";
+          }
+          {
+            name = "prost";
+            packageId = "prost 0.11.9";
+            optional = true;
+            usesDefaultFeatures = false;
+            features = [ "std" ];
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+            optional = true;
+            features = [ "net" "time" "macros" ];
+          }
+          {
+            name = "tokio-stream";
+            packageId = "tokio-stream";
+          }
+          {
+            name = "tower";
+            packageId = "tower";
+            optional = true;
+            usesDefaultFeatures = false;
+            features = [ "balance" "buffer" "discover" "limit" "load" "make" "timeout" "util" ];
+          }
+          {
+            name = "tower-layer";
+            packageId = "tower-layer";
+          }
+          {
+            name = "tower-service";
+            packageId = "tower-service";
+          }
+          {
+            name = "tracing";
+            packageId = "tracing";
+          }
+        ];
+        devDependencies = [
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "rt" "macros" ];
+          }
+          {
+            name = "tower";
+            packageId = "tower";
+            features = [ "full" ];
+          }
+        ];
+        features = {
+          "channel" = [ "dep:h2" "dep:hyper" "dep:tokio" "dep:tower" "dep:hyper-timeout" ];
+          "codegen" = [ "dep:async-trait" ];
+          "default" = [ "transport" "codegen" "prost" ];
+          "gzip" = [ "dep:flate2" ];
+          "prost" = [ "dep:prost" ];
+          "tls" = [ "dep:rustls-pemfile" "transport" "dep:tokio-rustls" "dep:async-stream" ];
+          "tls-roots" = [ "tls-roots-common" "dep:rustls-native-certs" ];
+          "tls-roots-common" = [ "tls" ];
+          "tls-webpki-roots" = [ "tls-roots-common" "dep:webpki-roots" ];
+          "transport" = [ "dep:axum" "channel" ];
+        };
+        resolvedDefaultFeatures = [ "channel" "codegen" "default" "prost" "transport" ];
+      };
+      "tonic-build" = rec {
+        crateName = "tonic-build";
+        version = "0.11.0";
+        edition = "2021";
+        sha256 = "1hm99ckaw0pzq8h22bdjy6gpbg06kpvs0f73nj60f456f3fzckmy";
+        authors = [
+          "Lucio Franco <luciofranco14@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "prettyplease";
+            packageId = "prettyplease";
+          }
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "prost-build";
+            packageId = "prost-build";
+            optional = true;
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn 2.0.48";
+          }
+        ];
+        features = {
+          "cleanup-markdown" = [ "prost" "prost-build/cleanup-markdown" ];
+          "default" = [ "transport" "prost" ];
+          "prost" = [ "prost-build" ];
+          "prost-build" = [ "dep:prost-build" ];
+        };
+        resolvedDefaultFeatures = [ "cleanup-markdown" "default" "prost" "prost-build" "transport" ];
+      };
+      "tonic-reflection" = rec {
+        crateName = "tonic-reflection";
+        version = "0.11.0";
+        edition = "2021";
+        sha256 = "19xn3kyg062d7y1cnw537v9cxkzzcrnfri0jb29fbyn0smxj532l";
+        authors = [
+          "James Nugent <james@jen20.com>"
+          "Samani G. Gikandi <samani@gojulas.com>"
+        ];
+        dependencies = [
+          {
+            name = "prost";
+            packageId = "prost 0.12.3";
+          }
+          {
+            name = "prost-types";
+            packageId = "prost-types";
+            optional = true;
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+            optional = true;
+            features = [ "sync" "rt" ];
+          }
+          {
+            name = "tokio-stream";
+            packageId = "tokio-stream";
+            optional = true;
+            features = [ "net" ];
+          }
+          {
+            name = "tonic";
+            packageId = "tonic 0.11.0";
+            usesDefaultFeatures = false;
+            features = [ "codegen" "prost" ];
+          }
+        ];
+        devDependencies = [
+          {
+            name = "tonic";
+            packageId = "tonic 0.11.0";
+            usesDefaultFeatures = false;
+            features = [ "transport" ];
+          }
+        ];
+        features = {
+          "default" = [ "server" ];
+          "prost-types" = [ "dep:prost-types" ];
+          "server" = [ "prost-types" "dep:tokio" "dep:tokio-stream" ];
+        };
+        resolvedDefaultFeatures = [ "default" "prost-types" "server" ];
+      };
+      "tower" = rec {
+        crateName = "tower";
+        version = "0.4.13";
+        edition = "2018";
+        sha256 = "073wncyqav4sak1p755hf6vl66njgfc1z1g1di9rxx3cvvh9pymq";
+        authors = [
+          "Tower Maintainers <team@tower-rs.com>"
+        ];
+        dependencies = [
+          {
+            name = "futures-core";
+            packageId = "futures-core";
+            optional = true;
+          }
+          {
+            name = "futures-util";
+            packageId = "futures-util";
+            optional = true;
+            usesDefaultFeatures = false;
+            features = [ "alloc" ];
+          }
+          {
+            name = "indexmap";
+            packageId = "indexmap 1.9.3";
+            optional = true;
+          }
+          {
+            name = "pin-project";
+            packageId = "pin-project";
+            optional = true;
+          }
+          {
+            name = "pin-project-lite";
+            packageId = "pin-project-lite";
+            optional = true;
+          }
+          {
+            name = "rand";
+            packageId = "rand";
+            optional = true;
+            features = [ "small_rng" ];
+          }
+          {
+            name = "slab";
+            packageId = "slab";
+            optional = true;
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+            optional = true;
+            features = [ "sync" ];
+          }
+          {
+            name = "tokio-util";
+            packageId = "tokio-util";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "tower-layer";
+            packageId = "tower-layer";
+          }
+          {
+            name = "tower-service";
+            packageId = "tower-service";
+          }
+          {
+            name = "tracing";
+            packageId = "tracing";
+            optional = true;
+            usesDefaultFeatures = false;
+            features = [ "std" ];
+          }
+        ];
+        devDependencies = [
+          {
+            name = "pin-project-lite";
+            packageId = "pin-project-lite";
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "macros" "sync" "test-util" "rt-multi-thread" ];
+          }
+        ];
+        features = {
+          "__common" = [ "futures-core" "pin-project-lite" ];
+          "balance" = [ "discover" "load" "ready-cache" "make" "rand" "slab" ];
+          "buffer" = [ "__common" "tokio/sync" "tokio/rt" "tokio-util" "tracing" ];
+          "default" = [ "log" ];
+          "discover" = [ "__common" ];
+          "filter" = [ "__common" "futures-util" ];
+          "full" = [ "balance" "buffer" "discover" "filter" "hedge" "limit" "load" "load-shed" "make" "ready-cache" "reconnect" "retry" "spawn-ready" "steer" "timeout" "util" ];
+          "futures-core" = [ "dep:futures-core" ];
+          "futures-util" = [ "dep:futures-util" ];
+          "hdrhistogram" = [ "dep:hdrhistogram" ];
+          "hedge" = [ "util" "filter" "futures-util" "hdrhistogram" "tokio/time" "tracing" ];
+          "indexmap" = [ "dep:indexmap" ];
+          "limit" = [ "__common" "tokio/time" "tokio/sync" "tokio-util" "tracing" ];
+          "load" = [ "__common" "tokio/time" "tracing" ];
+          "load-shed" = [ "__common" ];
+          "log" = [ "tracing/log" ];
+          "make" = [ "futures-util" "pin-project-lite" "tokio/io-std" ];
+          "pin-project" = [ "dep:pin-project" ];
+          "pin-project-lite" = [ "dep:pin-project-lite" ];
+          "rand" = [ "dep:rand" ];
+          "ready-cache" = [ "futures-core" "futures-util" "indexmap" "tokio/sync" "tracing" "pin-project-lite" ];
+          "reconnect" = [ "make" "tokio/io-std" "tracing" ];
+          "retry" = [ "__common" "tokio/time" ];
+          "slab" = [ "dep:slab" ];
+          "spawn-ready" = [ "__common" "futures-util" "tokio/sync" "tokio/rt" "util" "tracing" ];
+          "timeout" = [ "pin-project-lite" "tokio/time" ];
+          "tokio" = [ "dep:tokio" ];
+          "tokio-stream" = [ "dep:tokio-stream" ];
+          "tokio-util" = [ "dep:tokio-util" ];
+          "tracing" = [ "dep:tracing" ];
+          "util" = [ "__common" "futures-util" "pin-project" ];
+        };
+        resolvedDefaultFeatures = [ "__common" "balance" "buffer" "default" "discover" "futures-core" "futures-util" "indexmap" "limit" "load" "log" "make" "pin-project" "pin-project-lite" "rand" "ready-cache" "slab" "timeout" "tokio" "tokio-util" "tracing" "util" ];
+      };
+      "tower-layer" = rec {
+        crateName = "tower-layer";
+        version = "0.3.2";
+        edition = "2018";
+        sha256 = "1l7i17k9vlssrdg4s3b0ia5jjkmmxsvv8s9y9ih0jfi8ssz8s362";
+        authors = [
+          "Tower Maintainers <team@tower-rs.com>"
+        ];
+
+      };
+      "tower-service" = rec {
+        crateName = "tower-service";
+        version = "0.3.2";
+        edition = "2018";
+        sha256 = "0lmfzmmvid2yp2l36mbavhmqgsvzqf7r2wiwz73ml4xmwaf1rg5n";
+        authors = [
+          "Tower Maintainers <team@tower-rs.com>"
+        ];
+
+      };
+      "tracing" = rec {
+        crateName = "tracing";
+        version = "0.1.40";
+        edition = "2018";
+        sha256 = "1vv48dac9zgj9650pg2b4d0j3w6f3x9gbggf43scq5hrlysklln3";
+        authors = [
+          "Eliza Weisman <eliza@buoyant.io>"
+          "Tokio Contributors <team@tokio.rs>"
+        ];
+        dependencies = [
+          {
+            name = "log";
+            packageId = "log";
+            optional = true;
+          }
+          {
+            name = "pin-project-lite";
+            packageId = "pin-project-lite";
+          }
+          {
+            name = "tracing-attributes";
+            packageId = "tracing-attributes";
+            optional = true;
+          }
+          {
+            name = "tracing-core";
+            packageId = "tracing-core";
+            usesDefaultFeatures = false;
+          }
+        ];
+        devDependencies = [
+          {
+            name = "log";
+            packageId = "log";
+          }
+        ];
+        features = {
+          "attributes" = [ "tracing-attributes" ];
+          "default" = [ "std" "attributes" ];
+          "log" = [ "dep:log" ];
+          "log-always" = [ "log" ];
+          "std" = [ "tracing-core/std" ];
+          "tracing-attributes" = [ "dep:tracing-attributes" ];
+          "valuable" = [ "tracing-core/valuable" ];
+        };
+        resolvedDefaultFeatures = [ "attributes" "default" "log" "max_level_trace" "release_max_level_info" "std" "tracing-attributes" ];
+      };
+      "tracing-attributes" = rec {
+        crateName = "tracing-attributes";
+        version = "0.1.27";
+        edition = "2018";
+        sha256 = "1rvb5dn9z6d0xdj14r403z0af0bbaqhg02hq4jc97g5wds6lqw1l";
+        procMacro = true;
+        authors = [
+          "Tokio Contributors <team@tokio.rs>"
+          "Eliza Weisman <eliza@buoyant.io>"
+          "David Barsky <dbarsky@amazon.com>"
+        ];
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn 2.0.48";
+            usesDefaultFeatures = false;
+            features = [ "full" "parsing" "printing" "visit-mut" "clone-impls" "extra-traits" "proc-macro" ];
+          }
+        ];
+        features = { };
+      };
+      "tracing-core" = rec {
+        crateName = "tracing-core";
+        version = "0.1.32";
+        edition = "2018";
+        sha256 = "0m5aglin3cdwxpvbg6kz0r9r0k31j48n0kcfwsp6l49z26k3svf0";
+        authors = [
+          "Tokio Contributors <team@tokio.rs>"
+        ];
+        dependencies = [
+          {
+            name = "once_cell";
+            packageId = "once_cell";
+            optional = true;
+          }
+          {
+            name = "valuable";
+            packageId = "valuable";
+            optional = true;
+            usesDefaultFeatures = false;
+            target = { target, features }: (target."tracing_unstable" or false);
+          }
+        ];
+        features = {
+          "default" = [ "std" "valuable/std" ];
+          "once_cell" = [ "dep:once_cell" ];
+          "std" = [ "once_cell" ];
+          "valuable" = [ "dep:valuable" ];
+        };
+        resolvedDefaultFeatures = [ "default" "once_cell" "std" "valuable" ];
+      };
+      "tracing-futures" = rec {
+        crateName = "tracing-futures";
+        version = "0.2.5";
+        edition = "2018";
+        sha256 = "1wimg0iwa2ldq7xv98lvivvf3q9ykfminig8r1bs0ig22np9bl4p";
+        authors = [
+          "Eliza Weisman <eliza@buoyant.io>"
+          "Tokio Contributors <team@tokio.rs>"
+        ];
+        dependencies = [
+          {
+            name = "pin-project";
+            packageId = "pin-project";
+            optional = true;
+          }
+          {
+            name = "tracing";
+            packageId = "tracing";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "default" = [ "std-future" "std" ];
+          "futures" = [ "dep:futures" ];
+          "futures-01" = [ "futures_01" "std" ];
+          "futures-03" = [ "std-future" "futures" "futures-task" "std" ];
+          "futures-task" = [ "dep:futures-task" ];
+          "futures_01" = [ "dep:futures_01" ];
+          "pin-project" = [ "dep:pin-project" ];
+          "std" = [ "tracing/std" ];
+          "std-future" = [ "pin-project" ];
+          "tokio" = [ "dep:tokio" ];
+          "tokio-executor" = [ "dep:tokio-executor" ];
+        };
+        resolvedDefaultFeatures = [ "default" "pin-project" "std" "std-future" ];
+      };
+      "tracing-log" = rec {
+        crateName = "tracing-log";
+        version = "0.2.0";
+        edition = "2018";
+        sha256 = "1hs77z026k730ij1a9dhahzrl0s073gfa2hm5p0fbl0b80gmz1gf";
+        authors = [
+          "Tokio Contributors <team@tokio.rs>"
+        ];
+        dependencies = [
+          {
+            name = "log";
+            packageId = "log";
+          }
+          {
+            name = "once_cell";
+            packageId = "once_cell";
+          }
+          {
+            name = "tracing-core";
+            packageId = "tracing-core";
+          }
+        ];
+        features = {
+          "ahash" = [ "dep:ahash" ];
+          "default" = [ "log-tracer" "std" ];
+          "interest-cache" = [ "lru" "ahash" ];
+          "lru" = [ "dep:lru" ];
+          "std" = [ "log/std" ];
+        };
+        resolvedDefaultFeatures = [ "log-tracer" "std" ];
+      };
+      "tracing-opentelemetry" = rec {
+        crateName = "tracing-opentelemetry";
+        version = "0.22.0";
+        edition = "2018";
+        sha256 = "15jmwmbp6wz15bx20bfsmabx53wmlnd7wvzwz9hvkrq7aifc4yn6";
+        authors = [
+          "Julian Tescher <julian@tescher.me>"
+          "Tokio Contributors <team@tokio.rs>"
+        ];
+        dependencies = [
+          {
+            name = "js-sys";
+            packageId = "js-sys";
+            target = { target, features }: (("wasm32" == target."arch" or null) && (!("wasi" == target."os" or null)));
+          }
+          {
+            name = "once_cell";
+            packageId = "once_cell";
+          }
+          {
+            name = "opentelemetry";
+            packageId = "opentelemetry";
+            usesDefaultFeatures = false;
+            features = [ "trace" ];
+          }
+          {
+            name = "opentelemetry_sdk";
+            packageId = "opentelemetry_sdk";
+            usesDefaultFeatures = false;
+            features = [ "trace" ];
+          }
+          {
+            name = "smallvec";
+            packageId = "smallvec";
+            optional = true;
+          }
+          {
+            name = "tracing";
+            packageId = "tracing";
+            usesDefaultFeatures = false;
+            features = [ "std" ];
+          }
+          {
+            name = "tracing-core";
+            packageId = "tracing-core";
+          }
+          {
+            name = "tracing-log";
+            packageId = "tracing-log";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "tracing-subscriber";
+            packageId = "tracing-subscriber";
+            usesDefaultFeatures = false;
+            features = [ "registry" "std" ];
+          }
+          {
+            name = "web-time";
+            packageId = "web-time";
+            target = { target, features }: (("wasm32" == target."arch" or null) && (!("wasi" == target."os" or null)));
+          }
+        ];
+        devDependencies = [
+          {
+            name = "opentelemetry";
+            packageId = "opentelemetry";
+            features = [ "trace" "metrics" ];
+          }
+          {
+            name = "opentelemetry_sdk";
+            packageId = "opentelemetry_sdk";
+            usesDefaultFeatures = false;
+            features = [ "trace" "rt-tokio" ];
+          }
+          {
+            name = "tracing";
+            packageId = "tracing";
+            usesDefaultFeatures = false;
+            features = [ "std" "attributes" ];
+          }
+          {
+            name = "tracing-subscriber";
+            packageId = "tracing-subscriber";
+            usesDefaultFeatures = false;
+            features = [ "registry" "std" "fmt" ];
+          }
+        ];
+        features = {
+          "async-trait" = [ "dep:async-trait" ];
+          "default" = [ "tracing-log" "metrics" ];
+          "metrics" = [ "opentelemetry/metrics" "opentelemetry_sdk/metrics" "smallvec" ];
+          "smallvec" = [ "dep:smallvec" ];
+          "thiserror" = [ "dep:thiserror" ];
+          "tracing-log" = [ "dep:tracing-log" ];
+        };
+        resolvedDefaultFeatures = [ "default" "metrics" "smallvec" "tracing-log" ];
+      };
+      "tracing-serde" = rec {
+        crateName = "tracing-serde";
+        version = "0.1.3";
+        edition = "2018";
+        sha256 = "1qfr0va69djvxqvjrx4vqq7p6myy414lx4w1f6amcn0hfwqj2sxw";
+        authors = [
+          "Tokio Contributors <team@tokio.rs>"
+        ];
+        dependencies = [
+          {
+            name = "serde";
+            packageId = "serde";
+          }
+          {
+            name = "tracing-core";
+            packageId = "tracing-core";
+          }
+        ];
+        features = {
+          "valuable" = [ "valuable_crate" "valuable-serde" "tracing-core/valuable" ];
+          "valuable-serde" = [ "dep:valuable-serde" ];
+          "valuable_crate" = [ "dep:valuable_crate" ];
+        };
+      };
+      "tracing-subscriber" = rec {
+        crateName = "tracing-subscriber";
+        version = "0.3.18";
+        edition = "2018";
+        sha256 = "12vs1bwk4kig1l2qqjbbn2nm5amwiqmkcmnznylzmnfvjy6083xd";
+        authors = [
+          "Eliza Weisman <eliza@buoyant.io>"
+          "David Barsky <me@davidbarsky.com>"
+          "Tokio Contributors <team@tokio.rs>"
+        ];
+        dependencies = [
+          {
+            name = "matchers";
+            packageId = "matchers";
+            optional = true;
+          }
+          {
+            name = "nu-ansi-term";
+            packageId = "nu-ansi-term";
+            optional = true;
+          }
+          {
+            name = "once_cell";
+            packageId = "once_cell";
+            optional = true;
+          }
+          {
+            name = "regex";
+            packageId = "regex";
+            optional = true;
+            usesDefaultFeatures = false;
+            features = [ "std" "unicode-case" "unicode-perl" ];
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+            optional = true;
+          }
+          {
+            name = "serde_json";
+            packageId = "serde_json";
+            optional = true;
+          }
+          {
+            name = "sharded-slab";
+            packageId = "sharded-slab";
+            optional = true;
+          }
+          {
+            name = "smallvec";
+            packageId = "smallvec";
+            optional = true;
+          }
+          {
+            name = "thread_local";
+            packageId = "thread_local";
+            optional = true;
+          }
+          {
+            name = "tracing";
+            packageId = "tracing";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "tracing-core";
+            packageId = "tracing-core";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "tracing-log";
+            packageId = "tracing-log";
+            optional = true;
+            usesDefaultFeatures = false;
+            features = [ "log-tracer" "std" ];
+          }
+          {
+            name = "tracing-serde";
+            packageId = "tracing-serde";
+            optional = true;
+          }
+        ];
+        devDependencies = [
+          {
+            name = "regex";
+            packageId = "regex";
+            usesDefaultFeatures = false;
+            features = [ "std" ];
+          }
+          {
+            name = "tracing";
+            packageId = "tracing";
+          }
+          {
+            name = "tracing-log";
+            packageId = "tracing-log";
+          }
+        ];
+        features = {
+          "ansi" = [ "fmt" "nu-ansi-term" ];
+          "chrono" = [ "dep:chrono" ];
+          "default" = [ "smallvec" "fmt" "ansi" "tracing-log" "std" ];
+          "env-filter" = [ "matchers" "regex" "once_cell" "tracing" "std" "thread_local" ];
+          "fmt" = [ "registry" "std" ];
+          "json" = [ "tracing-serde" "serde" "serde_json" ];
+          "local-time" = [ "time/local-offset" ];
+          "matchers" = [ "dep:matchers" ];
+          "nu-ansi-term" = [ "dep:nu-ansi-term" ];
+          "once_cell" = [ "dep:once_cell" ];
+          "parking_lot" = [ "dep:parking_lot" ];
+          "regex" = [ "dep:regex" ];
+          "registry" = [ "sharded-slab" "thread_local" "std" ];
+          "serde" = [ "dep:serde" ];
+          "serde_json" = [ "dep:serde_json" ];
+          "sharded-slab" = [ "dep:sharded-slab" ];
+          "smallvec" = [ "dep:smallvec" ];
+          "std" = [ "alloc" "tracing-core/std" ];
+          "thread_local" = [ "dep:thread_local" ];
+          "time" = [ "dep:time" ];
+          "tracing" = [ "dep:tracing" ];
+          "tracing-log" = [ "dep:tracing-log" ];
+          "tracing-serde" = [ "dep:tracing-serde" ];
+          "valuable" = [ "tracing-core/valuable" "valuable_crate" "valuable-serde" "tracing-serde/valuable" ];
+          "valuable-serde" = [ "dep:valuable-serde" ];
+          "valuable_crate" = [ "dep:valuable_crate" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "ansi" "default" "env-filter" "fmt" "json" "matchers" "nu-ansi-term" "once_cell" "regex" "registry" "serde" "serde_json" "sharded-slab" "smallvec" "std" "thread_local" "tracing" "tracing-log" "tracing-serde" ];
+      };
+      "try-lock" = rec {
+        crateName = "try-lock";
+        version = "0.2.5";
+        edition = "2015";
+        sha256 = "0jqijrrvm1pyq34zn1jmy2vihd4jcrjlvsh4alkjahhssjnsn8g4";
+        authors = [
+          "Sean McArthur <sean@seanmonstar.com>"
+        ];
+
+      };
+      "tvix-build" = rec {
+        crateName = "tvix-build";
+        version = "0.1.0";
+        edition = "2021";
+        crateBin = [
+          {
+            name = "tvix-build";
+            path = "src/bin/tvix-build.rs";
+            requiredFeatures = [ ];
+          }
+        ];
+        # We can't filter paths with references in Nix 2.4
+        # See https://github.com/NixOS/nix/issues/5410
+        src =
+          if ((lib.versionOlder builtins.nixVersion "2.4pre20211007") || (lib.versionOlder "2.5" builtins.nixVersion))
+          then lib.cleanSourceWith { filter = sourceFilter; src = ./build; }
+          else ./build;
+        dependencies = [
+          {
+            name = "bytes";
+            packageId = "bytes";
+          }
+          {
+            name = "clap";
+            packageId = "clap";
+            features = [ "derive" "env" ];
+          }
+          {
+            name = "itertools";
+            packageId = "itertools 0.12.0";
+          }
+          {
+            name = "prost";
+            packageId = "prost 0.12.3";
+          }
+          {
+            name = "thiserror";
+            packageId = "thiserror";
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+          }
+          {
+            name = "tokio-listener";
+            packageId = "tokio-listener";
+            features = [ "tonic011" ];
+          }
+          {
+            name = "tonic";
+            packageId = "tonic 0.11.0";
+            features = [ "tls" "tls-roots" ];
+          }
+          {
+            name = "tonic-reflection";
+            packageId = "tonic-reflection";
+            optional = true;
+          }
+          {
+            name = "tracing";
+            packageId = "tracing";
+          }
+          {
+            name = "tracing-subscriber";
+            packageId = "tracing-subscriber";
+            features = [ "json" ];
+          }
+          {
+            name = "tvix-castore";
+            packageId = "tvix-castore";
+          }
+          {
+            name = "url";
+            packageId = "url";
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "prost-build";
+            packageId = "prost-build";
+          }
+          {
+            name = "tonic-build";
+            packageId = "tonic-build";
+          }
+        ];
+        devDependencies = [
+          {
+            name = "rstest";
+            packageId = "rstest";
+          }
+        ];
+        features = {
+          "tonic-reflection" = [ "dep:tonic-reflection" ];
+        };
+        resolvedDefaultFeatures = [ "default" "tonic-reflection" ];
+      };
+      "tvix-castore" = rec {
+        crateName = "tvix-castore";
+        version = "0.1.0";
+        edition = "2021";
+        # We can't filter paths with references in Nix 2.4
+        # See https://github.com/NixOS/nix/issues/5410
+        src =
+          if ((lib.versionOlder builtins.nixVersion "2.4pre20211007") || (lib.versionOlder "2.5" builtins.nixVersion))
+          then lib.cleanSourceWith { filter = sourceFilter; src = ./castore; }
+          else ./castore;
+        dependencies = [
+          {
+            name = "async-stream";
+            packageId = "async-stream";
+          }
+          {
+            name = "async-tempfile";
+            packageId = "async-tempfile";
+          }
+          {
+            name = "bigtable_rs";
+            packageId = "bigtable_rs";
+            optional = true;
+          }
+          {
+            name = "blake3";
+            packageId = "blake3";
+            features = [ "rayon" "std" "traits-preview" ];
+          }
+          {
+            name = "bstr";
+            packageId = "bstr";
+          }
+          {
+            name = "bytes";
+            packageId = "bytes";
+          }
+          {
+            name = "data-encoding";
+            packageId = "data-encoding";
+          }
+          {
+            name = "digest";
+            packageId = "digest";
+          }
+          {
+            name = "fastcdc";
+            packageId = "fastcdc";
+            features = [ "tokio" ];
+          }
+          {
+            name = "fuse-backend-rs";
+            packageId = "fuse-backend-rs";
+            optional = true;
+          }
+          {
+            name = "futures";
+            packageId = "futures";
+          }
+          {
+            name = "lazy_static";
+            packageId = "lazy_static";
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            optional = true;
+          }
+          {
+            name = "object_store";
+            packageId = "object_store";
+            features = [ "http" ];
+          }
+          {
+            name = "parking_lot";
+            packageId = "parking_lot 0.12.1";
+          }
+          {
+            name = "petgraph";
+            packageId = "petgraph";
+          }
+          {
+            name = "pin-project-lite";
+            packageId = "pin-project-lite";
+          }
+          {
+            name = "prost";
+            packageId = "prost 0.12.3";
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+            features = [ "derive" ];
+          }
+          {
+            name = "serde_qs";
+            packageId = "serde_qs";
+          }
+          {
+            name = "serde_with";
+            packageId = "serde_with";
+          }
+          {
+            name = "sled";
+            packageId = "sled";
+          }
+          {
+            name = "thiserror";
+            packageId = "thiserror";
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "fs" "macros" "net" "rt" "rt-multi-thread" "signal" ];
+          }
+          {
+            name = "tokio-stream";
+            packageId = "tokio-stream";
+            features = [ "fs" "net" ];
+          }
+          {
+            name = "tokio-tar";
+            packageId = "tokio-tar";
+          }
+          {
+            name = "tokio-util";
+            packageId = "tokio-util";
+            features = [ "io" "io-util" ];
+          }
+          {
+            name = "tonic";
+            packageId = "tonic 0.11.0";
+          }
+          {
+            name = "tonic-reflection";
+            packageId = "tonic-reflection";
+            optional = true;
+          }
+          {
+            name = "tower";
+            packageId = "tower";
+          }
+          {
+            name = "tracing";
+            packageId = "tracing";
+          }
+          {
+            name = "url";
+            packageId = "url";
+          }
+          {
+            name = "vhost";
+            packageId = "vhost";
+            optional = true;
+          }
+          {
+            name = "vhost-user-backend";
+            packageId = "vhost-user-backend";
+            optional = true;
+          }
+          {
+            name = "virtio-bindings";
+            packageId = "virtio-bindings 0.2.2";
+            optional = true;
+          }
+          {
+            name = "virtio-queue";
+            packageId = "virtio-queue";
+            optional = true;
+          }
+          {
+            name = "vm-memory";
+            packageId = "vm-memory";
+            optional = true;
+          }
+          {
+            name = "vmm-sys-util";
+            packageId = "vmm-sys-util";
+            optional = true;
+          }
+          {
+            name = "walkdir";
+            packageId = "walkdir";
+          }
+          {
+            name = "zstd";
+            packageId = "zstd";
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "prost-build";
+            packageId = "prost-build";
+          }
+          {
+            name = "tonic-build";
+            packageId = "tonic-build";
+          }
+        ];
+        devDependencies = [
+          {
+            name = "async-process";
+            packageId = "async-process";
+          }
+          {
+            name = "hex-literal";
+            packageId = "hex-literal";
+          }
+          {
+            name = "rstest";
+            packageId = "rstest";
+          }
+          {
+            name = "rstest_reuse";
+            packageId = "rstest_reuse";
+          }
+          {
+            name = "tempfile";
+            packageId = "tempfile";
+          }
+          {
+            name = "tokio-retry";
+            packageId = "tokio-retry";
+          }
+          {
+            name = "xattr";
+            packageId = "xattr";
+          }
+        ];
+        features = {
+          "cloud" = [ "dep:bigtable_rs" "object_store/aws" "object_store/azure" "object_store/gcp" ];
+          "fs" = [ "dep:libc" "dep:fuse-backend-rs" ];
+          "fuse" = [ "fs" ];
+          "tonic-reflection" = [ "dep:tonic-reflection" ];
+          "virtiofs" = [ "fs" "dep:vhost" "dep:vhost-user-backend" "dep:virtio-queue" "dep:vm-memory" "dep:vmm-sys-util" "dep:virtio-bindings" "fuse-backend-rs?/vhost-user-fs" "fuse-backend-rs?/virtiofs" ];
+        };
+        resolvedDefaultFeatures = [ "cloud" "default" "fs" "fuse" "integration" "tonic-reflection" "virtiofs" ];
+      };
+      "tvix-cli" = rec {
+        crateName = "tvix-cli";
+        version = "0.1.0";
+        edition = "2021";
+        crateBin = [
+          {
+            name = "tvix";
+            path = "src/main.rs";
+            requiredFeatures = [ ];
+          }
+        ];
+        # We can't filter paths with references in Nix 2.4
+        # See https://github.com/NixOS/nix/issues/5410
+        src =
+          if ((lib.versionOlder builtins.nixVersion "2.4pre20211007") || (lib.versionOlder "2.5" builtins.nixVersion))
+          then lib.cleanSourceWith { filter = sourceFilter; src = ./cli; }
+          else ./cli;
+        dependencies = [
+          {
+            name = "bytes";
+            packageId = "bytes";
+          }
+          {
+            name = "clap";
+            packageId = "clap";
+            features = [ "derive" "env" ];
+          }
+          {
+            name = "dirs";
+            packageId = "dirs";
+          }
+          {
+            name = "nix-compat";
+            packageId = "nix-compat";
+          }
+          {
+            name = "rustyline";
+            packageId = "rustyline";
+          }
+          {
+            name = "thiserror";
+            packageId = "thiserror";
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+          }
+          {
+            name = "tracing";
+            packageId = "tracing";
+            features = [ "max_level_trace" "release_max_level_info" ];
+          }
+          {
+            name = "tracing-subscriber";
+            packageId = "tracing-subscriber";
+            features = [ "json" ];
+          }
+          {
+            name = "tvix-build";
+            packageId = "tvix-build";
+          }
+          {
+            name = "tvix-castore";
+            packageId = "tvix-castore";
+          }
+          {
+            name = "tvix-eval";
+            packageId = "tvix-eval";
+          }
+          {
+            name = "tvix-glue";
+            packageId = "tvix-glue";
+          }
+          {
+            name = "tvix-store";
+            packageId = "tvix-store";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "wu-manber";
+            packageId = "wu-manber";
+          }
+        ];
+
+      };
+      "tvix-eval" = rec {
+        crateName = "tvix-eval";
+        version = "0.1.0";
+        edition = "2021";
+        # We can't filter paths with references in Nix 2.4
+        # See https://github.com/NixOS/nix/issues/5410
+        src =
+          if ((lib.versionOlder builtins.nixVersion "2.4pre20211007") || (lib.versionOlder "2.5" builtins.nixVersion))
+          then lib.cleanSourceWith { filter = sourceFilter; src = ./eval; }
+          else ./eval;
+        libName = "tvix_eval";
+        dependencies = [
+          {
+            name = "bstr";
+            packageId = "bstr";
+            features = [ "serde" ];
+          }
+          {
+            name = "bytes";
+            packageId = "bytes";
+          }
+          {
+            name = "codemap";
+            packageId = "codemap";
+          }
+          {
+            name = "codemap-diagnostic";
+            packageId = "codemap-diagnostic";
+          }
+          {
+            name = "data-encoding";
+            packageId = "data-encoding";
+          }
+          {
+            name = "dirs";
+            packageId = "dirs";
+          }
+          {
+            name = "genawaiter";
+            packageId = "genawaiter";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "imbl";
+            packageId = "imbl";
+            features = [ "serde" ];
+          }
+          {
+            name = "itertools";
+            packageId = "itertools 0.12.0";
+          }
+          {
+            name = "lazy_static";
+            packageId = "lazy_static";
+          }
+          {
+            name = "lexical-core";
+            packageId = "lexical-core";
+            features = [ "format" "parse-floats" ];
+          }
+          {
+            name = "md-5";
+            packageId = "md-5";
+          }
+          {
+            name = "os_str_bytes";
+            packageId = "os_str_bytes";
+            features = [ "conversions" ];
+          }
+          {
+            name = "path-clean";
+            packageId = "path-clean";
+          }
+          {
+            name = "proptest";
+            packageId = "proptest";
+            optional = true;
+            usesDefaultFeatures = false;
+            features = [ "std" "alloc" "tempfile" ];
+          }
+          {
+            name = "regex";
+            packageId = "regex";
+          }
+          {
+            name = "rnix";
+            packageId = "rnix";
+          }
+          {
+            name = "rowan";
+            packageId = "rowan";
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+            features = [ "rc" "derive" ];
+          }
+          {
+            name = "serde_json";
+            packageId = "serde_json";
+          }
+          {
+            name = "sha1";
+            packageId = "sha1";
+          }
+          {
+            name = "sha2";
+            packageId = "sha2";
+          }
+          {
+            name = "smol_str";
+            packageId = "smol_str";
+          }
+          {
+            name = "tabwriter";
+            packageId = "tabwriter";
+          }
+          {
+            name = "test-strategy";
+            packageId = "test-strategy";
+            optional = true;
+          }
+          {
+            name = "toml";
+            packageId = "toml";
+          }
+          {
+            name = "tvix-eval-builtin-macros";
+            packageId = "tvix-eval-builtin-macros";
+            rename = "builtin-macros";
+          }
+          {
+            name = "xml-rs";
+            packageId = "xml-rs";
+          }
+        ];
+        devDependencies = [
+          {
+            name = "criterion";
+            packageId = "criterion";
+          }
+          {
+            name = "itertools";
+            packageId = "itertools 0.12.0";
+          }
+          {
+            name = "pretty_assertions";
+            packageId = "pretty_assertions";
+          }
+          {
+            name = "rstest";
+            packageId = "rstest";
+          }
+          {
+            name = "tempfile";
+            packageId = "tempfile";
+          }
+        ];
+        features = {
+          "arbitrary" = [ "proptest" "test-strategy" "imbl/proptest" ];
+          "default" = [ "impure" "arbitrary" "nix_tests" ];
+          "proptest" = [ "dep:proptest" ];
+          "test-strategy" = [ "dep:test-strategy" ];
+        };
+        resolvedDefaultFeatures = [ "arbitrary" "default" "impure" "nix_tests" "proptest" "test-strategy" ];
+      };
+      "tvix-eval-builtin-macros" = rec {
+        crateName = "tvix-eval-builtin-macros";
+        version = "0.0.1";
+        edition = "2021";
+        # We can't filter paths with references in Nix 2.4
+        # See https://github.com/NixOS/nix/issues/5410
+        src =
+          if ((lib.versionOlder builtins.nixVersion "2.4pre20211007") || (lib.versionOlder "2.5" builtins.nixVersion))
+          then lib.cleanSourceWith { filter = sourceFilter; src = ./eval/builtin-macros; }
+          else ./eval/builtin-macros;
+        procMacro = true;
+        authors = [
+          "Griffin Smith <root@gws.fyi>"
+        ];
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn 1.0.109";
+            features = [ "full" "parsing" "printing" "visit" "visit-mut" "extra-traits" ];
+          }
+        ];
+        devDependencies = [
+          {
+            name = "tvix-eval";
+            packageId = "tvix-eval";
+          }
+        ];
+
+      };
+      "tvix-glue" = rec {
+        crateName = "tvix-glue";
+        version = "0.1.0";
+        edition = "2021";
+        # We can't filter paths with references in Nix 2.4
+        # See https://github.com/NixOS/nix/issues/5410
+        src =
+          if ((lib.versionOlder builtins.nixVersion "2.4pre20211007") || (lib.versionOlder "2.5" builtins.nixVersion))
+          then lib.cleanSourceWith { filter = sourceFilter; src = ./glue; }
+          else ./glue;
+        dependencies = [
+          {
+            name = "async-compression";
+            packageId = "async-compression";
+            features = [ "tokio" "gzip" "bzip2" "xz" ];
+          }
+          {
+            name = "bstr";
+            packageId = "bstr";
+          }
+          {
+            name = "bytes";
+            packageId = "bytes";
+          }
+          {
+            name = "data-encoding";
+            packageId = "data-encoding";
+          }
+          {
+            name = "futures";
+            packageId = "futures";
+          }
+          {
+            name = "magic";
+            packageId = "magic";
+          }
+          {
+            name = "md-5";
+            packageId = "md-5";
+          }
+          {
+            name = "nix-compat";
+            packageId = "nix-compat";
+          }
+          {
+            name = "pin-project";
+            packageId = "pin-project";
+          }
+          {
+            name = "reqwest";
+            packageId = "reqwest";
+            usesDefaultFeatures = false;
+            features = [ "rustls-tls-native-roots" ];
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+          }
+          {
+            name = "serde_json";
+            packageId = "serde_json";
+          }
+          {
+            name = "sha1";
+            packageId = "sha1";
+          }
+          {
+            name = "sha2";
+            packageId = "sha2";
+          }
+          {
+            name = "thiserror";
+            packageId = "thiserror";
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+          }
+          {
+            name = "tokio-tar";
+            packageId = "tokio-tar";
+          }
+          {
+            name = "tokio-util";
+            packageId = "tokio-util";
+            features = [ "io" "io-util" "compat" ];
+          }
+          {
+            name = "tracing";
+            packageId = "tracing";
+          }
+          {
+            name = "tvix-build";
+            packageId = "tvix-build";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "tvix-castore";
+            packageId = "tvix-castore";
+          }
+          {
+            name = "tvix-eval";
+            packageId = "tvix-eval";
+          }
+          {
+            name = "tvix-store";
+            packageId = "tvix-store";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "url";
+            packageId = "url";
+          }
+          {
+            name = "walkdir";
+            packageId = "walkdir";
+          }
+          {
+            name = "wu-manber";
+            packageId = "wu-manber";
+          }
+        ];
+        devDependencies = [
+          {
+            name = "criterion";
+            packageId = "criterion";
+            features = [ "html_reports" ];
+          }
+          {
+            name = "hex-literal";
+            packageId = "hex-literal";
+          }
+          {
+            name = "lazy_static";
+            packageId = "lazy_static";
+          }
+          {
+            name = "nix";
+            packageId = "nix 0.27.1";
+            features = [ "fs" ];
+          }
+          {
+            name = "pretty_assertions";
+            packageId = "pretty_assertions";
+          }
+          {
+            name = "rstest";
+            packageId = "rstest";
+          }
+          {
+            name = "tempfile";
+            packageId = "tempfile";
+          }
+        ];
+        features = {
+          "default" = [ "nix_tests" ];
+        };
+        resolvedDefaultFeatures = [ "default" "nix_tests" ];
+      };
+      "tvix-serde" = rec {
+        crateName = "tvix-serde";
+        version = "0.1.0";
+        edition = "2021";
+        # We can't filter paths with references in Nix 2.4
+        # See https://github.com/NixOS/nix/issues/5410
+        src =
+          if ((lib.versionOlder builtins.nixVersion "2.4pre20211007") || (lib.versionOlder "2.5" builtins.nixVersion))
+          then lib.cleanSourceWith { filter = sourceFilter; src = ./serde; }
+          else ./serde;
+        dependencies = [
+          {
+            name = "bstr";
+            packageId = "bstr";
+            features = [ "serde" ];
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+            features = [ "derive" ];
+          }
+          {
+            name = "tvix-eval";
+            packageId = "tvix-eval";
+          }
+        ];
+
+      };
+      "tvix-store" = rec {
+        crateName = "tvix-store";
+        version = "0.1.0";
+        edition = "2021";
+        crateBin = [
+          {
+            name = "tvix-store";
+            path = "src/bin/tvix-store.rs";
+            requiredFeatures = [ ];
+          }
+        ];
+        # We can't filter paths with references in Nix 2.4
+        # See https://github.com/NixOS/nix/issues/5410
+        src =
+          if ((lib.versionOlder builtins.nixVersion "2.4pre20211007") || (lib.versionOlder "2.5" builtins.nixVersion))
+          then lib.cleanSourceWith { filter = sourceFilter; src = ./store; }
+          else ./store;
+        dependencies = [
+          {
+            name = "anyhow";
+            packageId = "anyhow";
+          }
+          {
+            name = "async-compression";
+            packageId = "async-compression";
+            features = [ "tokio" "bzip2" "gzip" "xz" "zstd" ];
+          }
+          {
+            name = "async-stream";
+            packageId = "async-stream";
+          }
+          {
+            name = "bigtable_rs";
+            packageId = "bigtable_rs";
+            optional = true;
+          }
+          {
+            name = "blake3";
+            packageId = "blake3";
+            features = [ "rayon" "std" ];
+          }
+          {
+            name = "bstr";
+            packageId = "bstr";
+          }
+          {
+            name = "bytes";
+            packageId = "bytes";
+          }
+          {
+            name = "clap";
+            packageId = "clap";
+            features = [ "derive" "env" ];
+          }
+          {
+            name = "count-write";
+            packageId = "count-write";
+          }
+          {
+            name = "data-encoding";
+            packageId = "data-encoding";
+          }
+          {
+            name = "futures";
+            packageId = "futures";
+          }
+          {
+            name = "lazy_static";
+            packageId = "lazy_static";
+          }
+          {
+            name = "nix-compat";
+            packageId = "nix-compat";
+            features = [ "async" ];
+          }
+          {
+            name = "opentelemetry";
+            packageId = "opentelemetry";
+            optional = true;
+          }
+          {
+            name = "opentelemetry-otlp";
+            packageId = "opentelemetry-otlp";
+            optional = true;
+          }
+          {
+            name = "opentelemetry_sdk";
+            packageId = "opentelemetry_sdk";
+            optional = true;
+            features = [ "rt-tokio" ];
+          }
+          {
+            name = "pin-project-lite";
+            packageId = "pin-project-lite";
+          }
+          {
+            name = "prost";
+            packageId = "prost 0.12.3";
+          }
+          {
+            name = "reqwest";
+            packageId = "reqwest";
+            usesDefaultFeatures = false;
+            features = [ "rustls-tls-native-roots" "stream" ];
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+            features = [ "derive" ];
+          }
+          {
+            name = "serde_json";
+            packageId = "serde_json";
+          }
+          {
+            name = "serde_qs";
+            packageId = "serde_qs";
+          }
+          {
+            name = "serde_with";
+            packageId = "serde_with";
+          }
+          {
+            name = "sha2";
+            packageId = "sha2";
+          }
+          {
+            name = "sled";
+            packageId = "sled";
+          }
+          {
+            name = "thiserror";
+            packageId = "thiserror";
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "fs" "macros" "net" "rt" "rt-multi-thread" "signal" ];
+          }
+          {
+            name = "tokio-listener";
+            packageId = "tokio-listener";
+            features = [ "tonic011" ];
+          }
+          {
+            name = "tokio-stream";
+            packageId = "tokio-stream";
+            features = [ "fs" ];
+          }
+          {
+            name = "tokio-util";
+            packageId = "tokio-util";
+            features = [ "io" "io-util" "compat" ];
+          }
+          {
+            name = "tonic";
+            packageId = "tonic 0.11.0";
+            features = [ "tls" "tls-roots" ];
+          }
+          {
+            name = "tonic-reflection";
+            packageId = "tonic-reflection";
+            optional = true;
+          }
+          {
+            name = "tower";
+            packageId = "tower";
+          }
+          {
+            name = "tracing";
+            packageId = "tracing";
+          }
+          {
+            name = "tracing-opentelemetry";
+            packageId = "tracing-opentelemetry";
+          }
+          {
+            name = "tracing-subscriber";
+            packageId = "tracing-subscriber";
+            features = [ "env-filter" "json" ];
+          }
+          {
+            name = "tvix-castore";
+            packageId = "tvix-castore";
+          }
+          {
+            name = "url";
+            packageId = "url";
+          }
+          {
+            name = "walkdir";
+            packageId = "walkdir";
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "prost-build";
+            packageId = "prost-build";
+          }
+          {
+            name = "tonic-build";
+            packageId = "tonic-build";
+          }
+        ];
+        devDependencies = [
+          {
+            name = "async-process";
+            packageId = "async-process";
+          }
+          {
+            name = "rstest";
+            packageId = "rstest";
+          }
+          {
+            name = "rstest_reuse";
+            packageId = "rstest_reuse";
+          }
+          {
+            name = "tempfile";
+            packageId = "tempfile";
+          }
+          {
+            name = "tokio-retry";
+            packageId = "tokio-retry";
+          }
+        ];
+        features = {
+          "cloud" = [ "dep:bigtable_rs" "tvix-castore/cloud" ];
+          "default" = [ "cloud" "fuse" "otlp" "tonic-reflection" ];
+          "fuse" = [ "tvix-castore/fuse" ];
+          "otlp" = [ "dep:opentelemetry" "dep:opentelemetry-otlp" "dep:opentelemetry_sdk" ];
+          "tonic-reflection" = [ "dep:tonic-reflection" "tvix-castore/tonic-reflection" ];
+          "virtiofs" = [ "tvix-castore/virtiofs" ];
+        };
+        resolvedDefaultFeatures = [ "cloud" "default" "fuse" "integration" "otlp" "tonic-reflection" "virtiofs" ];
+      };
+      "typenum" = rec {
+        crateName = "typenum";
+        version = "1.17.0";
+        edition = "2018";
+        sha256 = "09dqxv69m9lj9zvv6xw5vxaqx15ps0vxyy5myg33i0kbqvq0pzs2";
+        build = "build/main.rs";
+        authors = [
+          "Paho Lurie-Gregg <paho@paholg.com>"
+          "Andre Bogus <bogusandre@gmail.com>"
+        ];
+        features = {
+          "scale-info" = [ "dep:scale-info" ];
+          "scale_info" = [ "scale-info/derive" ];
+        };
+      };
+      "typetag" = rec {
+        crateName = "typetag";
+        version = "0.2.16";
+        edition = "2021";
+        sha256 = "1bwswa9ah2sc6fmlfw2pim73rr1n6vhfwmidrsga8cn09r0ih7b6";
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "erased-serde";
+            packageId = "erased-serde";
+            usesDefaultFeatures = false;
+            features = [ "alloc" ];
+          }
+          {
+            name = "inventory";
+            packageId = "inventory";
+          }
+          {
+            name = "once_cell";
+            packageId = "once_cell";
+            usesDefaultFeatures = false;
+            features = [ "alloc" ];
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+            usesDefaultFeatures = false;
+            features = [ "alloc" "derive" ];
+          }
+          {
+            name = "typetag-impl";
+            packageId = "typetag-impl";
+          }
+        ];
+
+      };
+      "typetag-impl" = rec {
+        crateName = "typetag-impl";
+        version = "0.2.16";
+        edition = "2021";
+        sha256 = "1cabnvm526bcgh1sh34js5ils0gz4xwlgvwhm992acdr8xzqhwxc";
+        procMacro = true;
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn 2.0.48";
+            features = [ "full" ];
+          }
+        ];
+
+      };
+      "unarray" = rec {
+        crateName = "unarray";
+        version = "0.1.4";
+        edition = "2018";
+        sha256 = "154smf048k84prsdgh09nkm2n0w0336v84jd4zikyn6v6jrqbspa";
+
+      };
+      "unicase" = rec {
+        crateName = "unicase";
+        version = "2.7.0";
+        edition = "2015";
+        sha256 = "12gd74j79f94k4clxpf06l99wiv4p30wjr0qm04ihqk9zgdd9lpp";
+        authors = [
+          "Sean McArthur <sean@seanmonstar.com>"
+        ];
+        buildDependencies = [
+          {
+            name = "version_check";
+            packageId = "version_check";
+          }
+        ];
+        features = { };
+      };
+      "unicode-bidi" = rec {
+        crateName = "unicode-bidi";
+        version = "0.3.15";
+        edition = "2018";
+        sha256 = "0xcdxm7h0ydyprwpcbh436rbs6s6lph7f3gr527lzgv6lw053y88";
+        libName = "unicode_bidi";
+        authors = [
+          "The Servo Project Developers"
+        ];
+        features = {
+          "default" = [ "std" "hardcoded-data" ];
+          "flame" = [ "dep:flame" ];
+          "flame_it" = [ "flame" "flamer" ];
+          "flamer" = [ "dep:flamer" ];
+          "serde" = [ "dep:serde" ];
+          "with_serde" = [ "serde" ];
+        };
+        resolvedDefaultFeatures = [ "hardcoded-data" "std" ];
+      };
+      "unicode-ident" = rec {
+        crateName = "unicode-ident";
+        version = "1.0.12";
+        edition = "2018";
+        sha256 = "0jzf1znfpb2gx8nr8mvmyqs1crnv79l57nxnbiszc7xf7ynbjm1k";
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+
+      };
+      "unicode-normalization" = rec {
+        crateName = "unicode-normalization";
+        version = "0.1.22";
+        edition = "2018";
+        sha256 = "08d95g7b1irc578b2iyhzv4xhsa4pfvwsqxcl9lbcpabzkq16msw";
+        authors = [
+          "kwantam <kwantam@gmail.com>"
+          "Manish Goregaokar <manishsmail@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "tinyvec";
+            packageId = "tinyvec";
+            features = [ "alloc" ];
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "std" ];
+      };
+      "unicode-segmentation" = rec {
+        crateName = "unicode-segmentation";
+        version = "1.10.1";
+        edition = "2018";
+        sha256 = "0dky2hm5k51xy11hc3nk85p533rvghd462b6i0c532b7hl4j9mhx";
+        authors = [
+          "kwantam <kwantam@gmail.com>"
+          "Manish Goregaokar <manishsmail@gmail.com>"
+        ];
+        features = { };
+      };
+      "unicode-width" = rec {
+        crateName = "unicode-width";
+        version = "0.1.11";
+        edition = "2015";
+        sha256 = "11ds4ydhg8g7l06rlmh712q41qsrd0j0h00n1jm74kww3kqk65z5";
+        authors = [
+          "kwantam <kwantam@gmail.com>"
+          "Manish Goregaokar <manishsmail@gmail.com>"
+        ];
+        features = {
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "core" = [ "dep:core" ];
+          "rustc-dep-of-std" = [ "std" "core" "compiler_builtins" ];
+          "std" = [ "dep:std" ];
+        };
+        resolvedDefaultFeatures = [ "default" ];
+      };
+      "untrusted" = rec {
+        crateName = "untrusted";
+        version = "0.9.0";
+        edition = "2018";
+        sha256 = "1ha7ib98vkc538x0z60gfn0fc5whqdd85mb87dvisdcaifi6vjwf";
+        authors = [
+          "Brian Smith <brian@briansmith.org>"
+        ];
+
+      };
+      "url" = rec {
+        crateName = "url";
+        version = "2.5.0";
+        edition = "2018";
+        sha256 = "0cs65961miawncdg2z20171w0vqrmraswv2ihdpd8lxp7cp31rii";
+        authors = [
+          "The rust-url developers"
+        ];
+        dependencies = [
+          {
+            name = "form_urlencoded";
+            packageId = "form_urlencoded";
+          }
+          {
+            name = "idna";
+            packageId = "idna";
+          }
+          {
+            name = "percent-encoding";
+            packageId = "percent-encoding";
+          }
+        ];
+        features = {
+          "serde" = [ "dep:serde" ];
+        };
+        resolvedDefaultFeatures = [ "default" ];
+      };
+      "urlencoding" = rec {
+        crateName = "urlencoding";
+        version = "2.1.3";
+        edition = "2021";
+        sha256 = "1nj99jp37k47n0hvaz5fvz7z6jd0sb4ppvfy3nphr1zbnyixpy6s";
+        authors = [
+          "Kornel <kornel@geekhood.net>"
+          "Bertram Truong <b@bertramtruong.com>"
+        ];
+
+      };
+      "utf8parse" = rec {
+        crateName = "utf8parse";
+        version = "0.2.1";
+        edition = "2018";
+        sha256 = "02ip1a0az0qmc2786vxk2nqwsgcwf17d3a38fkf0q7hrmwh9c6vi";
+        authors = [
+          "Joe Wilm <joe@jwilm.com>"
+          "Christian Duerr <contact@christianduerr.com>"
+        ];
+        features = { };
+        resolvedDefaultFeatures = [ "default" ];
+      };
+      "uuid" = rec {
+        crateName = "uuid";
+        version = "1.7.0";
+        edition = "2018";
+        sha256 = "0aivp5ys7sg2izlj2sn6rr8p43vdcwg64naj8n0kqbd15iqcj37h";
+        authors = [
+          "Ashley Mannix<ashleymannix@live.com.au>"
+          "Christopher Armstrong"
+          "Dylan DPC<dylan.dpc@gmail.com>"
+          "Hunar Roop Kahlon<hunar.roop@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "getrandom";
+            packageId = "getrandom";
+            optional = true;
+          }
+        ];
+        features = {
+          "arbitrary" = [ "dep:arbitrary" ];
+          "atomic" = [ "dep:atomic" ];
+          "borsh" = [ "dep:borsh" "dep:borsh-derive" ];
+          "bytemuck" = [ "dep:bytemuck" ];
+          "default" = [ "std" ];
+          "fast-rng" = [ "rng" "dep:rand" ];
+          "js" = [ "dep:wasm-bindgen" "getrandom?/js" ];
+          "macro-diagnostics" = [ "dep:uuid-macro-internal" ];
+          "md5" = [ "dep:md-5" ];
+          "rng" = [ "dep:getrandom" ];
+          "serde" = [ "dep:serde" ];
+          "sha1" = [ "dep:sha1_smol" ];
+          "slog" = [ "dep:slog" ];
+          "v1" = [ "atomic" ];
+          "v3" = [ "md5" ];
+          "v4" = [ "rng" ];
+          "v5" = [ "sha1" ];
+          "v6" = [ "atomic" ];
+          "v7" = [ "atomic" "rng" ];
+          "zerocopy" = [ "dep:zerocopy" ];
+        };
+        resolvedDefaultFeatures = [ "default" "rng" "std" "v4" ];
+      };
+      "valuable" = rec {
+        crateName = "valuable";
+        version = "0.1.0";
+        edition = "2018";
+        sha256 = "0v9gp3nkjbl30z0fd56d8mx7w1csk86wwjhfjhr400wh9mfpw2w3";
+        features = {
+          "default" = [ "std" ];
+          "derive" = [ "valuable-derive" ];
+          "std" = [ "alloc" ];
+          "valuable-derive" = [ "dep:valuable-derive" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "std" ];
+      };
+      "vcpkg" = rec {
+        crateName = "vcpkg";
+        version = "0.2.15";
+        edition = "2015";
+        sha256 = "09i4nf5y8lig6xgj3f7fyrvzd3nlaw4znrihw8psidvv5yk4xkdc";
+        authors = [
+          "Jim McGrath <jimmc2@gmail.com>"
+        ];
+
+      };
+      "version_check" = rec {
+        crateName = "version_check";
+        version = "0.9.4";
+        edition = "2015";
+        sha256 = "0gs8grwdlgh0xq660d7wr80x14vxbizmd8dbp29p2pdncx8lp1s9";
+        authors = [
+          "Sergio Benitez <sb@sergio.bz>"
+        ];
+
+      };
+      "vhost" = rec {
+        crateName = "vhost";
+        version = "0.6.1";
+        edition = "2018";
+        sha256 = "0dczb95w5vcq852fzxsbc6zh7ll0p1mz7yrrchvv8xjjpy6rwxm6";
+        authors = [
+          "Liu Jiang <gerry@linux.alibaba.com>"
+        ];
+        dependencies = [
+          {
+            name = "bitflags";
+            packageId = "bitflags 1.3.2";
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+          }
+          {
+            name = "vm-memory";
+            packageId = "vm-memory";
+          }
+          {
+            name = "vmm-sys-util";
+            packageId = "vmm-sys-util";
+          }
+        ];
+        devDependencies = [
+          {
+            name = "vm-memory";
+            packageId = "vm-memory";
+            features = [ "backend-mmap" ];
+          }
+        ];
+        features = {
+          "vhost-net" = [ "vhost-kern" ];
+          "vhost-user-master" = [ "vhost-user" ];
+          "vhost-user-slave" = [ "vhost-user" ];
+          "vhost-vdpa" = [ "vhost-kern" ];
+        };
+        resolvedDefaultFeatures = [ "default" "vhost-user" "vhost-user-slave" ];
+      };
+      "vhost-user-backend" = rec {
+        crateName = "vhost-user-backend";
+        version = "0.8.0";
+        edition = "2018";
+        sha256 = "00s33wy8cj2i8b4hlxn7wd8zm1fpaa5kjhzv77b3khsavf8pn8wz";
+        authors = [
+          "The Cloud Hypervisor Authors"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+          }
+          {
+            name = "log";
+            packageId = "log";
+          }
+          {
+            name = "vhost";
+            packageId = "vhost";
+            features = [ "vhost-user-slave" ];
+          }
+          {
+            name = "virtio-bindings";
+            packageId = "virtio-bindings 0.1.0";
+          }
+          {
+            name = "virtio-queue";
+            packageId = "virtio-queue";
+          }
+          {
+            name = "vm-memory";
+            packageId = "vm-memory";
+            features = [ "backend-mmap" "backend-atomic" ];
+          }
+          {
+            name = "vmm-sys-util";
+            packageId = "vmm-sys-util";
+          }
+        ];
+        devDependencies = [
+          {
+            name = "vhost";
+            packageId = "vhost";
+            features = [ "vhost-user-master" "vhost-user-slave" ];
+          }
+          {
+            name = "vm-memory";
+            packageId = "vm-memory";
+            features = [ "backend-mmap" "backend-atomic" "backend-bitmap" ];
+          }
+        ];
+
+      };
+      "virtio-bindings 0.1.0" = rec {
+        crateName = "virtio-bindings";
+        version = "0.1.0";
+        edition = "2018";
+        sha256 = "0sxxhhmz1r4s4q5pd2lykswcv9qk05fmpwc5xlb8aj45h8bi5x9z";
+        authors = [
+          "Sergio Lopez <slp@redhat.com>"
+        ];
+        features = { };
+      };
+      "virtio-bindings 0.2.2" = rec {
+        crateName = "virtio-bindings";
+        version = "0.2.2";
+        edition = "2021";
+        sha256 = "11mfm9brqgwpfg0izsgc4n7xkqwxk5ad03ivslq0r88j50dwp2w7";
+        authors = [
+          "Sergio Lopez <slp@redhat.com>"
+        ];
+        features = { };
+      };
+      "virtio-queue" = rec {
+        crateName = "virtio-queue";
+        version = "0.7.1";
+        edition = "2021";
+        sha256 = "1gbppbapj7c0vyca88vl34cx4sp2cy9yg0v6bvyd5h11rhmixa1v";
+        authors = [
+          "The Chromium OS Authors"
+        ];
+        dependencies = [
+          {
+            name = "log";
+            packageId = "log";
+          }
+          {
+            name = "virtio-bindings";
+            packageId = "virtio-bindings 0.1.0";
+          }
+          {
+            name = "vm-memory";
+            packageId = "vm-memory";
+          }
+          {
+            name = "vmm-sys-util";
+            packageId = "vmm-sys-util";
+          }
+        ];
+        devDependencies = [
+          {
+            name = "vm-memory";
+            packageId = "vm-memory";
+            features = [ "backend-mmap" "backend-atomic" ];
+          }
+        ];
+        features = { };
+      };
+      "vm-memory" = rec {
+        crateName = "vm-memory";
+        version = "0.10.0";
+        edition = "2021";
+        sha256 = "0z423a8i4s3addq4yjad4ar5l6qwarjwdn94lismbd0mcqv712k8";
+        authors = [
+          "Liu Jiang <gerry@linux.alibaba.com>"
+        ];
+        dependencies = [
+          {
+            name = "arc-swap";
+            packageId = "arc-swap";
+            optional = true;
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+          }
+          {
+            name = "winapi";
+            packageId = "winapi";
+            target = { target, features }: (target."windows" or false);
+            features = [ "errhandlingapi" "sysinfoapi" ];
+          }
+        ];
+        features = {
+          "arc-swap" = [ "dep:arc-swap" ];
+          "backend-atomic" = [ "arc-swap" ];
+        };
+        resolvedDefaultFeatures = [ "arc-swap" "backend-atomic" "backend-mmap" "default" ];
+      };
+      "vmm-sys-util" = rec {
+        crateName = "vmm-sys-util";
+        version = "0.11.2";
+        edition = "2021";
+        sha256 = "0a9azxk6wsahwkggshbdga4jdryzfw6j5r21f11gf50j4f2b1ds8";
+        authors = [
+          "Intel Virtualization Team <vmm-maintainers@intel.com>"
+        ];
+        dependencies = [
+          {
+            name = "bitflags";
+            packageId = "bitflags 1.3.2";
+            target = { target, features }: (("linux" == target."os" or null) || ("android" == target."os" or null));
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+          }
+        ];
+        features = {
+          "serde" = [ "dep:serde" ];
+          "serde_derive" = [ "dep:serde_derive" ];
+          "with-serde" = [ "serde" "serde_derive" ];
+        };
+      };
+      "wait-timeout" = rec {
+        crateName = "wait-timeout";
+        version = "0.2.0";
+        edition = "2015";
+        crateBin = [ ];
+        sha256 = "1xpkk0j5l9pfmjfh1pi0i89invlavfrd9av5xp0zhxgb29dhy84z";
+        authors = [
+          "Alex Crichton <alex@alexcrichton.com>"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (target."unix" or false);
+          }
+        ];
+
+      };
+      "walkdir" = rec {
+        crateName = "walkdir";
+        version = "2.4.0";
+        edition = "2018";
+        sha256 = "1vjl9fmfc4v8k9ald23qrpcbyb8dl1ynyq8d516cm537r1yqa7fp";
+        authors = [
+          "Andrew Gallant <jamslam@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "same-file";
+            packageId = "same-file";
+          }
+          {
+            name = "winapi-util";
+            packageId = "winapi-util";
+            target = { target, features }: (target."windows" or false);
+          }
+        ];
+
+      };
+      "want" = rec {
+        crateName = "want";
+        version = "0.3.1";
+        edition = "2018";
+        sha256 = "03hbfrnvqqdchb5kgxyavb9jabwza0dmh2vw5kg0dq8rxl57d9xz";
+        authors = [
+          "Sean McArthur <sean@seanmonstar.com>"
+        ];
+        dependencies = [
+          {
+            name = "try-lock";
+            packageId = "try-lock";
+          }
+        ];
+
+      };
+      "wasi" = rec {
+        crateName = "wasi";
+        version = "0.11.0+wasi-snapshot-preview1";
+        edition = "2018";
+        sha256 = "08z4hxwkpdpalxjps1ai9y7ihin26y9f476i53dv98v45gkqg3cw";
+        authors = [
+          "The Cranelift Project Developers"
+        ];
+        features = {
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "core" = [ "dep:core" ];
+          "default" = [ "std" ];
+          "rustc-dep-of-std" = [ "compiler_builtins" "core" "rustc-std-workspace-alloc" ];
+          "rustc-std-workspace-alloc" = [ "dep:rustc-std-workspace-alloc" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "wasm-bindgen" = rec {
+        crateName = "wasm-bindgen";
+        version = "0.2.90";
+        edition = "2018";
+        sha256 = "01jlal3mynqwvqx4acrdnr9bvsdczaz2sy8lmmzmqh81lab348mi";
+        authors = [
+          "The wasm-bindgen Developers"
+        ];
+        dependencies = [
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "wasm-bindgen-macro";
+            packageId = "wasm-bindgen-macro";
+          }
+        ];
+        features = {
+          "default" = [ "spans" "std" ];
+          "enable-interning" = [ "std" ];
+          "gg-alloc" = [ "wasm-bindgen-test/gg-alloc" ];
+          "serde" = [ "dep:serde" ];
+          "serde-serialize" = [ "serde" "serde_json" "std" ];
+          "serde_json" = [ "dep:serde_json" ];
+          "spans" = [ "wasm-bindgen-macro/spans" ];
+          "strict-macro" = [ "wasm-bindgen-macro/strict-macro" ];
+          "xxx_debug_only_print_generated_code" = [ "wasm-bindgen-macro/xxx_debug_only_print_generated_code" ];
+        };
+        resolvedDefaultFeatures = [ "default" "spans" "std" ];
+      };
+      "wasm-bindgen-backend" = rec {
+        crateName = "wasm-bindgen-backend";
+        version = "0.2.90";
+        edition = "2018";
+        sha256 = "1kcxml9762zjdrn0h0n0qxfg1n7z1f577jcc5yimi3a0cddr7p7w";
+        authors = [
+          "The wasm-bindgen Developers"
+        ];
+        dependencies = [
+          {
+            name = "bumpalo";
+            packageId = "bumpalo";
+          }
+          {
+            name = "log";
+            packageId = "log";
+          }
+          {
+            name = "once_cell";
+            packageId = "once_cell";
+          }
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn 2.0.48";
+            features = [ "full" ];
+          }
+          {
+            name = "wasm-bindgen-shared";
+            packageId = "wasm-bindgen-shared";
+          }
+        ];
+        features = {
+          "extra-traits" = [ "syn/extra-traits" ];
+        };
+        resolvedDefaultFeatures = [ "spans" ];
+      };
+      "wasm-bindgen-futures" = rec {
+        crateName = "wasm-bindgen-futures";
+        version = "0.4.40";
+        edition = "2018";
+        sha256 = "0qf4bzlinyg0s4b38fhzdi1cqdd7rgrywqdjr3ngmgc6xcm07qmx";
+        authors = [
+          "The wasm-bindgen Developers"
+        ];
+        dependencies = [
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "js-sys";
+            packageId = "js-sys";
+          }
+          {
+            name = "wasm-bindgen";
+            packageId = "wasm-bindgen";
+          }
+          {
+            name = "web-sys";
+            packageId = "web-sys";
+            target = { target, features }: (builtins.elem "atomics" targetFeatures);
+            features = [ "MessageEvent" "Worker" ];
+          }
+        ];
+        features = {
+          "futures-core" = [ "dep:futures-core" ];
+          "futures-core-03-stream" = [ "futures-core" ];
+        };
+      };
+      "wasm-bindgen-macro" = rec {
+        crateName = "wasm-bindgen-macro";
+        version = "0.2.90";
+        edition = "2018";
+        sha256 = "16d980bql7y5krfqlmcr8mk1q4mrm0rmb0a99j92im5jc62j6k1y";
+        procMacro = true;
+        authors = [
+          "The wasm-bindgen Developers"
+        ];
+        dependencies = [
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "wasm-bindgen-macro-support";
+            packageId = "wasm-bindgen-macro-support";
+          }
+        ];
+        features = {
+          "spans" = [ "wasm-bindgen-macro-support/spans" ];
+          "strict-macro" = [ "wasm-bindgen-macro-support/strict-macro" ];
+        };
+        resolvedDefaultFeatures = [ "spans" ];
+      };
+      "wasm-bindgen-macro-support" = rec {
+        crateName = "wasm-bindgen-macro-support";
+        version = "0.2.90";
+        edition = "2018";
+        sha256 = "19r5bsyjw0fvim7dsj8pbwrq8v0ggh845lhfasgavhbdh2vapqds";
+        authors = [
+          "The wasm-bindgen Developers"
+        ];
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn 2.0.48";
+            features = [ "visit" "full" ];
+          }
+          {
+            name = "wasm-bindgen-backend";
+            packageId = "wasm-bindgen-backend";
+          }
+          {
+            name = "wasm-bindgen-shared";
+            packageId = "wasm-bindgen-shared";
+          }
+        ];
+        features = {
+          "extra-traits" = [ "syn/extra-traits" ];
+          "spans" = [ "wasm-bindgen-backend/spans" ];
+        };
+        resolvedDefaultFeatures = [ "spans" ];
+      };
+      "wasm-bindgen-shared" = rec {
+        crateName = "wasm-bindgen-shared";
+        version = "0.2.90";
+        edition = "2018";
+        links = "wasm_bindgen";
+        sha256 = "0av0m0shdg1jxhf66ymjbq03m0qb7ypm297glndm7mri3hxl34ad";
+        authors = [
+          "The wasm-bindgen Developers"
+        ];
+
+      };
+      "wasm-streams" = rec {
+        crateName = "wasm-streams";
+        version = "0.3.0";
+        edition = "2021";
+        sha256 = "1iqa4kmhbsjj8k4q15i1x0x4p3xda0dhbg7zw51mydr4g129sq5l";
+        type = [ "cdylib" "rlib" ];
+        authors = [
+          "Mattias Buelens <mattias@buelens.com>"
+        ];
+        dependencies = [
+          {
+            name = "futures-util";
+            packageId = "futures-util";
+            features = [ "io" "sink" ];
+          }
+          {
+            name = "js-sys";
+            packageId = "js-sys";
+          }
+          {
+            name = "wasm-bindgen";
+            packageId = "wasm-bindgen";
+          }
+          {
+            name = "wasm-bindgen-futures";
+            packageId = "wasm-bindgen-futures";
+          }
+          {
+            name = "web-sys";
+            packageId = "web-sys";
+            features = [ "AbortSignal" ];
+          }
+        ];
+        devDependencies = [
+          {
+            name = "web-sys";
+            packageId = "web-sys";
+            features = [ "console" "AbortSignal" "Response" "ReadableStream" "Window" ];
+          }
+        ];
+
+      };
+      "web-sys" = rec {
+        crateName = "web-sys";
+        version = "0.3.67";
+        edition = "2018";
+        sha256 = "1vfjjj3i49gy8bh8znnqhak1hx7xj9c2a3jzc0wpmgp0nqrj7kaq";
+        authors = [
+          "The wasm-bindgen Developers"
+        ];
+        dependencies = [
+          {
+            name = "js-sys";
+            packageId = "js-sys";
+          }
+          {
+            name = "wasm-bindgen";
+            packageId = "wasm-bindgen";
+          }
+        ];
+        features = {
+          "AbortSignal" = [ "EventTarget" ];
+          "AnalyserNode" = [ "AudioNode" "EventTarget" ];
+          "Animation" = [ "EventTarget" ];
+          "AnimationEvent" = [ "Event" ];
+          "AnimationPlaybackEvent" = [ "Event" ];
+          "Attr" = [ "EventTarget" "Node" ];
+          "AudioBufferSourceNode" = [ "AudioNode" "AudioScheduledSourceNode" "EventTarget" ];
+          "AudioContext" = [ "BaseAudioContext" "EventTarget" ];
+          "AudioDestinationNode" = [ "AudioNode" "EventTarget" ];
+          "AudioNode" = [ "EventTarget" ];
+          "AudioProcessingEvent" = [ "Event" ];
+          "AudioScheduledSourceNode" = [ "AudioNode" "EventTarget" ];
+          "AudioStreamTrack" = [ "EventTarget" "MediaStreamTrack" ];
+          "AudioTrackList" = [ "EventTarget" ];
+          "AudioWorklet" = [ "Worklet" ];
+          "AudioWorkletGlobalScope" = [ "WorkletGlobalScope" ];
+          "AudioWorkletNode" = [ "AudioNode" "EventTarget" ];
+          "AuthenticatorAssertionResponse" = [ "AuthenticatorResponse" ];
+          "AuthenticatorAttestationResponse" = [ "AuthenticatorResponse" ];
+          "BaseAudioContext" = [ "EventTarget" ];
+          "BatteryManager" = [ "EventTarget" ];
+          "BeforeUnloadEvent" = [ "Event" ];
+          "BiquadFilterNode" = [ "AudioNode" "EventTarget" ];
+          "BlobEvent" = [ "Event" ];
+          "Bluetooth" = [ "EventTarget" ];
+          "BluetoothAdvertisingEvent" = [ "Event" ];
+          "BluetoothDevice" = [ "EventTarget" ];
+          "BluetoothPermissionResult" = [ "EventTarget" "PermissionStatus" ];
+          "BluetoothRemoteGattCharacteristic" = [ "EventTarget" ];
+          "BluetoothRemoteGattService" = [ "EventTarget" ];
+          "BroadcastChannel" = [ "EventTarget" ];
+          "CanvasCaptureMediaStream" = [ "EventTarget" "MediaStream" ];
+          "CanvasCaptureMediaStreamTrack" = [ "EventTarget" "MediaStreamTrack" ];
+          "CdataSection" = [ "CharacterData" "EventTarget" "Node" "Text" ];
+          "ChannelMergerNode" = [ "AudioNode" "EventTarget" ];
+          "ChannelSplitterNode" = [ "AudioNode" "EventTarget" ];
+          "CharacterData" = [ "EventTarget" "Node" ];
+          "ChromeWorker" = [ "EventTarget" "Worker" ];
+          "Clipboard" = [ "EventTarget" ];
+          "ClipboardEvent" = [ "Event" ];
+          "CloseEvent" = [ "Event" ];
+          "Comment" = [ "CharacterData" "EventTarget" "Node" ];
+          "CompositionEvent" = [ "Event" "UiEvent" ];
+          "ConstantSourceNode" = [ "AudioNode" "AudioScheduledSourceNode" "EventTarget" ];
+          "ConvolverNode" = [ "AudioNode" "EventTarget" ];
+          "CssAnimation" = [ "Animation" "EventTarget" ];
+          "CssConditionRule" = [ "CssGroupingRule" "CssRule" ];
+          "CssCounterStyleRule" = [ "CssRule" ];
+          "CssFontFaceRule" = [ "CssRule" ];
+          "CssFontFeatureValuesRule" = [ "CssRule" ];
+          "CssGroupingRule" = [ "CssRule" ];
+          "CssImportRule" = [ "CssRule" ];
+          "CssKeyframeRule" = [ "CssRule" ];
+          "CssKeyframesRule" = [ "CssRule" ];
+          "CssMediaRule" = [ "CssConditionRule" "CssGroupingRule" "CssRule" ];
+          "CssNamespaceRule" = [ "CssRule" ];
+          "CssPageRule" = [ "CssRule" ];
+          "CssStyleRule" = [ "CssRule" ];
+          "CssStyleSheet" = [ "StyleSheet" ];
+          "CssSupportsRule" = [ "CssConditionRule" "CssGroupingRule" "CssRule" ];
+          "CssTransition" = [ "Animation" "EventTarget" ];
+          "CustomEvent" = [ "Event" ];
+          "DedicatedWorkerGlobalScope" = [ "EventTarget" "WorkerGlobalScope" ];
+          "DelayNode" = [ "AudioNode" "EventTarget" ];
+          "DeviceLightEvent" = [ "Event" ];
+          "DeviceMotionEvent" = [ "Event" ];
+          "DeviceOrientationEvent" = [ "Event" ];
+          "DeviceProximityEvent" = [ "Event" ];
+          "Document" = [ "EventTarget" "Node" ];
+          "DocumentFragment" = [ "EventTarget" "Node" ];
+          "DocumentTimeline" = [ "AnimationTimeline" ];
+          "DocumentType" = [ "EventTarget" "Node" ];
+          "DomMatrix" = [ "DomMatrixReadOnly" ];
+          "DomPoint" = [ "DomPointReadOnly" ];
+          "DomRect" = [ "DomRectReadOnly" ];
+          "DomRequest" = [ "EventTarget" ];
+          "DragEvent" = [ "Event" "MouseEvent" "UiEvent" ];
+          "DynamicsCompressorNode" = [ "AudioNode" "EventTarget" ];
+          "Element" = [ "EventTarget" "Node" ];
+          "ErrorEvent" = [ "Event" ];
+          "EventSource" = [ "EventTarget" ];
+          "ExtendableEvent" = [ "Event" ];
+          "ExtendableMessageEvent" = [ "Event" "ExtendableEvent" ];
+          "FetchEvent" = [ "Event" "ExtendableEvent" ];
+          "FetchObserver" = [ "EventTarget" ];
+          "File" = [ "Blob" ];
+          "FileReader" = [ "EventTarget" ];
+          "FileSystemDirectoryEntry" = [ "FileSystemEntry" ];
+          "FileSystemDirectoryHandle" = [ "FileSystemHandle" ];
+          "FileSystemFileEntry" = [ "FileSystemEntry" ];
+          "FileSystemFileHandle" = [ "FileSystemHandle" ];
+          "FileSystemWritableFileStream" = [ "WritableStream" ];
+          "FocusEvent" = [ "Event" "UiEvent" ];
+          "FontFaceSet" = [ "EventTarget" ];
+          "FontFaceSetLoadEvent" = [ "Event" ];
+          "GainNode" = [ "AudioNode" "EventTarget" ];
+          "GamepadAxisMoveEvent" = [ "Event" "GamepadEvent" ];
+          "GamepadButtonEvent" = [ "Event" "GamepadEvent" ];
+          "GamepadEvent" = [ "Event" ];
+          "GpuDevice" = [ "EventTarget" ];
+          "GpuInternalError" = [ "GpuError" ];
+          "GpuOutOfMemoryError" = [ "GpuError" ];
+          "GpuPipelineError" = [ "DomException" ];
+          "GpuUncapturedErrorEvent" = [ "Event" ];
+          "GpuValidationError" = [ "GpuError" ];
+          "HashChangeEvent" = [ "Event" ];
+          "Hid" = [ "EventTarget" ];
+          "HidConnectionEvent" = [ "Event" ];
+          "HidDevice" = [ "EventTarget" ];
+          "HidInputReportEvent" = [ "Event" ];
+          "HtmlAnchorElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlAreaElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlAudioElement" = [ "Element" "EventTarget" "HtmlElement" "HtmlMediaElement" "Node" ];
+          "HtmlBaseElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlBodyElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlBrElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlButtonElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlCanvasElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlDListElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlDataElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlDataListElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlDetailsElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlDialogElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlDirectoryElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlDivElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlDocument" = [ "Document" "EventTarget" "Node" ];
+          "HtmlElement" = [ "Element" "EventTarget" "Node" ];
+          "HtmlEmbedElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlFieldSetElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlFontElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlFormControlsCollection" = [ "HtmlCollection" ];
+          "HtmlFormElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlFrameElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlFrameSetElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlHeadElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlHeadingElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlHrElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlHtmlElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlIFrameElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlImageElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlInputElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlLabelElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlLegendElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlLiElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlLinkElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlMapElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlMediaElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlMenuElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlMenuItemElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlMetaElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlMeterElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlModElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlOListElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlObjectElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlOptGroupElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlOptionElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlOptionsCollection" = [ "HtmlCollection" ];
+          "HtmlOutputElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlParagraphElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlParamElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlPictureElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlPreElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlProgressElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlQuoteElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlScriptElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlSelectElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlSlotElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlSourceElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlSpanElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlStyleElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlTableCaptionElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlTableCellElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlTableColElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlTableElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlTableRowElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlTableSectionElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlTemplateElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlTextAreaElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlTimeElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlTitleElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlTrackElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlUListElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlUnknownElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlVideoElement" = [ "Element" "EventTarget" "HtmlElement" "HtmlMediaElement" "Node" ];
+          "IdbCursorWithValue" = [ "IdbCursor" ];
+          "IdbDatabase" = [ "EventTarget" ];
+          "IdbFileHandle" = [ "EventTarget" ];
+          "IdbFileRequest" = [ "DomRequest" "EventTarget" ];
+          "IdbLocaleAwareKeyRange" = [ "IdbKeyRange" ];
+          "IdbMutableFile" = [ "EventTarget" ];
+          "IdbOpenDbRequest" = [ "EventTarget" "IdbRequest" ];
+          "IdbRequest" = [ "EventTarget" ];
+          "IdbTransaction" = [ "EventTarget" ];
+          "IdbVersionChangeEvent" = [ "Event" ];
+          "IirFilterNode" = [ "AudioNode" "EventTarget" ];
+          "ImageCaptureErrorEvent" = [ "Event" ];
+          "ImageTrack" = [ "EventTarget" ];
+          "InputEvent" = [ "Event" "UiEvent" ];
+          "KeyboardEvent" = [ "Event" "UiEvent" ];
+          "KeyframeEffect" = [ "AnimationEffect" ];
+          "LocalMediaStream" = [ "EventTarget" "MediaStream" ];
+          "MediaDevices" = [ "EventTarget" ];
+          "MediaElementAudioSourceNode" = [ "AudioNode" "EventTarget" ];
+          "MediaEncryptedEvent" = [ "Event" ];
+          "MediaKeyError" = [ "Event" ];
+          "MediaKeyMessageEvent" = [ "Event" ];
+          "MediaKeySession" = [ "EventTarget" ];
+          "MediaQueryList" = [ "EventTarget" ];
+          "MediaQueryListEvent" = [ "Event" ];
+          "MediaRecorder" = [ "EventTarget" ];
+          "MediaRecorderErrorEvent" = [ "Event" ];
+          "MediaSource" = [ "EventTarget" ];
+          "MediaStream" = [ "EventTarget" ];
+          "MediaStreamAudioDestinationNode" = [ "AudioNode" "EventTarget" ];
+          "MediaStreamAudioSourceNode" = [ "AudioNode" "EventTarget" ];
+          "MediaStreamEvent" = [ "Event" ];
+          "MediaStreamTrack" = [ "EventTarget" ];
+          "MediaStreamTrackEvent" = [ "Event" ];
+          "MediaStreamTrackGenerator" = [ "EventTarget" "MediaStreamTrack" ];
+          "MessageEvent" = [ "Event" ];
+          "MessagePort" = [ "EventTarget" ];
+          "MidiAccess" = [ "EventTarget" ];
+          "MidiConnectionEvent" = [ "Event" ];
+          "MidiInput" = [ "EventTarget" "MidiPort" ];
+          "MidiMessageEvent" = [ "Event" ];
+          "MidiOutput" = [ "EventTarget" "MidiPort" ];
+          "MidiPort" = [ "EventTarget" ];
+          "MouseEvent" = [ "Event" "UiEvent" ];
+          "MouseScrollEvent" = [ "Event" "MouseEvent" "UiEvent" ];
+          "MutationEvent" = [ "Event" ];
+          "NetworkInformation" = [ "EventTarget" ];
+          "Node" = [ "EventTarget" ];
+          "Notification" = [ "EventTarget" ];
+          "NotificationEvent" = [ "Event" "ExtendableEvent" ];
+          "OfflineAudioCompletionEvent" = [ "Event" ];
+          "OfflineAudioContext" = [ "BaseAudioContext" "EventTarget" ];
+          "OfflineResourceList" = [ "EventTarget" ];
+          "OffscreenCanvas" = [ "EventTarget" ];
+          "OscillatorNode" = [ "AudioNode" "AudioScheduledSourceNode" "EventTarget" ];
+          "PageTransitionEvent" = [ "Event" ];
+          "PaintWorkletGlobalScope" = [ "WorkletGlobalScope" ];
+          "PannerNode" = [ "AudioNode" "EventTarget" ];
+          "PaymentMethodChangeEvent" = [ "Event" "PaymentRequestUpdateEvent" ];
+          "PaymentRequestUpdateEvent" = [ "Event" ];
+          "Performance" = [ "EventTarget" ];
+          "PerformanceMark" = [ "PerformanceEntry" ];
+          "PerformanceMeasure" = [ "PerformanceEntry" ];
+          "PerformanceNavigationTiming" = [ "PerformanceEntry" "PerformanceResourceTiming" ];
+          "PerformanceResourceTiming" = [ "PerformanceEntry" ];
+          "PermissionStatus" = [ "EventTarget" ];
+          "PointerEvent" = [ "Event" "MouseEvent" "UiEvent" ];
+          "PopStateEvent" = [ "Event" ];
+          "PopupBlockedEvent" = [ "Event" ];
+          "PresentationAvailability" = [ "EventTarget" ];
+          "PresentationConnection" = [ "EventTarget" ];
+          "PresentationConnectionAvailableEvent" = [ "Event" ];
+          "PresentationConnectionCloseEvent" = [ "Event" ];
+          "PresentationConnectionList" = [ "EventTarget" ];
+          "PresentationRequest" = [ "EventTarget" ];
+          "ProcessingInstruction" = [ "CharacterData" "EventTarget" "Node" ];
+          "ProgressEvent" = [ "Event" ];
+          "PromiseRejectionEvent" = [ "Event" ];
+          "PublicKeyCredential" = [ "Credential" ];
+          "PushEvent" = [ "Event" "ExtendableEvent" ];
+          "RadioNodeList" = [ "NodeList" ];
+          "RtcDataChannel" = [ "EventTarget" ];
+          "RtcDataChannelEvent" = [ "Event" ];
+          "RtcPeerConnection" = [ "EventTarget" ];
+          "RtcPeerConnectionIceEvent" = [ "Event" ];
+          "RtcTrackEvent" = [ "Event" ];
+          "RtcdtmfSender" = [ "EventTarget" ];
+          "RtcdtmfToneChangeEvent" = [ "Event" ];
+          "Screen" = [ "EventTarget" ];
+          "ScreenOrientation" = [ "EventTarget" ];
+          "ScriptProcessorNode" = [ "AudioNode" "EventTarget" ];
+          "ScrollAreaEvent" = [ "Event" "UiEvent" ];
+          "SecurityPolicyViolationEvent" = [ "Event" ];
+          "Serial" = [ "EventTarget" ];
+          "SerialPort" = [ "EventTarget" ];
+          "ServiceWorker" = [ "EventTarget" ];
+          "ServiceWorkerContainer" = [ "EventTarget" ];
+          "ServiceWorkerGlobalScope" = [ "EventTarget" "WorkerGlobalScope" ];
+          "ServiceWorkerRegistration" = [ "EventTarget" ];
+          "ShadowRoot" = [ "DocumentFragment" "EventTarget" "Node" ];
+          "SharedWorker" = [ "EventTarget" ];
+          "SharedWorkerGlobalScope" = [ "EventTarget" "WorkerGlobalScope" ];
+          "SourceBuffer" = [ "EventTarget" ];
+          "SourceBufferList" = [ "EventTarget" ];
+          "SpeechRecognition" = [ "EventTarget" ];
+          "SpeechRecognitionError" = [ "Event" ];
+          "SpeechRecognitionEvent" = [ "Event" ];
+          "SpeechSynthesis" = [ "EventTarget" ];
+          "SpeechSynthesisErrorEvent" = [ "Event" "SpeechSynthesisEvent" ];
+          "SpeechSynthesisEvent" = [ "Event" ];
+          "SpeechSynthesisUtterance" = [ "EventTarget" ];
+          "StereoPannerNode" = [ "AudioNode" "EventTarget" ];
+          "StorageEvent" = [ "Event" ];
+          "SubmitEvent" = [ "Event" ];
+          "SvgAnimateElement" = [ "Element" "EventTarget" "Node" "SvgAnimationElement" "SvgElement" ];
+          "SvgAnimateMotionElement" = [ "Element" "EventTarget" "Node" "SvgAnimationElement" "SvgElement" ];
+          "SvgAnimateTransformElement" = [ "Element" "EventTarget" "Node" "SvgAnimationElement" "SvgElement" ];
+          "SvgAnimationElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgCircleElement" = [ "Element" "EventTarget" "Node" "SvgElement" "SvgGeometryElement" "SvgGraphicsElement" ];
+          "SvgClipPathElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgComponentTransferFunctionElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgDefsElement" = [ "Element" "EventTarget" "Node" "SvgElement" "SvgGraphicsElement" ];
+          "SvgDescElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgElement" = [ "Element" "EventTarget" "Node" ];
+          "SvgEllipseElement" = [ "Element" "EventTarget" "Node" "SvgElement" "SvgGeometryElement" "SvgGraphicsElement" ];
+          "SvgFilterElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgForeignObjectElement" = [ "Element" "EventTarget" "Node" "SvgElement" "SvgGraphicsElement" ];
+          "SvgGeometryElement" = [ "Element" "EventTarget" "Node" "SvgElement" "SvgGraphicsElement" ];
+          "SvgGradientElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgGraphicsElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgImageElement" = [ "Element" "EventTarget" "Node" "SvgElement" "SvgGraphicsElement" ];
+          "SvgLineElement" = [ "Element" "EventTarget" "Node" "SvgElement" "SvgGeometryElement" "SvgGraphicsElement" ];
+          "SvgLinearGradientElement" = [ "Element" "EventTarget" "Node" "SvgElement" "SvgGradientElement" ];
+          "SvgMarkerElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgMaskElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgMetadataElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgPathElement" = [ "Element" "EventTarget" "Node" "SvgElement" "SvgGeometryElement" "SvgGraphicsElement" ];
+          "SvgPathSegArcAbs" = [ "SvgPathSeg" ];
+          "SvgPathSegArcRel" = [ "SvgPathSeg" ];
+          "SvgPathSegClosePath" = [ "SvgPathSeg" ];
+          "SvgPathSegCurvetoCubicAbs" = [ "SvgPathSeg" ];
+          "SvgPathSegCurvetoCubicRel" = [ "SvgPathSeg" ];
+          "SvgPathSegCurvetoCubicSmoothAbs" = [ "SvgPathSeg" ];
+          "SvgPathSegCurvetoCubicSmoothRel" = [ "SvgPathSeg" ];
+          "SvgPathSegCurvetoQuadraticAbs" = [ "SvgPathSeg" ];
+          "SvgPathSegCurvetoQuadraticRel" = [ "SvgPathSeg" ];
+          "SvgPathSegCurvetoQuadraticSmoothAbs" = [ "SvgPathSeg" ];
+          "SvgPathSegCurvetoQuadraticSmoothRel" = [ "SvgPathSeg" ];
+          "SvgPathSegLinetoAbs" = [ "SvgPathSeg" ];
+          "SvgPathSegLinetoHorizontalAbs" = [ "SvgPathSeg" ];
+          "SvgPathSegLinetoHorizontalRel" = [ "SvgPathSeg" ];
+          "SvgPathSegLinetoRel" = [ "SvgPathSeg" ];
+          "SvgPathSegLinetoVerticalAbs" = [ "SvgPathSeg" ];
+          "SvgPathSegLinetoVerticalRel" = [ "SvgPathSeg" ];
+          "SvgPathSegMovetoAbs" = [ "SvgPathSeg" ];
+          "SvgPathSegMovetoRel" = [ "SvgPathSeg" ];
+          "SvgPatternElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgPolygonElement" = [ "Element" "EventTarget" "Node" "SvgElement" "SvgGeometryElement" "SvgGraphicsElement" ];
+          "SvgPolylineElement" = [ "Element" "EventTarget" "Node" "SvgElement" "SvgGeometryElement" "SvgGraphicsElement" ];
+          "SvgRadialGradientElement" = [ "Element" "EventTarget" "Node" "SvgElement" "SvgGradientElement" ];
+          "SvgRectElement" = [ "Element" "EventTarget" "Node" "SvgElement" "SvgGeometryElement" "SvgGraphicsElement" ];
+          "SvgScriptElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgSetElement" = [ "Element" "EventTarget" "Node" "SvgAnimationElement" "SvgElement" ];
+          "SvgStopElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgStyleElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgSwitchElement" = [ "Element" "EventTarget" "Node" "SvgElement" "SvgGraphicsElement" ];
+          "SvgSymbolElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgTextContentElement" = [ "Element" "EventTarget" "Node" "SvgElement" "SvgGraphicsElement" ];
+          "SvgTextElement" = [ "Element" "EventTarget" "Node" "SvgElement" "SvgGraphicsElement" "SvgTextContentElement" "SvgTextPositioningElement" ];
+          "SvgTextPathElement" = [ "Element" "EventTarget" "Node" "SvgElement" "SvgGraphicsElement" "SvgTextContentElement" ];
+          "SvgTextPositioningElement" = [ "Element" "EventTarget" "Node" "SvgElement" "SvgGraphicsElement" "SvgTextContentElement" ];
+          "SvgTitleElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgUseElement" = [ "Element" "EventTarget" "Node" "SvgElement" "SvgGraphicsElement" ];
+          "SvgViewElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgaElement" = [ "Element" "EventTarget" "Node" "SvgElement" "SvgGraphicsElement" ];
+          "SvgfeBlendElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgfeColorMatrixElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgfeComponentTransferElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgfeCompositeElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgfeConvolveMatrixElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgfeDiffuseLightingElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgfeDisplacementMapElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgfeDistantLightElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgfeDropShadowElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgfeFloodElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgfeFuncAElement" = [ "Element" "EventTarget" "Node" "SvgComponentTransferFunctionElement" "SvgElement" ];
+          "SvgfeFuncBElement" = [ "Element" "EventTarget" "Node" "SvgComponentTransferFunctionElement" "SvgElement" ];
+          "SvgfeFuncGElement" = [ "Element" "EventTarget" "Node" "SvgComponentTransferFunctionElement" "SvgElement" ];
+          "SvgfeFuncRElement" = [ "Element" "EventTarget" "Node" "SvgComponentTransferFunctionElement" "SvgElement" ];
+          "SvgfeGaussianBlurElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgfeImageElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgfeMergeElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgfeMergeNodeElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgfeMorphologyElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgfeOffsetElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgfePointLightElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgfeSpecularLightingElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgfeSpotLightElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgfeTileElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgfeTurbulenceElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvggElement" = [ "Element" "EventTarget" "Node" "SvgElement" "SvgGraphicsElement" ];
+          "SvgmPathElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgsvgElement" = [ "Element" "EventTarget" "Node" "SvgElement" "SvgGraphicsElement" ];
+          "SvgtSpanElement" = [ "Element" "EventTarget" "Node" "SvgElement" "SvgGraphicsElement" "SvgTextContentElement" "SvgTextPositioningElement" ];
+          "TaskController" = [ "AbortController" ];
+          "TaskPriorityChangeEvent" = [ "Event" ];
+          "TaskSignal" = [ "AbortSignal" "EventTarget" ];
+          "TcpServerSocket" = [ "EventTarget" ];
+          "TcpServerSocketEvent" = [ "Event" ];
+          "TcpSocket" = [ "EventTarget" ];
+          "TcpSocketErrorEvent" = [ "Event" ];
+          "TcpSocketEvent" = [ "Event" ];
+          "Text" = [ "CharacterData" "EventTarget" "Node" ];
+          "TextTrack" = [ "EventTarget" ];
+          "TextTrackCue" = [ "EventTarget" ];
+          "TextTrackList" = [ "EventTarget" ];
+          "TimeEvent" = [ "Event" ];
+          "TouchEvent" = [ "Event" "UiEvent" ];
+          "TrackEvent" = [ "Event" ];
+          "TransitionEvent" = [ "Event" ];
+          "UiEvent" = [ "Event" ];
+          "Usb" = [ "EventTarget" ];
+          "UsbConnectionEvent" = [ "Event" ];
+          "UsbPermissionResult" = [ "EventTarget" "PermissionStatus" ];
+          "UserProximityEvent" = [ "Event" ];
+          "ValueEvent" = [ "Event" ];
+          "VideoStreamTrack" = [ "EventTarget" "MediaStreamTrack" ];
+          "VideoTrackList" = [ "EventTarget" ];
+          "VrDisplay" = [ "EventTarget" ];
+          "VttCue" = [ "EventTarget" "TextTrackCue" ];
+          "WakeLockSentinel" = [ "EventTarget" ];
+          "WaveShaperNode" = [ "AudioNode" "EventTarget" ];
+          "WebGlContextEvent" = [ "Event" ];
+          "WebKitCssMatrix" = [ "DomMatrix" "DomMatrixReadOnly" ];
+          "WebSocket" = [ "EventTarget" ];
+          "WebTransportError" = [ "DomException" ];
+          "WebTransportReceiveStream" = [ "ReadableStream" ];
+          "WebTransportSendStream" = [ "WritableStream" ];
+          "WheelEvent" = [ "Event" "MouseEvent" "UiEvent" ];
+          "Window" = [ "EventTarget" ];
+          "WindowClient" = [ "Client" ];
+          "Worker" = [ "EventTarget" ];
+          "WorkerDebuggerGlobalScope" = [ "EventTarget" ];
+          "WorkerGlobalScope" = [ "EventTarget" ];
+          "XmlDocument" = [ "Document" "EventTarget" "Node" ];
+          "XmlHttpRequest" = [ "EventTarget" "XmlHttpRequestEventTarget" ];
+          "XmlHttpRequestEventTarget" = [ "EventTarget" ];
+          "XmlHttpRequestUpload" = [ "EventTarget" "XmlHttpRequestEventTarget" ];
+          "XrBoundedReferenceSpace" = [ "EventTarget" "XrReferenceSpace" "XrSpace" ];
+          "XrInputSourceEvent" = [ "Event" ];
+          "XrInputSourcesChangeEvent" = [ "Event" ];
+          "XrJointPose" = [ "XrPose" ];
+          "XrJointSpace" = [ "EventTarget" "XrSpace" ];
+          "XrLayer" = [ "EventTarget" ];
+          "XrPermissionStatus" = [ "EventTarget" "PermissionStatus" ];
+          "XrReferenceSpace" = [ "EventTarget" "XrSpace" ];
+          "XrReferenceSpaceEvent" = [ "Event" ];
+          "XrSession" = [ "EventTarget" ];
+          "XrSessionEvent" = [ "Event" ];
+          "XrSpace" = [ "EventTarget" ];
+          "XrSystem" = [ "EventTarget" ];
+          "XrViewerPose" = [ "XrPose" ];
+          "XrWebGlLayer" = [ "EventTarget" "XrLayer" ];
+        };
+        resolvedDefaultFeatures = [ "AbortController" "AbortSignal" "Blob" "BlobPropertyBag" "CanvasRenderingContext2d" "Document" "DomRect" "DomRectReadOnly" "Element" "Event" "EventTarget" "File" "FormData" "Headers" "HtmlCanvasElement" "HtmlElement" "MessageEvent" "Node" "ReadableStream" "Request" "RequestCredentials" "RequestInit" "RequestMode" "Response" "ServiceWorkerGlobalScope" "Window" "Worker" "WorkerGlobalScope" ];
+      };
+      "web-time" = rec {
+        crateName = "web-time";
+        version = "0.2.4";
+        edition = "2021";
+        sha256 = "1q6gk0nkwbfz30g1pz8g52mq00zjx7m5im36k3474aw73jdh8c5a";
+        dependencies = [
+          {
+            name = "js-sys";
+            packageId = "js-sys";
+            target = { target, features }: ((builtins.elem "wasm" target."family") && (!(("emscripten" == target."os" or null) || ("wasi" == target."os" or null))));
+          }
+          {
+            name = "wasm-bindgen";
+            packageId = "wasm-bindgen";
+            usesDefaultFeatures = false;
+            target = { target, features }: ((builtins.elem "wasm" target."family") && (!(("emscripten" == target."os" or null) || ("wasi" == target."os" or null))));
+          }
+        ];
+
+      };
+      "which 4.4.2" = rec {
+        crateName = "which";
+        version = "4.4.2";
+        edition = "2021";
+        sha256 = "1ixzmx3svsv5hbdvd8vdhd3qwvf6ns8jdpif1wmwsy10k90j9fl7";
+        authors = [
+          "Harry Fei <tiziyuanfang@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "either";
+            packageId = "either";
+          }
+          {
+            name = "home";
+            packageId = "home";
+            target = { target, features }: ((target."windows" or false) || (target."unix" or false) || ("redox" == target."os" or null));
+          }
+          {
+            name = "once_cell";
+            packageId = "once_cell";
+            target = { target, features }: (target."windows" or false);
+          }
+          {
+            name = "rustix";
+            packageId = "rustix";
+            usesDefaultFeatures = false;
+            features = [ "fs" "std" ];
+          }
+        ];
+        features = {
+          "regex" = [ "dep:regex" ];
+        };
+      };
+      "which 5.0.0" = rec {
+        crateName = "which";
+        version = "5.0.0";
+        edition = "2021";
+        sha256 = "053fpbczryyn8lcbpkvwl8v2rzld0pr30r5lh1cxv87kjs2ymwwv";
+        authors = [
+          "Harry Fei <tiziyuanfang@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "either";
+            packageId = "either";
+          }
+          {
+            name = "home";
+            packageId = "home";
+            target = { target, features }: ((target."windows" or false) || (target."unix" or false) || ("redox" == target."os" or null));
+          }
+          {
+            name = "once_cell";
+            packageId = "once_cell";
+            target = { target, features }: (target."windows" or false);
+          }
+          {
+            name = "rustix";
+            packageId = "rustix";
+            usesDefaultFeatures = false;
+            features = [ "fs" "std" ];
+          }
+          {
+            name = "windows-sys";
+            packageId = "windows-sys 0.48.0";
+            target = { target, features }: (target."windows" or false);
+            features = [ "Win32_Storage_FileSystem" "Win32_Foundation" ];
+          }
+        ];
+        features = {
+          "regex" = [ "dep:regex" ];
+        };
+      };
+      "winapi" = rec {
+        crateName = "winapi";
+        version = "0.3.9";
+        edition = "2015";
+        sha256 = "06gl025x418lchw1wxj64ycr7gha83m44cjr5sarhynd9xkrm0sw";
+        authors = [
+          "Peter Atashian <retep998@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "winapi-i686-pc-windows-gnu";
+            packageId = "winapi-i686-pc-windows-gnu";
+            target = { target, features }: (pkgs.rust.lib.toRustTarget stdenv.hostPlatform == "i686-pc-windows-gnu");
+          }
+          {
+            name = "winapi-x86_64-pc-windows-gnu";
+            packageId = "winapi-x86_64-pc-windows-gnu";
+            target = { target, features }: (pkgs.rust.lib.toRustTarget stdenv.hostPlatform == "x86_64-pc-windows-gnu");
+          }
+        ];
+        features = {
+          "debug" = [ "impl-debug" ];
+        };
+        resolvedDefaultFeatures = [ "basetsd" "consoleapi" "errhandlingapi" "fileapi" "handleapi" "knownfolders" "minwindef" "ntstatus" "objbase" "processenv" "processthreadsapi" "shellapi" "shlobj" "std" "stringapiset" "synchapi" "sysinfoapi" "winbase" "wincon" "winerror" "winnt" "winuser" ];
+      };
+      "winapi-i686-pc-windows-gnu" = rec {
+        crateName = "winapi-i686-pc-windows-gnu";
+        version = "0.4.0";
+        edition = "2015";
+        sha256 = "1dmpa6mvcvzz16zg6d5vrfy4bxgg541wxrcip7cnshi06v38ffxc";
+        authors = [
+          "Peter Atashian <retep998@gmail.com>"
+        ];
+
+      };
+      "winapi-util" = rec {
+        crateName = "winapi-util";
+        version = "0.1.6";
+        edition = "2021";
+        sha256 = "15i5lm39wd44004i9d5qspry2cynkrpvwzghr6s2c3dsk28nz7pj";
+        authors = [
+          "Andrew Gallant <jamslam@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "winapi";
+            packageId = "winapi";
+            target = { target, features }: (target."windows" or false);
+            features = [ "std" "consoleapi" "errhandlingapi" "fileapi" "minwindef" "processenv" "sysinfoapi" "winbase" "wincon" "winerror" "winnt" ];
+          }
+        ];
+
+      };
+      "winapi-x86_64-pc-windows-gnu" = rec {
+        crateName = "winapi-x86_64-pc-windows-gnu";
+        version = "0.4.0";
+        edition = "2015";
+        sha256 = "0gqq64czqb64kskjryj8isp62m2sgvx25yyj3kpc2myh85w24bki";
+        authors = [
+          "Peter Atashian <retep998@gmail.com>"
+        ];
+
+      };
+      "windows-core" = rec {
+        crateName = "windows-core";
+        version = "0.52.0";
+        edition = "2021";
+        sha256 = "1nc3qv7sy24x0nlnb32f7alzpd6f72l4p24vl65vydbyil669ark";
+        authors = [
+          "Microsoft"
+        ];
+        dependencies = [
+          {
+            name = "windows-targets";
+            packageId = "windows-targets 0.52.0";
+          }
+        ];
+        features = { };
+        resolvedDefaultFeatures = [ "default" ];
+      };
+      "windows-sys 0.48.0" = rec {
+        crateName = "windows-sys";
+        version = "0.48.0";
+        edition = "2018";
+        sha256 = "1aan23v5gs7gya1lc46hqn9mdh8yph3fhxmhxlw36pn6pqc28zb7";
+        authors = [
+          "Microsoft"
+        ];
+        dependencies = [
+          {
+            name = "windows-targets";
+            packageId = "windows-targets 0.48.5";
+          }
+        ];
+        features = {
+          "Wdk_System" = [ "Wdk" ];
+          "Wdk_System_OfflineRegistry" = [ "Wdk_System" ];
+          "Win32_Data" = [ "Win32" ];
+          "Win32_Data_HtmlHelp" = [ "Win32_Data" ];
+          "Win32_Data_RightsManagement" = [ "Win32_Data" ];
+          "Win32_Data_Xml" = [ "Win32_Data" ];
+          "Win32_Data_Xml_MsXml" = [ "Win32_Data_Xml" ];
+          "Win32_Data_Xml_XmlLite" = [ "Win32_Data_Xml" ];
+          "Win32_Devices" = [ "Win32" ];
+          "Win32_Devices_AllJoyn" = [ "Win32_Devices" ];
+          "Win32_Devices_BiometricFramework" = [ "Win32_Devices" ];
+          "Win32_Devices_Bluetooth" = [ "Win32_Devices" ];
+          "Win32_Devices_Communication" = [ "Win32_Devices" ];
+          "Win32_Devices_DeviceAccess" = [ "Win32_Devices" ];
+          "Win32_Devices_DeviceAndDriverInstallation" = [ "Win32_Devices" ];
+          "Win32_Devices_DeviceQuery" = [ "Win32_Devices" ];
+          "Win32_Devices_Display" = [ "Win32_Devices" ];
+          "Win32_Devices_Enumeration" = [ "Win32_Devices" ];
+          "Win32_Devices_Enumeration_Pnp" = [ "Win32_Devices_Enumeration" ];
+          "Win32_Devices_Fax" = [ "Win32_Devices" ];
+          "Win32_Devices_FunctionDiscovery" = [ "Win32_Devices" ];
+          "Win32_Devices_Geolocation" = [ "Win32_Devices" ];
+          "Win32_Devices_HumanInterfaceDevice" = [ "Win32_Devices" ];
+          "Win32_Devices_ImageAcquisition" = [ "Win32_Devices" ];
+          "Win32_Devices_PortableDevices" = [ "Win32_Devices" ];
+          "Win32_Devices_Properties" = [ "Win32_Devices" ];
+          "Win32_Devices_Pwm" = [ "Win32_Devices" ];
+          "Win32_Devices_Sensors" = [ "Win32_Devices" ];
+          "Win32_Devices_SerialCommunication" = [ "Win32_Devices" ];
+          "Win32_Devices_Tapi" = [ "Win32_Devices" ];
+          "Win32_Devices_Usb" = [ "Win32_Devices" ];
+          "Win32_Devices_WebServicesOnDevices" = [ "Win32_Devices" ];
+          "Win32_Foundation" = [ "Win32" ];
+          "Win32_Gaming" = [ "Win32" ];
+          "Win32_Globalization" = [ "Win32" ];
+          "Win32_Graphics" = [ "Win32" ];
+          "Win32_Graphics_Dwm" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Gdi" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Hlsl" = [ "Win32_Graphics" ];
+          "Win32_Graphics_OpenGL" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Printing" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Printing_PrintTicket" = [ "Win32_Graphics_Printing" ];
+          "Win32_Management" = [ "Win32" ];
+          "Win32_Management_MobileDeviceManagementRegistration" = [ "Win32_Management" ];
+          "Win32_Media" = [ "Win32" ];
+          "Win32_Media_Audio" = [ "Win32_Media" ];
+          "Win32_Media_Audio_Apo" = [ "Win32_Media_Audio" ];
+          "Win32_Media_Audio_DirectMusic" = [ "Win32_Media_Audio" ];
+          "Win32_Media_Audio_Endpoints" = [ "Win32_Media_Audio" ];
+          "Win32_Media_Audio_XAudio2" = [ "Win32_Media_Audio" ];
+          "Win32_Media_DeviceManager" = [ "Win32_Media" ];
+          "Win32_Media_DxMediaObjects" = [ "Win32_Media" ];
+          "Win32_Media_KernelStreaming" = [ "Win32_Media" ];
+          "Win32_Media_LibrarySharingServices" = [ "Win32_Media" ];
+          "Win32_Media_MediaPlayer" = [ "Win32_Media" ];
+          "Win32_Media_Multimedia" = [ "Win32_Media" ];
+          "Win32_Media_Speech" = [ "Win32_Media" ];
+          "Win32_Media_Streaming" = [ "Win32_Media" ];
+          "Win32_Media_WindowsMediaFormat" = [ "Win32_Media" ];
+          "Win32_NetworkManagement" = [ "Win32" ];
+          "Win32_NetworkManagement_Dhcp" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_Dns" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_InternetConnectionWizard" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_IpHelper" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_MobileBroadband" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_Multicast" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_Ndis" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_NetBios" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_NetManagement" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_NetShell" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_NetworkDiagnosticsFramework" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_NetworkPolicyServer" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_P2P" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_QoS" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_Rras" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_Snmp" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WNet" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WebDav" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WiFi" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WindowsConnectNow" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WindowsConnectionManager" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WindowsFilteringPlatform" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WindowsFirewall" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WindowsNetworkVirtualization" = [ "Win32_NetworkManagement" ];
+          "Win32_Networking" = [ "Win32" ];
+          "Win32_Networking_ActiveDirectory" = [ "Win32_Networking" ];
+          "Win32_Networking_BackgroundIntelligentTransferService" = [ "Win32_Networking" ];
+          "Win32_Networking_Clustering" = [ "Win32_Networking" ];
+          "Win32_Networking_HttpServer" = [ "Win32_Networking" ];
+          "Win32_Networking_Ldap" = [ "Win32_Networking" ];
+          "Win32_Networking_NetworkListManager" = [ "Win32_Networking" ];
+          "Win32_Networking_RemoteDifferentialCompression" = [ "Win32_Networking" ];
+          "Win32_Networking_WebSocket" = [ "Win32_Networking" ];
+          "Win32_Networking_WinHttp" = [ "Win32_Networking" ];
+          "Win32_Networking_WinInet" = [ "Win32_Networking" ];
+          "Win32_Networking_WinSock" = [ "Win32_Networking" ];
+          "Win32_Networking_WindowsWebServices" = [ "Win32_Networking" ];
+          "Win32_Security" = [ "Win32" ];
+          "Win32_Security_AppLocker" = [ "Win32_Security" ];
+          "Win32_Security_Authentication" = [ "Win32_Security" ];
+          "Win32_Security_Authentication_Identity" = [ "Win32_Security_Authentication" ];
+          "Win32_Security_Authentication_Identity_Provider" = [ "Win32_Security_Authentication_Identity" ];
+          "Win32_Security_Authorization" = [ "Win32_Security" ];
+          "Win32_Security_Authorization_UI" = [ "Win32_Security_Authorization" ];
+          "Win32_Security_ConfigurationSnapin" = [ "Win32_Security" ];
+          "Win32_Security_Credentials" = [ "Win32_Security" ];
+          "Win32_Security_Cryptography" = [ "Win32_Security" ];
+          "Win32_Security_Cryptography_Catalog" = [ "Win32_Security_Cryptography" ];
+          "Win32_Security_Cryptography_Certificates" = [ "Win32_Security_Cryptography" ];
+          "Win32_Security_Cryptography_Sip" = [ "Win32_Security_Cryptography" ];
+          "Win32_Security_Cryptography_UI" = [ "Win32_Security_Cryptography" ];
+          "Win32_Security_DiagnosticDataQuery" = [ "Win32_Security" ];
+          "Win32_Security_DirectoryServices" = [ "Win32_Security" ];
+          "Win32_Security_EnterpriseData" = [ "Win32_Security" ];
+          "Win32_Security_ExtensibleAuthenticationProtocol" = [ "Win32_Security" ];
+          "Win32_Security_Isolation" = [ "Win32_Security" ];
+          "Win32_Security_LicenseProtection" = [ "Win32_Security" ];
+          "Win32_Security_NetworkAccessProtection" = [ "Win32_Security" ];
+          "Win32_Security_Tpm" = [ "Win32_Security" ];
+          "Win32_Security_WinTrust" = [ "Win32_Security" ];
+          "Win32_Security_WinWlx" = [ "Win32_Security" ];
+          "Win32_Storage" = [ "Win32" ];
+          "Win32_Storage_Cabinets" = [ "Win32_Storage" ];
+          "Win32_Storage_CloudFilters" = [ "Win32_Storage" ];
+          "Win32_Storage_Compression" = [ "Win32_Storage" ];
+          "Win32_Storage_DataDeduplication" = [ "Win32_Storage" ];
+          "Win32_Storage_DistributedFileSystem" = [ "Win32_Storage" ];
+          "Win32_Storage_EnhancedStorage" = [ "Win32_Storage" ];
+          "Win32_Storage_FileHistory" = [ "Win32_Storage" ];
+          "Win32_Storage_FileServerResourceManager" = [ "Win32_Storage" ];
+          "Win32_Storage_FileSystem" = [ "Win32_Storage" ];
+          "Win32_Storage_Imapi" = [ "Win32_Storage" ];
+          "Win32_Storage_IndexServer" = [ "Win32_Storage" ];
+          "Win32_Storage_InstallableFileSystems" = [ "Win32_Storage" ];
+          "Win32_Storage_IscsiDisc" = [ "Win32_Storage" ];
+          "Win32_Storage_Jet" = [ "Win32_Storage" ];
+          "Win32_Storage_OfflineFiles" = [ "Win32_Storage" ];
+          "Win32_Storage_OperationRecorder" = [ "Win32_Storage" ];
+          "Win32_Storage_Packaging" = [ "Win32_Storage" ];
+          "Win32_Storage_Packaging_Appx" = [ "Win32_Storage_Packaging" ];
+          "Win32_Storage_Packaging_Opc" = [ "Win32_Storage_Packaging" ];
+          "Win32_Storage_ProjectedFileSystem" = [ "Win32_Storage" ];
+          "Win32_Storage_StructuredStorage" = [ "Win32_Storage" ];
+          "Win32_Storage_Vhd" = [ "Win32_Storage" ];
+          "Win32_Storage_VirtualDiskService" = [ "Win32_Storage" ];
+          "Win32_Storage_Vss" = [ "Win32_Storage" ];
+          "Win32_Storage_Xps" = [ "Win32_Storage" ];
+          "Win32_Storage_Xps_Printing" = [ "Win32_Storage_Xps" ];
+          "Win32_System" = [ "Win32" ];
+          "Win32_System_AddressBook" = [ "Win32_System" ];
+          "Win32_System_Antimalware" = [ "Win32_System" ];
+          "Win32_System_ApplicationInstallationAndServicing" = [ "Win32_System" ];
+          "Win32_System_ApplicationVerifier" = [ "Win32_System" ];
+          "Win32_System_AssessmentTool" = [ "Win32_System" ];
+          "Win32_System_ClrHosting" = [ "Win32_System" ];
+          "Win32_System_Com" = [ "Win32_System" ];
+          "Win32_System_Com_CallObj" = [ "Win32_System_Com" ];
+          "Win32_System_Com_ChannelCredentials" = [ "Win32_System_Com" ];
+          "Win32_System_Com_Events" = [ "Win32_System_Com" ];
+          "Win32_System_Com_Marshal" = [ "Win32_System_Com" ];
+          "Win32_System_Com_StructuredStorage" = [ "Win32_System_Com" ];
+          "Win32_System_Com_UI" = [ "Win32_System_Com" ];
+          "Win32_System_Com_Urlmon" = [ "Win32_System_Com" ];
+          "Win32_System_ComponentServices" = [ "Win32_System" ];
+          "Win32_System_Console" = [ "Win32_System" ];
+          "Win32_System_Contacts" = [ "Win32_System" ];
+          "Win32_System_CorrelationVector" = [ "Win32_System" ];
+          "Win32_System_DataExchange" = [ "Win32_System" ];
+          "Win32_System_DeploymentServices" = [ "Win32_System" ];
+          "Win32_System_DesktopSharing" = [ "Win32_System" ];
+          "Win32_System_DeveloperLicensing" = [ "Win32_System" ];
+          "Win32_System_Diagnostics" = [ "Win32_System" ];
+          "Win32_System_Diagnostics_Ceip" = [ "Win32_System_Diagnostics" ];
+          "Win32_System_Diagnostics_ClrProfiling" = [ "Win32_System_Diagnostics" ];
+          "Win32_System_Diagnostics_Debug" = [ "Win32_System_Diagnostics" ];
+          "Win32_System_Diagnostics_Debug_ActiveScript" = [ "Win32_System_Diagnostics_Debug" ];
+          "Win32_System_Diagnostics_Debug_Extensions" = [ "Win32_System_Diagnostics_Debug" ];
+          "Win32_System_Diagnostics_Etw" = [ "Win32_System_Diagnostics" ];
+          "Win32_System_Diagnostics_ProcessSnapshotting" = [ "Win32_System_Diagnostics" ];
+          "Win32_System_Diagnostics_ToolHelp" = [ "Win32_System_Diagnostics" ];
+          "Win32_System_DistributedTransactionCoordinator" = [ "Win32_System" ];
+          "Win32_System_Environment" = [ "Win32_System" ];
+          "Win32_System_ErrorReporting" = [ "Win32_System" ];
+          "Win32_System_EventCollector" = [ "Win32_System" ];
+          "Win32_System_EventLog" = [ "Win32_System" ];
+          "Win32_System_EventNotificationService" = [ "Win32_System" ];
+          "Win32_System_GroupPolicy" = [ "Win32_System" ];
+          "Win32_System_HostCompute" = [ "Win32_System" ];
+          "Win32_System_HostComputeNetwork" = [ "Win32_System" ];
+          "Win32_System_HostComputeSystem" = [ "Win32_System" ];
+          "Win32_System_Hypervisor" = [ "Win32_System" ];
+          "Win32_System_IO" = [ "Win32_System" ];
+          "Win32_System_Iis" = [ "Win32_System" ];
+          "Win32_System_Ioctl" = [ "Win32_System" ];
+          "Win32_System_JobObjects" = [ "Win32_System" ];
+          "Win32_System_Js" = [ "Win32_System" ];
+          "Win32_System_Kernel" = [ "Win32_System" ];
+          "Win32_System_LibraryLoader" = [ "Win32_System" ];
+          "Win32_System_Mailslots" = [ "Win32_System" ];
+          "Win32_System_Mapi" = [ "Win32_System" ];
+          "Win32_System_Memory" = [ "Win32_System" ];
+          "Win32_System_Memory_NonVolatile" = [ "Win32_System_Memory" ];
+          "Win32_System_MessageQueuing" = [ "Win32_System" ];
+          "Win32_System_MixedReality" = [ "Win32_System" ];
+          "Win32_System_Mmc" = [ "Win32_System" ];
+          "Win32_System_Ole" = [ "Win32_System" ];
+          "Win32_System_ParentalControls" = [ "Win32_System" ];
+          "Win32_System_PasswordManagement" = [ "Win32_System" ];
+          "Win32_System_Performance" = [ "Win32_System" ];
+          "Win32_System_Performance_HardwareCounterProfiling" = [ "Win32_System_Performance" ];
+          "Win32_System_Pipes" = [ "Win32_System" ];
+          "Win32_System_Power" = [ "Win32_System" ];
+          "Win32_System_ProcessStatus" = [ "Win32_System" ];
+          "Win32_System_RealTimeCommunications" = [ "Win32_System" ];
+          "Win32_System_Recovery" = [ "Win32_System" ];
+          "Win32_System_Registry" = [ "Win32_System" ];
+          "Win32_System_RemoteAssistance" = [ "Win32_System" ];
+          "Win32_System_RemoteDesktop" = [ "Win32_System" ];
+          "Win32_System_RemoteManagement" = [ "Win32_System" ];
+          "Win32_System_RestartManager" = [ "Win32_System" ];
+          "Win32_System_Restore" = [ "Win32_System" ];
+          "Win32_System_Rpc" = [ "Win32_System" ];
+          "Win32_System_Search" = [ "Win32_System" ];
+          "Win32_System_Search_Common" = [ "Win32_System_Search" ];
+          "Win32_System_SecurityCenter" = [ "Win32_System" ];
+          "Win32_System_ServerBackup" = [ "Win32_System" ];
+          "Win32_System_Services" = [ "Win32_System" ];
+          "Win32_System_SettingsManagementInfrastructure" = [ "Win32_System" ];
+          "Win32_System_SetupAndMigration" = [ "Win32_System" ];
+          "Win32_System_Shutdown" = [ "Win32_System" ];
+          "Win32_System_StationsAndDesktops" = [ "Win32_System" ];
+          "Win32_System_SubsystemForLinux" = [ "Win32_System" ];
+          "Win32_System_SystemInformation" = [ "Win32_System" ];
+          "Win32_System_SystemServices" = [ "Win32_System" ];
+          "Win32_System_TaskScheduler" = [ "Win32_System" ];
+          "Win32_System_Threading" = [ "Win32_System" ];
+          "Win32_System_Time" = [ "Win32_System" ];
+          "Win32_System_TpmBaseServices" = [ "Win32_System" ];
+          "Win32_System_UpdateAgent" = [ "Win32_System" ];
+          "Win32_System_UpdateAssessment" = [ "Win32_System" ];
+          "Win32_System_UserAccessLogging" = [ "Win32_System" ];
+          "Win32_System_VirtualDosMachines" = [ "Win32_System" ];
+          "Win32_System_WindowsProgramming" = [ "Win32_System" ];
+          "Win32_System_WindowsSync" = [ "Win32_System" ];
+          "Win32_System_Wmi" = [ "Win32_System" ];
+          "Win32_UI" = [ "Win32" ];
+          "Win32_UI_Accessibility" = [ "Win32_UI" ];
+          "Win32_UI_Animation" = [ "Win32_UI" ];
+          "Win32_UI_ColorSystem" = [ "Win32_UI" ];
+          "Win32_UI_Controls" = [ "Win32_UI" ];
+          "Win32_UI_Controls_Dialogs" = [ "Win32_UI_Controls" ];
+          "Win32_UI_Controls_RichEdit" = [ "Win32_UI_Controls" ];
+          "Win32_UI_HiDpi" = [ "Win32_UI" ];
+          "Win32_UI_Input" = [ "Win32_UI" ];
+          "Win32_UI_Input_Ime" = [ "Win32_UI_Input" ];
+          "Win32_UI_Input_Ink" = [ "Win32_UI_Input" ];
+          "Win32_UI_Input_KeyboardAndMouse" = [ "Win32_UI_Input" ];
+          "Win32_UI_Input_Pointer" = [ "Win32_UI_Input" ];
+          "Win32_UI_Input_Radial" = [ "Win32_UI_Input" ];
+          "Win32_UI_Input_Touch" = [ "Win32_UI_Input" ];
+          "Win32_UI_Input_XboxController" = [ "Win32_UI_Input" ];
+          "Win32_UI_InteractionContext" = [ "Win32_UI" ];
+          "Win32_UI_LegacyWindowsEnvironmentFeatures" = [ "Win32_UI" ];
+          "Win32_UI_Magnification" = [ "Win32_UI" ];
+          "Win32_UI_Notifications" = [ "Win32_UI" ];
+          "Win32_UI_Ribbon" = [ "Win32_UI" ];
+          "Win32_UI_Shell" = [ "Win32_UI" ];
+          "Win32_UI_Shell_Common" = [ "Win32_UI_Shell" ];
+          "Win32_UI_Shell_PropertiesSystem" = [ "Win32_UI_Shell" ];
+          "Win32_UI_TabletPC" = [ "Win32_UI" ];
+          "Win32_UI_TextServices" = [ "Win32_UI" ];
+          "Win32_UI_WindowsAndMessaging" = [ "Win32_UI" ];
+          "Win32_UI_Wpf" = [ "Win32_UI" ];
+          "Win32_Web" = [ "Win32" ];
+          "Win32_Web_InternetExplorer" = [ "Win32_Web" ];
+        };
+        resolvedDefaultFeatures = [ "Win32" "Win32_Foundation" "Win32_Networking" "Win32_Networking_WinSock" "Win32_Security" "Win32_Storage" "Win32_Storage_FileSystem" "Win32_System" "Win32_System_Console" "Win32_System_Diagnostics" "Win32_System_Diagnostics_Debug" "Win32_System_IO" "Win32_System_Pipes" "Win32_System_Registry" "Win32_System_SystemServices" "Win32_System_Threading" "Win32_System_Time" "Win32_System_WindowsProgramming" "default" ];
+      };
+      "windows-sys 0.52.0" = rec {
+        crateName = "windows-sys";
+        version = "0.52.0";
+        edition = "2021";
+        sha256 = "0gd3v4ji88490zgb6b5mq5zgbvwv7zx1ibn8v3x83rwcdbryaar8";
+        authors = [
+          "Microsoft"
+        ];
+        dependencies = [
+          {
+            name = "windows-targets";
+            packageId = "windows-targets 0.52.0";
+          }
+        ];
+        features = {
+          "Wdk_Foundation" = [ "Wdk" ];
+          "Wdk_Graphics" = [ "Wdk" ];
+          "Wdk_Graphics_Direct3D" = [ "Wdk_Graphics" ];
+          "Wdk_Storage" = [ "Wdk" ];
+          "Wdk_Storage_FileSystem" = [ "Wdk_Storage" ];
+          "Wdk_Storage_FileSystem_Minifilters" = [ "Wdk_Storage_FileSystem" ];
+          "Wdk_System" = [ "Wdk" ];
+          "Wdk_System_IO" = [ "Wdk_System" ];
+          "Wdk_System_OfflineRegistry" = [ "Wdk_System" ];
+          "Wdk_System_Registry" = [ "Wdk_System" ];
+          "Wdk_System_SystemInformation" = [ "Wdk_System" ];
+          "Wdk_System_SystemServices" = [ "Wdk_System" ];
+          "Wdk_System_Threading" = [ "Wdk_System" ];
+          "Win32_Data" = [ "Win32" ];
+          "Win32_Data_HtmlHelp" = [ "Win32_Data" ];
+          "Win32_Data_RightsManagement" = [ "Win32_Data" ];
+          "Win32_Devices" = [ "Win32" ];
+          "Win32_Devices_AllJoyn" = [ "Win32_Devices" ];
+          "Win32_Devices_BiometricFramework" = [ "Win32_Devices" ];
+          "Win32_Devices_Bluetooth" = [ "Win32_Devices" ];
+          "Win32_Devices_Communication" = [ "Win32_Devices" ];
+          "Win32_Devices_DeviceAndDriverInstallation" = [ "Win32_Devices" ];
+          "Win32_Devices_DeviceQuery" = [ "Win32_Devices" ];
+          "Win32_Devices_Display" = [ "Win32_Devices" ];
+          "Win32_Devices_Enumeration" = [ "Win32_Devices" ];
+          "Win32_Devices_Enumeration_Pnp" = [ "Win32_Devices_Enumeration" ];
+          "Win32_Devices_Fax" = [ "Win32_Devices" ];
+          "Win32_Devices_HumanInterfaceDevice" = [ "Win32_Devices" ];
+          "Win32_Devices_PortableDevices" = [ "Win32_Devices" ];
+          "Win32_Devices_Properties" = [ "Win32_Devices" ];
+          "Win32_Devices_Pwm" = [ "Win32_Devices" ];
+          "Win32_Devices_Sensors" = [ "Win32_Devices" ];
+          "Win32_Devices_SerialCommunication" = [ "Win32_Devices" ];
+          "Win32_Devices_Tapi" = [ "Win32_Devices" ];
+          "Win32_Devices_Usb" = [ "Win32_Devices" ];
+          "Win32_Devices_WebServicesOnDevices" = [ "Win32_Devices" ];
+          "Win32_Foundation" = [ "Win32" ];
+          "Win32_Gaming" = [ "Win32" ];
+          "Win32_Globalization" = [ "Win32" ];
+          "Win32_Graphics" = [ "Win32" ];
+          "Win32_Graphics_Dwm" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Gdi" = [ "Win32_Graphics" ];
+          "Win32_Graphics_GdiPlus" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Hlsl" = [ "Win32_Graphics" ];
+          "Win32_Graphics_OpenGL" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Printing" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Printing_PrintTicket" = [ "Win32_Graphics_Printing" ];
+          "Win32_Management" = [ "Win32" ];
+          "Win32_Management_MobileDeviceManagementRegistration" = [ "Win32_Management" ];
+          "Win32_Media" = [ "Win32" ];
+          "Win32_Media_Audio" = [ "Win32_Media" ];
+          "Win32_Media_DxMediaObjects" = [ "Win32_Media" ];
+          "Win32_Media_KernelStreaming" = [ "Win32_Media" ];
+          "Win32_Media_Multimedia" = [ "Win32_Media" ];
+          "Win32_Media_Streaming" = [ "Win32_Media" ];
+          "Win32_Media_WindowsMediaFormat" = [ "Win32_Media" ];
+          "Win32_NetworkManagement" = [ "Win32" ];
+          "Win32_NetworkManagement_Dhcp" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_Dns" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_InternetConnectionWizard" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_IpHelper" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_Multicast" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_Ndis" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_NetBios" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_NetManagement" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_NetShell" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_NetworkDiagnosticsFramework" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_P2P" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_QoS" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_Rras" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_Snmp" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WNet" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WebDav" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WiFi" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WindowsConnectionManager" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WindowsFilteringPlatform" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WindowsFirewall" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WindowsNetworkVirtualization" = [ "Win32_NetworkManagement" ];
+          "Win32_Networking" = [ "Win32" ];
+          "Win32_Networking_ActiveDirectory" = [ "Win32_Networking" ];
+          "Win32_Networking_Clustering" = [ "Win32_Networking" ];
+          "Win32_Networking_HttpServer" = [ "Win32_Networking" ];
+          "Win32_Networking_Ldap" = [ "Win32_Networking" ];
+          "Win32_Networking_WebSocket" = [ "Win32_Networking" ];
+          "Win32_Networking_WinHttp" = [ "Win32_Networking" ];
+          "Win32_Networking_WinInet" = [ "Win32_Networking" ];
+          "Win32_Networking_WinSock" = [ "Win32_Networking" ];
+          "Win32_Networking_WindowsWebServices" = [ "Win32_Networking" ];
+          "Win32_Security" = [ "Win32" ];
+          "Win32_Security_AppLocker" = [ "Win32_Security" ];
+          "Win32_Security_Authentication" = [ "Win32_Security" ];
+          "Win32_Security_Authentication_Identity" = [ "Win32_Security_Authentication" ];
+          "Win32_Security_Authorization" = [ "Win32_Security" ];
+          "Win32_Security_Credentials" = [ "Win32_Security" ];
+          "Win32_Security_Cryptography" = [ "Win32_Security" ];
+          "Win32_Security_Cryptography_Catalog" = [ "Win32_Security_Cryptography" ];
+          "Win32_Security_Cryptography_Certificates" = [ "Win32_Security_Cryptography" ];
+          "Win32_Security_Cryptography_Sip" = [ "Win32_Security_Cryptography" ];
+          "Win32_Security_Cryptography_UI" = [ "Win32_Security_Cryptography" ];
+          "Win32_Security_DiagnosticDataQuery" = [ "Win32_Security" ];
+          "Win32_Security_DirectoryServices" = [ "Win32_Security" ];
+          "Win32_Security_EnterpriseData" = [ "Win32_Security" ];
+          "Win32_Security_ExtensibleAuthenticationProtocol" = [ "Win32_Security" ];
+          "Win32_Security_Isolation" = [ "Win32_Security" ];
+          "Win32_Security_LicenseProtection" = [ "Win32_Security" ];
+          "Win32_Security_NetworkAccessProtection" = [ "Win32_Security" ];
+          "Win32_Security_WinTrust" = [ "Win32_Security" ];
+          "Win32_Security_WinWlx" = [ "Win32_Security" ];
+          "Win32_Storage" = [ "Win32" ];
+          "Win32_Storage_Cabinets" = [ "Win32_Storage" ];
+          "Win32_Storage_CloudFilters" = [ "Win32_Storage" ];
+          "Win32_Storage_Compression" = [ "Win32_Storage" ];
+          "Win32_Storage_DistributedFileSystem" = [ "Win32_Storage" ];
+          "Win32_Storage_FileHistory" = [ "Win32_Storage" ];
+          "Win32_Storage_FileSystem" = [ "Win32_Storage" ];
+          "Win32_Storage_Imapi" = [ "Win32_Storage" ];
+          "Win32_Storage_IndexServer" = [ "Win32_Storage" ];
+          "Win32_Storage_InstallableFileSystems" = [ "Win32_Storage" ];
+          "Win32_Storage_IscsiDisc" = [ "Win32_Storage" ];
+          "Win32_Storage_Jet" = [ "Win32_Storage" ];
+          "Win32_Storage_Nvme" = [ "Win32_Storage" ];
+          "Win32_Storage_OfflineFiles" = [ "Win32_Storage" ];
+          "Win32_Storage_OperationRecorder" = [ "Win32_Storage" ];
+          "Win32_Storage_Packaging" = [ "Win32_Storage" ];
+          "Win32_Storage_Packaging_Appx" = [ "Win32_Storage_Packaging" ];
+          "Win32_Storage_ProjectedFileSystem" = [ "Win32_Storage" ];
+          "Win32_Storage_StructuredStorage" = [ "Win32_Storage" ];
+          "Win32_Storage_Vhd" = [ "Win32_Storage" ];
+          "Win32_Storage_Xps" = [ "Win32_Storage" ];
+          "Win32_System" = [ "Win32" ];
+          "Win32_System_AddressBook" = [ "Win32_System" ];
+          "Win32_System_Antimalware" = [ "Win32_System" ];
+          "Win32_System_ApplicationInstallationAndServicing" = [ "Win32_System" ];
+          "Win32_System_ApplicationVerifier" = [ "Win32_System" ];
+          "Win32_System_ClrHosting" = [ "Win32_System" ];
+          "Win32_System_Com" = [ "Win32_System" ];
+          "Win32_System_Com_Marshal" = [ "Win32_System_Com" ];
+          "Win32_System_Com_StructuredStorage" = [ "Win32_System_Com" ];
+          "Win32_System_Com_Urlmon" = [ "Win32_System_Com" ];
+          "Win32_System_ComponentServices" = [ "Win32_System" ];
+          "Win32_System_Console" = [ "Win32_System" ];
+          "Win32_System_CorrelationVector" = [ "Win32_System" ];
+          "Win32_System_DataExchange" = [ "Win32_System" ];
+          "Win32_System_DeploymentServices" = [ "Win32_System" ];
+          "Win32_System_DeveloperLicensing" = [ "Win32_System" ];
+          "Win32_System_Diagnostics" = [ "Win32_System" ];
+          "Win32_System_Diagnostics_Ceip" = [ "Win32_System_Diagnostics" ];
+          "Win32_System_Diagnostics_Debug" = [ "Win32_System_Diagnostics" ];
+          "Win32_System_Diagnostics_Debug_Extensions" = [ "Win32_System_Diagnostics_Debug" ];
+          "Win32_System_Diagnostics_Etw" = [ "Win32_System_Diagnostics" ];
+          "Win32_System_Diagnostics_ProcessSnapshotting" = [ "Win32_System_Diagnostics" ];
+          "Win32_System_Diagnostics_ToolHelp" = [ "Win32_System_Diagnostics" ];
+          "Win32_System_DistributedTransactionCoordinator" = [ "Win32_System" ];
+          "Win32_System_Environment" = [ "Win32_System" ];
+          "Win32_System_ErrorReporting" = [ "Win32_System" ];
+          "Win32_System_EventCollector" = [ "Win32_System" ];
+          "Win32_System_EventLog" = [ "Win32_System" ];
+          "Win32_System_EventNotificationService" = [ "Win32_System" ];
+          "Win32_System_GroupPolicy" = [ "Win32_System" ];
+          "Win32_System_HostCompute" = [ "Win32_System" ];
+          "Win32_System_HostComputeNetwork" = [ "Win32_System" ];
+          "Win32_System_HostComputeSystem" = [ "Win32_System" ];
+          "Win32_System_Hypervisor" = [ "Win32_System" ];
+          "Win32_System_IO" = [ "Win32_System" ];
+          "Win32_System_Iis" = [ "Win32_System" ];
+          "Win32_System_Ioctl" = [ "Win32_System" ];
+          "Win32_System_JobObjects" = [ "Win32_System" ];
+          "Win32_System_Js" = [ "Win32_System" ];
+          "Win32_System_Kernel" = [ "Win32_System" ];
+          "Win32_System_LibraryLoader" = [ "Win32_System" ];
+          "Win32_System_Mailslots" = [ "Win32_System" ];
+          "Win32_System_Mapi" = [ "Win32_System" ];
+          "Win32_System_Memory" = [ "Win32_System" ];
+          "Win32_System_Memory_NonVolatile" = [ "Win32_System_Memory" ];
+          "Win32_System_MessageQueuing" = [ "Win32_System" ];
+          "Win32_System_MixedReality" = [ "Win32_System" ];
+          "Win32_System_Ole" = [ "Win32_System" ];
+          "Win32_System_PasswordManagement" = [ "Win32_System" ];
+          "Win32_System_Performance" = [ "Win32_System" ];
+          "Win32_System_Performance_HardwareCounterProfiling" = [ "Win32_System_Performance" ];
+          "Win32_System_Pipes" = [ "Win32_System" ];
+          "Win32_System_Power" = [ "Win32_System" ];
+          "Win32_System_ProcessStatus" = [ "Win32_System" ];
+          "Win32_System_Recovery" = [ "Win32_System" ];
+          "Win32_System_Registry" = [ "Win32_System" ];
+          "Win32_System_RemoteDesktop" = [ "Win32_System" ];
+          "Win32_System_RemoteManagement" = [ "Win32_System" ];
+          "Win32_System_RestartManager" = [ "Win32_System" ];
+          "Win32_System_Restore" = [ "Win32_System" ];
+          "Win32_System_Rpc" = [ "Win32_System" ];
+          "Win32_System_Search" = [ "Win32_System" ];
+          "Win32_System_Search_Common" = [ "Win32_System_Search" ];
+          "Win32_System_SecurityCenter" = [ "Win32_System" ];
+          "Win32_System_Services" = [ "Win32_System" ];
+          "Win32_System_SetupAndMigration" = [ "Win32_System" ];
+          "Win32_System_Shutdown" = [ "Win32_System" ];
+          "Win32_System_StationsAndDesktops" = [ "Win32_System" ];
+          "Win32_System_SubsystemForLinux" = [ "Win32_System" ];
+          "Win32_System_SystemInformation" = [ "Win32_System" ];
+          "Win32_System_SystemServices" = [ "Win32_System" ];
+          "Win32_System_Threading" = [ "Win32_System" ];
+          "Win32_System_Time" = [ "Win32_System" ];
+          "Win32_System_TpmBaseServices" = [ "Win32_System" ];
+          "Win32_System_UserAccessLogging" = [ "Win32_System" ];
+          "Win32_System_Variant" = [ "Win32_System" ];
+          "Win32_System_VirtualDosMachines" = [ "Win32_System" ];
+          "Win32_System_WindowsProgramming" = [ "Win32_System" ];
+          "Win32_System_Wmi" = [ "Win32_System" ];
+          "Win32_UI" = [ "Win32" ];
+          "Win32_UI_Accessibility" = [ "Win32_UI" ];
+          "Win32_UI_ColorSystem" = [ "Win32_UI" ];
+          "Win32_UI_Controls" = [ "Win32_UI" ];
+          "Win32_UI_Controls_Dialogs" = [ "Win32_UI_Controls" ];
+          "Win32_UI_HiDpi" = [ "Win32_UI" ];
+          "Win32_UI_Input" = [ "Win32_UI" ];
+          "Win32_UI_Input_Ime" = [ "Win32_UI_Input" ];
+          "Win32_UI_Input_KeyboardAndMouse" = [ "Win32_UI_Input" ];
+          "Win32_UI_Input_Pointer" = [ "Win32_UI_Input" ];
+          "Win32_UI_Input_Touch" = [ "Win32_UI_Input" ];
+          "Win32_UI_Input_XboxController" = [ "Win32_UI_Input" ];
+          "Win32_UI_InteractionContext" = [ "Win32_UI" ];
+          "Win32_UI_Magnification" = [ "Win32_UI" ];
+          "Win32_UI_Shell" = [ "Win32_UI" ];
+          "Win32_UI_Shell_PropertiesSystem" = [ "Win32_UI_Shell" ];
+          "Win32_UI_TabletPC" = [ "Win32_UI" ];
+          "Win32_UI_TextServices" = [ "Win32_UI" ];
+          "Win32_UI_WindowsAndMessaging" = [ "Win32_UI" ];
+          "Win32_Web" = [ "Win32" ];
+          "Win32_Web_InternetExplorer" = [ "Win32_Web" ];
+        };
+        resolvedDefaultFeatures = [ "Wdk" "Wdk_Foundation" "Wdk_Storage" "Wdk_Storage_FileSystem" "Win32" "Win32_Foundation" "Win32_NetworkManagement" "Win32_NetworkManagement_IpHelper" "Win32_Networking" "Win32_Networking_WinSock" "Win32_Security" "Win32_Security_Authentication" "Win32_Security_Authentication_Identity" "Win32_Security_Credentials" "Win32_Security_Cryptography" "Win32_Storage" "Win32_Storage_FileSystem" "Win32_System" "Win32_System_Com" "Win32_System_Console" "Win32_System_Diagnostics" "Win32_System_Diagnostics_Debug" "Win32_System_IO" "Win32_System_LibraryLoader" "Win32_System_Memory" "Win32_System_Threading" "Win32_System_WindowsProgramming" "Win32_UI" "Win32_UI_Shell" "default" ];
+      };
+      "windows-targets 0.48.5" = rec {
+        crateName = "windows-targets";
+        version = "0.48.5";
+        edition = "2018";
+        sha256 = "034ljxqshifs1lan89xwpcy1hp0lhdh4b5n0d2z4fwjx2piacbws";
+        authors = [
+          "Microsoft"
+        ];
+        dependencies = [
+          {
+            name = "windows_aarch64_gnullvm";
+            packageId = "windows_aarch64_gnullvm 0.48.5";
+            target = { target, features }: (pkgs.rust.lib.toRustTarget stdenv.hostPlatform == "aarch64-pc-windows-gnullvm");
+          }
+          {
+            name = "windows_aarch64_msvc";
+            packageId = "windows_aarch64_msvc 0.48.5";
+            target = { target, features }: (("aarch64" == target."arch" or null) && ("msvc" == target."env" or null) && (!(target."windows_raw_dylib" or false)));
+          }
+          {
+            name = "windows_i686_gnu";
+            packageId = "windows_i686_gnu 0.48.5";
+            target = { target, features }: (("x86" == target."arch" or null) && ("gnu" == target."env" or null) && (!(target."windows_raw_dylib" or false)));
+          }
+          {
+            name = "windows_i686_msvc";
+            packageId = "windows_i686_msvc 0.48.5";
+            target = { target, features }: (("x86" == target."arch" or null) && ("msvc" == target."env" or null) && (!(target."windows_raw_dylib" or false)));
+          }
+          {
+            name = "windows_x86_64_gnu";
+            packageId = "windows_x86_64_gnu 0.48.5";
+            target = { target, features }: (("x86_64" == target."arch" or null) && ("gnu" == target."env" or null) && (!("llvm" == target."abi" or null)) && (!(target."windows_raw_dylib" or false)));
+          }
+          {
+            name = "windows_x86_64_gnullvm";
+            packageId = "windows_x86_64_gnullvm 0.48.5";
+            target = { target, features }: (pkgs.rust.lib.toRustTarget stdenv.hostPlatform == "x86_64-pc-windows-gnullvm");
+          }
+          {
+            name = "windows_x86_64_msvc";
+            packageId = "windows_x86_64_msvc 0.48.5";
+            target = { target, features }: (("x86_64" == target."arch" or null) && ("msvc" == target."env" or null) && (!(target."windows_raw_dylib" or false)));
+          }
+        ];
+
+      };
+      "windows-targets 0.52.0" = rec {
+        crateName = "windows-targets";
+        version = "0.52.0";
+        edition = "2021";
+        sha256 = "1kg7a27ynzw8zz3krdgy6w5gbqcji27j1sz4p7xk2j5j8082064a";
+        authors = [
+          "Microsoft"
+        ];
+        dependencies = [
+          {
+            name = "windows_aarch64_gnullvm";
+            packageId = "windows_aarch64_gnullvm 0.52.0";
+            target = { target, features }: (pkgs.rust.lib.toRustTarget stdenv.hostPlatform == "aarch64-pc-windows-gnullvm");
+          }
+          {
+            name = "windows_aarch64_msvc";
+            packageId = "windows_aarch64_msvc 0.52.0";
+            target = { target, features }: (("aarch64" == target."arch" or null) && ("msvc" == target."env" or null) && (!(target."windows_raw_dylib" or false)));
+          }
+          {
+            name = "windows_i686_gnu";
+            packageId = "windows_i686_gnu 0.52.0";
+            target = { target, features }: (("x86" == target."arch" or null) && ("gnu" == target."env" or null) && (!(target."windows_raw_dylib" or false)));
+          }
+          {
+            name = "windows_i686_msvc";
+            packageId = "windows_i686_msvc 0.52.0";
+            target = { target, features }: (("x86" == target."arch" or null) && ("msvc" == target."env" or null) && (!(target."windows_raw_dylib" or false)));
+          }
+          {
+            name = "windows_x86_64_gnu";
+            packageId = "windows_x86_64_gnu 0.52.0";
+            target = { target, features }: (("x86_64" == target."arch" or null) && ("gnu" == target."env" or null) && (!("llvm" == target."abi" or null)) && (!(target."windows_raw_dylib" or false)));
+          }
+          {
+            name = "windows_x86_64_gnullvm";
+            packageId = "windows_x86_64_gnullvm 0.52.0";
+            target = { target, features }: (pkgs.rust.lib.toRustTarget stdenv.hostPlatform == "x86_64-pc-windows-gnullvm");
+          }
+          {
+            name = "windows_x86_64_msvc";
+            packageId = "windows_x86_64_msvc 0.52.0";
+            target = { target, features }: (("x86_64" == target."arch" or null) && ("msvc" == target."env" or null) && (!(target."windows_raw_dylib" or false)));
+          }
+        ];
+
+      };
+      "windows_aarch64_gnullvm 0.48.5" = rec {
+        crateName = "windows_aarch64_gnullvm";
+        version = "0.48.5";
+        edition = "2018";
+        sha256 = "1n05v7qblg1ci3i567inc7xrkmywczxrs1z3lj3rkkxw18py6f1b";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "windows_aarch64_gnullvm 0.52.0" = rec {
+        crateName = "windows_aarch64_gnullvm";
+        version = "0.52.0";
+        edition = "2021";
+        sha256 = "1shmn1kbdc0bpphcxz0vlph96bxz0h1jlmh93s9agf2dbpin8xyb";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "windows_aarch64_msvc 0.48.5" = rec {
+        crateName = "windows_aarch64_msvc";
+        version = "0.48.5";
+        edition = "2018";
+        sha256 = "1g5l4ry968p73g6bg6jgyvy9lb8fyhcs54067yzxpcpkf44k2dfw";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "windows_aarch64_msvc 0.52.0" = rec {
+        crateName = "windows_aarch64_msvc";
+        version = "0.52.0";
+        edition = "2021";
+        sha256 = "1vvmy1ypvzdvxn9yf0b8ygfl85gl2gpcyvsvqppsmlpisil07amv";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "windows_i686_gnu 0.48.5" = rec {
+        crateName = "windows_i686_gnu";
+        version = "0.48.5";
+        edition = "2018";
+        sha256 = "0gklnglwd9ilqx7ac3cn8hbhkraqisd0n83jxzf9837nvvkiand7";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "windows_i686_gnu 0.52.0" = rec {
+        crateName = "windows_i686_gnu";
+        version = "0.52.0";
+        edition = "2021";
+        sha256 = "04zkglz4p3pjsns5gbz85v4s5aw102raz4spj4b0lmm33z5kg1m2";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "windows_i686_msvc 0.48.5" = rec {
+        crateName = "windows_i686_msvc";
+        version = "0.48.5";
+        edition = "2018";
+        sha256 = "01m4rik437dl9rdf0ndnm2syh10hizvq0dajdkv2fjqcywrw4mcg";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "windows_i686_msvc 0.52.0" = rec {
+        crateName = "windows_i686_msvc";
+        version = "0.52.0";
+        edition = "2021";
+        sha256 = "16kvmbvx0vr0zbgnaz6nsks9ycvfh5xp05bjrhq65kj623iyirgz";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "windows_x86_64_gnu 0.48.5" = rec {
+        crateName = "windows_x86_64_gnu";
+        version = "0.48.5";
+        edition = "2018";
+        sha256 = "13kiqqcvz2vnyxzydjh73hwgigsdr2z1xpzx313kxll34nyhmm2k";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "windows_x86_64_gnu 0.52.0" = rec {
+        crateName = "windows_x86_64_gnu";
+        version = "0.52.0";
+        edition = "2021";
+        sha256 = "1zdy4qn178sil5sdm63lm7f0kkcjg6gvdwmcprd2yjmwn8ns6vrx";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "windows_x86_64_gnullvm 0.48.5" = rec {
+        crateName = "windows_x86_64_gnullvm";
+        version = "0.48.5";
+        edition = "2018";
+        sha256 = "1k24810wfbgz8k48c2yknqjmiigmql6kk3knmddkv8k8g1v54yqb";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "windows_x86_64_gnullvm 0.52.0" = rec {
+        crateName = "windows_x86_64_gnullvm";
+        version = "0.52.0";
+        edition = "2021";
+        sha256 = "17lllq4l2k1lqgcnw1cccphxp9vs7inq99kjlm2lfl9zklg7wr8s";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "windows_x86_64_msvc 0.48.5" = rec {
+        crateName = "windows_x86_64_msvc";
+        version = "0.48.5";
+        edition = "2018";
+        sha256 = "0f4mdp895kkjh9zv8dxvn4pc10xr7839lf5pa9l0193i2pkgr57d";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "windows_x86_64_msvc 0.52.0" = rec {
+        crateName = "windows_x86_64_msvc";
+        version = "0.52.0";
+        edition = "2021";
+        sha256 = "012wfq37f18c09ij5m6rniw7xxn5fcvrxbqd0wd8vgnl3hfn9yfz";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "winreg" = rec {
+        crateName = "winreg";
+        version = "0.50.0";
+        edition = "2018";
+        sha256 = "1cddmp929k882mdh6i9f2as848f13qqna6czwsqzkh1pqnr5fkjj";
+        authors = [
+          "Igor Shaula <gentoo90@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "windows-sys";
+            packageId = "windows-sys 0.48.0";
+            features = [ "Win32_Foundation" "Win32_System_Time" "Win32_System_Registry" "Win32_Security" "Win32_Storage_FileSystem" "Win32_System_Diagnostics_Debug" ];
+          }
+        ];
+        features = {
+          "chrono" = [ "dep:chrono" ];
+          "serde" = [ "dep:serde" ];
+          "serialization-serde" = [ "transactions" "serde" ];
+        };
+      };
+      "wu-manber" = rec {
+        crateName = "wu-manber";
+        version = "0.1.0";
+        edition = "2015";
+        workspace_member = null;
+        src = pkgs.fetchgit {
+          url = "https://github.com/tvlfyi/wu-manber.git";
+          rev = "0d5b22bea136659f7de60b102a7030e0daaa503d";
+          sha256 = "1zhk83lbq99xzyjwphv2qrb8f8qgfqwa5bbbvyzm0z0bljsjv0pd";
+        };
+        authors = [
+          "Joe Neeman <joeneeman@gmail.com>"
+        ];
+
+      };
+      "xattr" = rec {
+        crateName = "xattr";
+        version = "1.3.1";
+        edition = "2021";
+        sha256 = "0kqxm36w89vc6qcpn6pizlhgjgzq138sx4hdhbv2g6wk4ld4za4d";
+        authors = [
+          "Steven Allen <steven@stebalien.com>"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (("freebsd" == target."os" or null) || ("netbsd" == target."os" or null));
+          }
+          {
+            name = "linux-raw-sys";
+            packageId = "linux-raw-sys";
+            usesDefaultFeatures = false;
+            target = { target, features }: ("linux" == target."os" or null);
+            features = [ "std" ];
+          }
+          {
+            name = "rustix";
+            packageId = "rustix";
+            usesDefaultFeatures = false;
+            features = [ "fs" "std" ];
+          }
+        ];
+        devDependencies = [
+          {
+            name = "rustix";
+            packageId = "rustix";
+            usesDefaultFeatures = false;
+            features = [ "net" ];
+          }
+        ];
+        features = {
+          "default" = [ "unsupported" ];
+        };
+        resolvedDefaultFeatures = [ "default" "unsupported" ];
+      };
+      "xml-rs" = rec {
+        crateName = "xml-rs";
+        version = "0.8.19";
+        edition = "2021";
+        crateBin = [ ];
+        sha256 = "0nnpvk3fv32hgh7vs9gbg2swmzxx5yz73f4b7rak7q39q2x9rjqg";
+        libName = "xml";
+        authors = [
+          "Vladimir Matveev <vmatveev@citrine.cc>"
+        ];
+
+      };
+      "xz2" = rec {
+        crateName = "xz2";
+        version = "0.1.7";
+        edition = "2018";
+        sha256 = "1qk7nzpblizvayyq4xzi4b0zacmmbqr6vb9fc0v1avyp17f4931q";
+        authors = [
+          "Alex Crichton <alex@alexcrichton.com>"
+        ];
+        dependencies = [
+          {
+            name = "lzma-sys";
+            packageId = "lzma-sys";
+          }
+        ];
+        features = {
+          "futures" = [ "dep:futures" ];
+          "static" = [ "lzma-sys/static" ];
+          "tokio" = [ "tokio-io" "futures" ];
+          "tokio-io" = [ "dep:tokio-io" ];
+        };
+      };
+      "yansi" = rec {
+        crateName = "yansi";
+        version = "0.5.1";
+        edition = "2015";
+        sha256 = "1v4qljgzh73knr7291cgwrf56zrvhmpn837n5n5pypzq1kciq109";
+        authors = [
+          "Sergio Benitez <sb@sergio.bz>"
+        ];
+
+      };
+      "zeroize" = rec {
+        crateName = "zeroize";
+        version = "1.7.0";
+        edition = "2021";
+        sha256 = "0bfvby7k9pdp6623p98yz2irqnamcyzpn7zh20nqmdn68b0lwnsj";
+        authors = [
+          "The RustCrypto Project Developers"
+        ];
+        features = {
+          "default" = [ "alloc" ];
+          "derive" = [ "zeroize_derive" ];
+          "serde" = [ "dep:serde" ];
+          "std" = [ "alloc" ];
+          "zeroize_derive" = [ "dep:zeroize_derive" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" ];
+      };
+      "zstd" = rec {
+        crateName = "zstd";
+        version = "0.13.0";
+        edition = "2018";
+        sha256 = "0401q54s9r35x2i7m1kwppgkj79g0pb6xz3xpby7qlkdb44k7yxz";
+        authors = [
+          "Alexandre Bury <alexandre.bury@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "zstd-safe";
+            packageId = "zstd-safe";
+            usesDefaultFeatures = false;
+            features = [ "std" ];
+          }
+        ];
+        features = {
+          "arrays" = [ "zstd-safe/arrays" ];
+          "bindgen" = [ "zstd-safe/bindgen" ];
+          "debug" = [ "zstd-safe/debug" ];
+          "default" = [ "legacy" "arrays" "zdict_builder" ];
+          "experimental" = [ "zstd-safe/experimental" ];
+          "fat-lto" = [ "zstd-safe/fat-lto" ];
+          "legacy" = [ "zstd-safe/legacy" ];
+          "no_asm" = [ "zstd-safe/no_asm" ];
+          "pkg-config" = [ "zstd-safe/pkg-config" ];
+          "thin" = [ "zstd-safe/thin" ];
+          "thin-lto" = [ "zstd-safe/thin-lto" ];
+          "zdict_builder" = [ "zstd-safe/zdict_builder" ];
+          "zstdmt" = [ "zstd-safe/zstdmt" ];
+        };
+        resolvedDefaultFeatures = [ "arrays" "default" "legacy" "zdict_builder" ];
+      };
+      "zstd-safe" = rec {
+        crateName = "zstd-safe";
+        version = "7.0.0";
+        edition = "2018";
+        sha256 = "0gpav2lcibrpmyslmjkcn3w0w64qif3jjljd2h8lr4p249s7qx23";
+        authors = [
+          "Alexandre Bury <alexandre.bury@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "zstd-sys";
+            packageId = "zstd-sys";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "bindgen" = [ "zstd-sys/bindgen" ];
+          "debug" = [ "zstd-sys/debug" ];
+          "default" = [ "legacy" "arrays" "zdict_builder" ];
+          "experimental" = [ "zstd-sys/experimental" ];
+          "fat-lto" = [ "zstd-sys/fat-lto" ];
+          "legacy" = [ "zstd-sys/legacy" ];
+          "no_asm" = [ "zstd-sys/no_asm" ];
+          "pkg-config" = [ "zstd-sys/pkg-config" ];
+          "std" = [ "zstd-sys/std" ];
+          "thin" = [ "zstd-sys/thin" ];
+          "thin-lto" = [ "zstd-sys/thin-lto" ];
+          "zdict_builder" = [ "zstd-sys/zdict_builder" ];
+          "zstdmt" = [ "zstd-sys/zstdmt" ];
+        };
+        resolvedDefaultFeatures = [ "arrays" "legacy" "std" "zdict_builder" ];
+      };
+      "zstd-sys" = rec {
+        crateName = "zstd-sys";
+        version = "2.0.9+zstd.1.5.5";
+        edition = "2018";
+        links = "zstd";
+        sha256 = "0mk6a2367swdi22zg03lcackpnvgq96d7120awd4i83lm2lfy5ly";
+        authors = [
+          "Alexandre Bury <alexandre.bury@gmail.com>"
+        ];
+        buildDependencies = [
+          {
+            name = "cc";
+            packageId = "cc";
+            features = [ "parallel" ];
+          }
+          {
+            name = "pkg-config";
+            packageId = "pkg-config";
+          }
+        ];
+        features = {
+          "bindgen" = [ "dep:bindgen" ];
+          "default" = [ "legacy" "zdict_builder" ];
+        };
+        resolvedDefaultFeatures = [ "legacy" "std" "zdict_builder" ];
+      };
+    };
+
+    #
+    # crate2nix/default.nix (excerpt start)
+    #
+
+    /* Target (platform) data for conditional dependencies.
+      This corresponds roughly to what buildRustCrate is setting.
+    */
+    makeDefaultTarget = platform: {
+      unix = platform.isUnix;
+      windows = platform.isWindows;
+      fuchsia = true;
+      test = false;
+
+      /* We are choosing an arbitrary rust version to grab `lib` from,
+      which is unfortunate, but `lib` has been version-agnostic the
+      whole time so this is good enough for now.
+      */
+      os = pkgs.rust.lib.toTargetOs platform;
+      arch = pkgs.rust.lib.toTargetArch platform;
+      family = pkgs.rust.lib.toTargetFamily platform;
+      vendor = pkgs.rust.lib.toTargetVendor platform;
+      env = "gnu";
+      endian =
+        if platform.parsed.cpu.significantByte.name == "littleEndian"
+        then "little" else "big";
+      pointer_width = toString platform.parsed.cpu.bits;
+      debug_assertions = false;
+    };
+
+    /* Filters common temp files and build files. */
+    # TODO(pkolloch): Substitute with gitignore filter
+    sourceFilter = name: type:
+      let
+        baseName = builtins.baseNameOf (builtins.toString name);
+      in
+        ! (
+          # Filter out git
+          baseName == ".gitignore"
+          || (type == "directory" && baseName == ".git")
+
+          # Filter out build results
+          || (
+            type == "directory" && (
+              baseName == "target"
+              || baseName == "_site"
+              || baseName == ".sass-cache"
+              || baseName == ".jekyll-metadata"
+              || baseName == "build-artifacts"
+            )
+          )
+
+          # Filter out nix-build result symlinks
+          || (
+            type == "symlink" && lib.hasPrefix "result" baseName
+          )
+
+          # Filter out IDE config
+          || (
+            type == "directory" && (
+              baseName == ".idea" || baseName == ".vscode"
+            )
+          ) || lib.hasSuffix ".iml" baseName
+
+          # Filter out nix build files
+          || baseName == "Cargo.nix"
+
+          # Filter out editor backup / swap files.
+          || lib.hasSuffix "~" baseName
+          || builtins.match "^\\.sw[a-z]$$" baseName != null
+          || builtins.match "^\\..*\\.sw[a-z]$$" baseName != null
+          || lib.hasSuffix ".tmp" baseName
+          || lib.hasSuffix ".bak" baseName
+          || baseName == "tests.nix"
+        );
+
+    /* Returns a crate which depends on successful test execution
+      of crate given as the second argument.
+
+      testCrateFlags: list of flags to pass to the test exectuable
+      testInputs: list of packages that should be available during test execution
+    */
+    crateWithTest = { crate, testCrate, testCrateFlags, testInputs, testPreRun, testPostRun }:
+      assert builtins.typeOf testCrateFlags == "list";
+      assert builtins.typeOf testInputs == "list";
+      assert builtins.typeOf testPreRun == "string";
+      assert builtins.typeOf testPostRun == "string";
+      let
+        # override the `crate` so that it will build and execute tests instead of
+        # building the actual lib and bin targets We just have to pass `--test`
+        # to rustc and it will do the right thing.  We execute the tests and copy
+        # their log and the test executables to $out for later inspection.
+        test =
+          let
+            drv = testCrate.override
+              (
+                _: {
+                  buildTests = true;
+                  release = false;
+                }
+              );
+            # If the user hasn't set any pre/post commands, we don't want to
+            # insert empty lines. This means that any existing users of crate2nix
+            # don't get a spurious rebuild unless they set these explicitly.
+            testCommand = pkgs.lib.concatStringsSep "\n"
+              (pkgs.lib.filter (s: s != "") [
+                testPreRun
+                "$f $testCrateFlags 2>&1 | tee -a $out"
+                testPostRun
+              ]);
+          in
+          pkgs.runCommand "run-tests-${testCrate.name}"
+            {
+              inherit testCrateFlags;
+              buildInputs = testInputs;
+            } ''
+            set -e
+
+            export RUST_BACKTRACE=1
+
+            # recreate a file hierarchy as when running tests with cargo
+
+            # the source for test data
+            # It's necessary to locate the source in $NIX_BUILD_TOP/source/
+            # instead of $NIX_BUILD_TOP/
+            # because we compiled those test binaries in the former and not the latter.
+            # So all paths will expect source tree to be there and not in the build top directly.
+            # For example: $NIX_BUILD_TOP := /build in general, if you ask yourself.
+            # NOTE: There could be edge cases if `crate.sourceRoot` does exist but
+            # it's very hard to reason about them.
+            # Open a bug if you run into this!
+            mkdir -p source/
+            cd source/
+
+            ${pkgs.buildPackages.xorg.lndir}/bin/lndir ${crate.src}
+
+            # build outputs
+            testRoot=target/debug
+            mkdir -p $testRoot
+
+            # executables of the crate
+            # we copy to prevent std::env::current_exe() to resolve to a store location
+            for i in ${crate}/bin/*; do
+              cp "$i" "$testRoot"
+            done
+            chmod +w -R .
+
+            # test harness executables are suffixed with a hash, like cargo does
+            # this allows to prevent name collision with the main
+            # executables of the crate
+            hash=$(basename $out)
+            for file in ${drv}/tests/*; do
+              f=$testRoot/$(basename $file)-$hash
+              cp $file $f
+              ${testCommand}
+            done
+          '';
+      in
+      pkgs.runCommand "${crate.name}-linked"
+        {
+          inherit (crate) outputs crateName;
+          passthru = (crate.passthru or { }) // {
+            inherit test;
+          };
+        }
+        (lib.optionalString (stdenv.buildPlatform.canExecute stdenv.hostPlatform) ''
+          echo tested by ${test}
+        '' + ''
+          ${lib.concatMapStringsSep "\n" (output: "ln -s ${crate.${output}} ${"$"}${output}") crate.outputs}
+        '');
+
+    /* A restricted overridable version of builtRustCratesWithFeatures. */
+    buildRustCrateWithFeatures =
+      { packageId
+      , features ? rootFeatures
+      , crateOverrides ? defaultCrateOverrides
+      , buildRustCrateForPkgsFunc ? null
+      , runTests ? false
+      , testCrateFlags ? [ ]
+      , testInputs ? [ ]
+        # Any command to run immediatelly before a test is executed.
+      , testPreRun ? ""
+        # Any command run immediatelly after a test is executed.
+      , testPostRun ? ""
+      }:
+      lib.makeOverridable
+        (
+          { features
+          , crateOverrides
+          , runTests
+          , testCrateFlags
+          , testInputs
+          , testPreRun
+          , testPostRun
+          }:
+          let
+            buildRustCrateForPkgsFuncOverriden =
+              if buildRustCrateForPkgsFunc != null
+              then buildRustCrateForPkgsFunc
+              else
+                (
+                  if crateOverrides == pkgs.defaultCrateOverrides
+                  then buildRustCrateForPkgs
+                  else
+                    pkgs: (buildRustCrateForPkgs pkgs).override {
+                      defaultCrateOverrides = crateOverrides;
+                    }
+                );
+            builtRustCrates = builtRustCratesWithFeatures {
+              inherit packageId features;
+              buildRustCrateForPkgsFunc = buildRustCrateForPkgsFuncOverriden;
+              runTests = false;
+            };
+            builtTestRustCrates = builtRustCratesWithFeatures {
+              inherit packageId features;
+              buildRustCrateForPkgsFunc = buildRustCrateForPkgsFuncOverriden;
+              runTests = true;
+            };
+            drv = builtRustCrates.crates.${packageId};
+            testDrv = builtTestRustCrates.crates.${packageId};
+            derivation =
+              if runTests then
+                crateWithTest
+                  {
+                    crate = drv;
+                    testCrate = testDrv;
+                    inherit testCrateFlags testInputs testPreRun testPostRun;
+                  }
+              else drv;
+          in
+          derivation
+        )
+        { inherit features crateOverrides runTests testCrateFlags testInputs testPreRun testPostRun; };
+
+    /* Returns an attr set with packageId mapped to the result of buildRustCrateForPkgsFunc
+      for the corresponding crate.
+    */
+    builtRustCratesWithFeatures =
+      { packageId
+      , features
+      , crateConfigs ? crates
+      , buildRustCrateForPkgsFunc
+      , runTests
+      , makeTarget ? makeDefaultTarget
+      } @ args:
+        assert (builtins.isAttrs crateConfigs);
+        assert (builtins.isString packageId);
+        assert (builtins.isList features);
+        assert (builtins.isAttrs (makeTarget stdenv.hostPlatform));
+        assert (builtins.isBool runTests);
+        let
+          rootPackageId = packageId;
+          mergedFeatures = mergePackageFeatures
+            (
+              args // {
+                inherit rootPackageId;
+                target = makeTarget stdenv.hostPlatform // { test = runTests; };
+              }
+            );
+          # Memoize built packages so that reappearing packages are only built once.
+          builtByPackageIdByPkgs = mkBuiltByPackageIdByPkgs pkgs;
+          mkBuiltByPackageIdByPkgs = pkgs:
+            let
+              self = {
+                crates = lib.mapAttrs (packageId: value: buildByPackageIdForPkgsImpl self pkgs packageId) crateConfigs;
+                target = makeTarget pkgs.stdenv.hostPlatform;
+                build = mkBuiltByPackageIdByPkgs pkgs.buildPackages;
+              };
+            in
+            self;
+          buildByPackageIdForPkgsImpl = self: pkgs: packageId:
+            let
+              features = mergedFeatures."${packageId}" or [ ];
+              crateConfig' = crateConfigs."${packageId}";
+              crateConfig =
+                builtins.removeAttrs crateConfig' [ "resolvedDefaultFeatures" "devDependencies" ];
+              devDependencies =
+                lib.optionals
+                  (runTests && packageId == rootPackageId)
+                  (crateConfig'.devDependencies or [ ]);
+              dependencies =
+                dependencyDerivations {
+                  inherit features;
+                  inherit (self) target;
+                  buildByPackageId = depPackageId:
+                    # proc_macro crates must be compiled for the build architecture
+                    if crateConfigs.${depPackageId}.procMacro or false
+                    then self.build.crates.${depPackageId}
+                    else self.crates.${depPackageId};
+                  dependencies =
+                    (crateConfig.dependencies or [ ])
+                    ++ devDependencies;
+                };
+              buildDependencies =
+                dependencyDerivations {
+                  inherit features;
+                  inherit (self.build) target;
+                  buildByPackageId = depPackageId:
+                    self.build.crates.${depPackageId};
+                  dependencies = crateConfig.buildDependencies or [ ];
+                };
+              dependenciesWithRenames =
+                let
+                  buildDeps = filterEnabledDependencies {
+                    inherit features;
+                    inherit (self) target;
+                    dependencies = crateConfig.dependencies or [ ] ++ devDependencies;
+                  };
+                  hostDeps = filterEnabledDependencies {
+                    inherit features;
+                    inherit (self.build) target;
+                    dependencies = crateConfig.buildDependencies or [ ];
+                  };
+                in
+                lib.filter (d: d ? "rename") (hostDeps ++ buildDeps);
+              # Crate renames have the form:
+              #
+              # {
+              #    crate_name = [
+              #       { version = "1.2.3"; rename = "crate_name01"; }
+              #    ];
+              #    # ...
+              # }
+              crateRenames =
+                let
+                  grouped =
+                    lib.groupBy
+                      (dependency: dependency.name)
+                      dependenciesWithRenames;
+                  versionAndRename = dep:
+                    let
+                      package = crateConfigs."${dep.packageId}";
+                    in
+                    { inherit (dep) rename; inherit (package) version; };
+                in
+                lib.mapAttrs (name: builtins.map versionAndRename) grouped;
+            in
+            buildRustCrateForPkgsFunc pkgs
+              (
+                crateConfig // {
+                  # https://github.com/NixOS/nixpkgs/issues/218712
+                  dontStrip = stdenv.hostPlatform.isDarwin;
+                  src = crateConfig.src or (
+                    pkgs.fetchurl rec {
+                      name = "${crateConfig.crateName}-${crateConfig.version}.tar.gz";
+                      # https://www.pietroalbini.org/blog/downloading-crates-io/
+                      # Not rate-limited, CDN URL.
+                      url = "https://static.crates.io/crates/${crateConfig.crateName}/${crateConfig.crateName}-${crateConfig.version}.crate";
+                      sha256 =
+                        assert (lib.assertMsg (crateConfig ? sha256) "Missing sha256 for ${name}");
+                        crateConfig.sha256;
+                    }
+                  );
+                  extraRustcOpts = lib.lists.optional (targetFeatures != [ ]) "-C target-feature=${lib.concatMapStringsSep "," (x: "+${x}") targetFeatures}";
+                  inherit features dependencies buildDependencies crateRenames release;
+                }
+              );
+        in
+        builtByPackageIdByPkgs;
+
+    /* Returns the actual derivations for the given dependencies. */
+    dependencyDerivations =
+      { buildByPackageId
+      , features
+      , dependencies
+      , target
+      }:
+        assert (builtins.isList features);
+        assert (builtins.isList dependencies);
+        assert (builtins.isAttrs target);
+        let
+          enabledDependencies = filterEnabledDependencies {
+            inherit dependencies features target;
+          };
+          depDerivation = dependency: buildByPackageId dependency.packageId;
+        in
+        map depDerivation enabledDependencies;
+
+    /* Returns a sanitized version of val with all values substituted that cannot
+      be serialized as JSON.
+    */
+    sanitizeForJson = val:
+      if builtins.isAttrs val
+      then lib.mapAttrs (n: sanitizeForJson) val
+      else if builtins.isList val
+      then builtins.map sanitizeForJson val
+      else if builtins.isFunction val
+      then "function"
+      else val;
+
+    /* Returns various tools to debug a crate. */
+    debugCrate = { packageId, target ? makeDefaultTarget stdenv.hostPlatform }:
+      assert (builtins.isString packageId);
+      let
+        debug = rec {
+          # The built tree as passed to buildRustCrate.
+          buildTree = buildRustCrateWithFeatures {
+            buildRustCrateForPkgsFunc = _: lib.id;
+            inherit packageId;
+          };
+          sanitizedBuildTree = sanitizeForJson buildTree;
+          dependencyTree = sanitizeForJson
+            (
+              buildRustCrateWithFeatures {
+                buildRustCrateForPkgsFunc = _: crate: {
+                  "01_crateName" = crate.crateName or false;
+                  "02_features" = crate.features or [ ];
+                  "03_dependencies" = crate.dependencies or [ ];
+                };
+                inherit packageId;
+              }
+            );
+          mergedPackageFeatures = mergePackageFeatures {
+            features = rootFeatures;
+            inherit packageId target;
+          };
+          diffedDefaultPackageFeatures = diffDefaultPackageFeatures {
+            inherit packageId target;
+          };
+        };
+      in
+      { internal = debug; };
+
+    /* Returns differences between cargo default features and crate2nix default
+      features.
+
+      This is useful for verifying the feature resolution in crate2nix.
+    */
+    diffDefaultPackageFeatures =
+      { crateConfigs ? crates
+      , packageId
+      , target
+      }:
+        assert (builtins.isAttrs crateConfigs);
+        let
+          prefixValues = prefix: lib.mapAttrs (n: v: { "${prefix}" = v; });
+          mergedFeatures =
+            prefixValues
+              "crate2nix"
+              (mergePackageFeatures { inherit crateConfigs packageId target; features = [ "default" ]; });
+          configs = prefixValues "cargo" crateConfigs;
+          combined = lib.foldAttrs (a: b: a // b) { } [ mergedFeatures configs ];
+          onlyInCargo =
+            builtins.attrNames
+              (lib.filterAttrs (n: v: !(v ? "crate2nix") && (v ? "cargo")) combined);
+          onlyInCrate2Nix =
+            builtins.attrNames
+              (lib.filterAttrs (n: v: (v ? "crate2nix") && !(v ? "cargo")) combined);
+          differentFeatures = lib.filterAttrs
+            (
+              n: v:
+                (v ? "crate2nix")
+                && (v ? "cargo")
+                && (v.crate2nix.features or [ ]) != (v."cargo".resolved_default_features or [ ])
+            )
+            combined;
+        in
+        builtins.toJSON {
+          inherit onlyInCargo onlyInCrate2Nix differentFeatures;
+        };
+
+    /* Returns an attrset mapping packageId to the list of enabled features.
+
+      If multiple paths to a dependency enable different features, the
+      corresponding feature sets are merged. Features in rust are additive.
+    */
+    mergePackageFeatures =
+      { crateConfigs ? crates
+      , packageId
+      , rootPackageId ? packageId
+      , features ? rootFeatures
+      , dependencyPath ? [ crates.${packageId}.crateName ]
+      , featuresByPackageId ? { }
+      , target
+        # Adds devDependencies to the crate with rootPackageId.
+      , runTests ? false
+      , ...
+      } @ args:
+        assert (builtins.isAttrs crateConfigs);
+        assert (builtins.isString packageId);
+        assert (builtins.isString rootPackageId);
+        assert (builtins.isList features);
+        assert (builtins.isList dependencyPath);
+        assert (builtins.isAttrs featuresByPackageId);
+        assert (builtins.isAttrs target);
+        assert (builtins.isBool runTests);
+        let
+          crateConfig = crateConfigs."${packageId}" or (builtins.throw "Package not found: ${packageId}");
+          expandedFeatures = expandFeatures (crateConfig.features or { }) features;
+          enabledFeatures = enableFeatures (crateConfig.dependencies or [ ]) expandedFeatures;
+          depWithResolvedFeatures = dependency:
+            let
+              inherit (dependency) packageId;
+              features = dependencyFeatures enabledFeatures dependency;
+            in
+            { inherit packageId features; };
+          resolveDependencies = cache: path: dependencies:
+            assert (builtins.isAttrs cache);
+            assert (builtins.isList dependencies);
+            let
+              enabledDependencies = filterEnabledDependencies {
+                inherit dependencies target;
+                features = enabledFeatures;
+              };
+              directDependencies = map depWithResolvedFeatures enabledDependencies;
+              foldOverCache = op: lib.foldl op cache directDependencies;
+            in
+            foldOverCache
+              (
+                cache: { packageId, features }:
+                  let
+                    cacheFeatures = cache.${packageId} or [ ];
+                    combinedFeatures = sortedUnique (cacheFeatures ++ features);
+                  in
+                  if cache ? ${packageId} && cache.${packageId} == combinedFeatures
+                  then cache
+                  else
+                    mergePackageFeatures {
+                      features = combinedFeatures;
+                      featuresByPackageId = cache;
+                      inherit crateConfigs packageId target runTests rootPackageId;
+                    }
+              );
+          cacheWithSelf =
+            let
+              cacheFeatures = featuresByPackageId.${packageId} or [ ];
+              combinedFeatures = sortedUnique (cacheFeatures ++ enabledFeatures);
+            in
+            featuresByPackageId // {
+              "${packageId}" = combinedFeatures;
+            };
+          cacheWithDependencies =
+            resolveDependencies cacheWithSelf "dep"
+              (
+                crateConfig.dependencies or [ ]
+                ++ lib.optionals
+                  (runTests && packageId == rootPackageId)
+                  (crateConfig.devDependencies or [ ])
+              );
+          cacheWithAll =
+            resolveDependencies
+              cacheWithDependencies "build"
+              (crateConfig.buildDependencies or [ ]);
+        in
+        cacheWithAll;
+
+    /* Returns the enabled dependencies given the enabled features. */
+    filterEnabledDependencies = { dependencies, features, target }:
+      assert (builtins.isList dependencies);
+      assert (builtins.isList features);
+      assert (builtins.isAttrs target);
+
+      lib.filter
+        (
+          dep:
+          let
+            targetFunc = dep.target or (features: true);
+          in
+          targetFunc { inherit features target; }
+          && (
+            !(dep.optional or false)
+            || builtins.any (doesFeatureEnableDependency dep) features
+          )
+        )
+        dependencies;
+
+    /* Returns whether the given feature should enable the given dependency. */
+    doesFeatureEnableDependency = dependency: feature:
+      let
+        name = dependency.rename or dependency.name;
+        prefix = "${name}/";
+        len = builtins.stringLength prefix;
+        startsWithPrefix = builtins.substring 0 len feature == prefix;
+      in
+      feature == name || feature == "dep:" + name || startsWithPrefix;
+
+    /* Returns the expanded features for the given inputFeatures by applying the
+      rules in featureMap.
+
+      featureMap is an attribute set which maps feature names to lists of further
+      feature names to enable in case this feature is selected.
+    */
+    expandFeatures = featureMap: inputFeatures:
+      assert (builtins.isAttrs featureMap);
+      assert (builtins.isList inputFeatures);
+      let
+        expandFeaturesNoCycle = oldSeen: inputFeatures:
+          if inputFeatures != [ ]
+          then
+            let
+              # The feature we're currently expanding.
+              feature = builtins.head inputFeatures;
+              # All the features we've seen/expanded so far, including the one
+              # we're currently processing.
+              seen = oldSeen // { ${feature} = 1; };
+              # Expand the feature but be careful to not re-introduce a feature
+              # that we've already seen: this can easily cause a cycle, see issue
+              # #209.
+              enables = builtins.filter (f: !(seen ? "${f}")) (featureMap."${feature}" or [ ]);
+            in
+            [ feature ] ++ (expandFeaturesNoCycle seen (builtins.tail inputFeatures ++ enables))
+          # No more features left, nothing to expand to.
+          else [ ];
+        outFeatures = expandFeaturesNoCycle { } inputFeatures;
+      in
+      sortedUnique outFeatures;
+
+    /* This function adds optional dependencies as features if they are enabled
+      indirectly by dependency features. This function mimics Cargo's behavior
+      described in a note at:
+      https://doc.rust-lang.org/nightly/cargo/reference/features.html#dependency-features
+    */
+    enableFeatures = dependencies: features:
+      assert (builtins.isList features);
+      assert (builtins.isList dependencies);
+      let
+        additionalFeatures = lib.concatMap
+          (
+            dependency:
+              assert (builtins.isAttrs dependency);
+              let
+                enabled = builtins.any (doesFeatureEnableDependency dependency) features;
+              in
+              if (dependency.optional or false) && enabled
+              then [ (dependency.rename or dependency.name) ]
+              else [ ]
+          )
+          dependencies;
+      in
+      sortedUnique (features ++ additionalFeatures);
+
+    /*
+      Returns the actual features for the given dependency.
+
+      features: The features of the crate that refers this dependency.
+    */
+    dependencyFeatures = features: dependency:
+      assert (builtins.isList features);
+      assert (builtins.isAttrs dependency);
+      let
+        defaultOrNil =
+          if dependency.usesDefaultFeatures or true
+          then [ "default" ]
+          else [ ];
+        explicitFeatures = dependency.features or [ ];
+        additionalDependencyFeatures =
+          let
+            name = dependency.rename or dependency.name;
+            stripPrefixMatch = prefix: s:
+              if lib.hasPrefix prefix s
+              then lib.removePrefix prefix s
+              else null;
+            extractFeature = feature: lib.findFirst
+              (f: f != null)
+              null
+              (map (prefix: stripPrefixMatch prefix feature) [
+                (name + "/")
+                (name + "?/")
+              ]);
+            dependencyFeatures = lib.filter (f: f != null) (map extractFeature features);
+          in
+          dependencyFeatures;
+      in
+      defaultOrNil ++ explicitFeatures ++ additionalDependencyFeatures;
+
+    /* Sorts and removes duplicates from a list of strings. */
+    sortedUnique = features:
+      assert (builtins.isList features);
+      assert (builtins.all builtins.isString features);
+      let
+        outFeaturesSet = lib.foldl (set: feature: set // { "${feature}" = 1; }) { } features;
+        outFeaturesUnique = builtins.attrNames outFeaturesSet;
+      in
+      builtins.sort (a: b: a < b) outFeaturesUnique;
+
+    deprecationWarning = message: value:
+      if strictDeprecation
+      then builtins.throw "strictDeprecation enabled, aborting: ${message}"
+      else builtins.trace message value;
+
+    #
+    # crate2nix/default.nix (excerpt end)
+    #
+  };
+}
+
diff --git a/tvix/Cargo.toml b/tvix/Cargo.toml
new file mode 100644
index 0000000000..6cd19831dc
--- /dev/null
+++ b/tvix/Cargo.toml
@@ -0,0 +1,38 @@
+# This Cargo file is a workspace configuration as per
+# https://doc.rust-lang.org/book/ch14-03-cargo-workspaces.html
+#
+# We add this file to get a coherent set of dependencies across Tvix
+# crates by sharing a Cargo.lock. This is necessary because of the
+# currently limited support for Rust builds in Nix.
+#
+# Note that this explicitly does *not* mean that //tvix should be
+# considered "one project": This is simply a workaround for a
+# technical limitation and it should be our aim to remove this
+# workspace file and make the subprojects independent.
+#
+# Note also that CI targets for actual projects should *not* be tied
+# to //tvix, but to its subprojects. A potential file at
+# //tvix/default.nix should likely *not* expose anything other than
+# extra steps or other auxiliary targets.
+
+[workspace]
+resolver = "2"
+
+members = [
+  "build",
+  "castore",
+  "cli",
+  "eval",
+  "eval/builtin-macros",
+  "glue",
+  "nix-compat",
+  "serde",
+  "store",
+]
+
+# Add a profile to all targets that enables release optimisations, but
+# retains debug symbols. This is great for use with
+# benchmarking/profiling tools.
+[profile.release-with-debug]
+inherits = "release"
+debug = true
diff --git a/tvix/OWNERS b/tvix/OWNERS
index 3005e4f6d3..a0b1dd8d77 100644
--- a/tvix/OWNERS
+++ b/tvix/OWNERS
@@ -1,5 +1,7 @@
-inherited: false
-owners:
-  - adisbladis
-  - flokli
-  - tazjin
+set noparent
+
+adisbladis
+flokli
+aspen
+sterni
+tazjin
diff --git a/tvix/README.md b/tvix/README.md
index 9569cedf33..bf96afa4ba 100644
--- a/tvix/README.md
+++ b/tvix/README.md
@@ -1,10 +1,109 @@
-Tvix
-====
+<div align="center">
+  <img src="https://tvix.dev/logo.webp">
+</div>
 
-For more information about Tvix, contact one of the project owners. We
-are interested in people who would like to help us review designs,
-brainstorm and describe requirements that we may not yet have
-considered.
+-----------------
+
+Tvix is a new implementation of the Nix language and package manager. See the
+[announcement post][post-1] for information about the background of this
+project.
+
+Tvix is developed by [TVL][tvl] in our monorepo, the `depot`, at
+[//tvix][tvix-src]. Code reviews take place on [Gerrit][tvix-gerrit], bugs are
+filed in [our issue tracker][b].
+
+For more information about Tvix, feel free to reach out. We are interested in
+people who would like to help us review designs, brainstorm and describe
+requirements that we may not yet have considered.
+
+Most of the discussion around development happens in our dedicated IRC channel,
+[`#tvix-dev`][tvix-dev-irc] on [hackint][],
+which is also reachable [via XMPP][hackint-xmpp]
+at [`#tvix-dev@irc.hackint.org`][tvix-dev-xmpp] (sic!)
+and [via Matrix][hackint-matrix] at [`#tvix-dev:hackint.org`][tvix-dev-matrix].
+
+There's also the IRC channel of the [wider TVL community][tvl-getting-in-touch],
+less on-topic, or our [mailing list][].
+
+Contributions to Tvix follow the TVL [review flow][review-docs] and
+[contribution guidelines][contributing].
+
+[post-1]: https://tvl.fyi/blog/rewriting-nix
+[tvl]: https://tvl.fyi
+[tvix-src]: https://cs.tvl.fyi/depot/-/tree/tvix/
+[tvix-gerrit]: https://cl.tvl.fyi/q/path:%255Etvix.*
+[b]: https://b.tvl.fyi
+[tvl-getting-in-touch]: https://tvl.fyi/#getting-in-touch
+[mailing list]: https://inbox.tvl.su
+[review-docs]: https://code.tvl.fyi/about/docs/REVIEWS.md
+[contributing]: https://code.tvl.fyi/about/docs/CONTRIBUTING.md
+[tvix-dev-irc]: ircs://irc.hackint.org:6697/#tvix-dev
+[hackint]: https://hackint.org/
+[hackint-xmpp]: https://hackint.org/transport/xmpp
+[tvix-dev-xmpp]: xmpp:#tvix-dev@irc.hackint.org?join
+[hackint-matrix]: https://hackint.org/transport/matrix
+[tvix-dev-matrix]: https://matrix.to/#/#tvix-dev:hackint.org
+[tvix-dev-webchat]: https://webirc.hackint.org/#ircs://irc.hackint.org/#tvix-dev
+
+WARNING: Tvix is not ready for use in production. None of our current APIs
+should be considered stable in any way.
+
+WARNING: Any other instances of this project or repository are
+[`josh`-mirrors][josh]. We do not accept code contributions or issues outside of
+the tooling and communication methods outlined above.
+
+[josh]: https://github.com/josh-project/josh
+
+## Components
+
+This folder contains the following components:
+
+* `//tvix/castore` - subtree storage/transfer in a content-addressed fashion
+* `//tvix/cli` - preliminary REPL & CLI implementation for Tvix
+* `//tvix/eval` - an implementation of the Nix programming language
+* `//tvix/nar-bridge`
+  * `nar-bridge-http`: A HTTP webserver providing a Nix HTTP Binary Cache interface in front of a tvix-store
+* `//tvix/nix-compat` - a Rust library for compatibility with C++ Nix, features like encodings and hashing schemes and formats
+* `//tvix/serde` - a Rust library for using the Nix language for app configuration
+* `//tvix/store` - a "filesystem" linking Nix store paths and metadata with the content-addressed layer
+
+Some additional folders with auxiliary things exist and can be explored at your
+leisure.
+
+## Building the CLI
+
+The CLI can also be built with standard Rust tooling (i.e. `cargo build`),
+as long as you are in a shell with the right dependencies.
+
+ - If you cloned the full monorepo, it can be provided by
+   `mg shell //tvix:shell`.
+ - If you cloned the `tvix` workspace only
+   (`git clone https://code.tvl.fyi/depot.git:workspace=views/tvix.git`),
+   `nix-shell` provides it.
+
+If you're in the TVL monorepo, you can also run `mg build //tvix/cli`
+(or `mg build` from inside that folder) for a more incremental build.
+
+Please follow the depot-wide instructions on how to get `mg` and use the depot
+tooling.
+
+### Compatibility
+**Important note:** We only use and test Nix builds of our software
+against Nix 2.3. There are a variety of bugs and subtle problems in
+newer Nix versions which we do not have the bandwidth to address,
+builds in newer Nix versions may or may not work.
+
+## Rust projects, crate2nix
+
+Some parts of Tvix are written in Rust. To simplify the dependency
+management on the Nix side of these builds, we use `crate2nix` in a
+single Rust workspace in `//tvix` to maintain the Nix build
+configuration.
+
+When making changes to Cargo dependency configuration in any of the
+Rust projects under `//tvix`, be sure to run
+`mg run //tools:crate2nix-generate` in `//tvix` itself and commit the changes
+to the generated `Cargo.nix` file. This only applies to the full TVL checkout.
 
 ## License structure
 
diff --git a/tvix/boot/README.md b/tvix/boot/README.md
new file mode 100644
index 0000000000..13a4855060
--- /dev/null
+++ b/tvix/boot/README.md
@@ -0,0 +1,136 @@
+# tvix/boot
+
+This directory provides tooling to boot VMs with /nix/store provided by
+virtiofs.
+
+In the `tests/` subdirectory, there's some integration tests.
+
+## //tvix/boot:runVM
+A script spinning up a `tvix-store virtiofs` daemon, then starting a cloud-
+hypervisor VM.
+
+The cloud-hypervisor VM is using a (semi-)minimal kernel image with virtiofs
+support, and a custom initrd (using u-root). It supports various command line
+options, to be able to do VM tests, act as an interactive shell or exec a binary
+from a closure.
+
+It supports the following env vars:
+ - `CH_NUM_CPUS=1` controls the number of CPUs available to the VM
+ - `CH_MEM_SIZE=512M` controls the memory availabe to the VM
+ - `CH_CMDLINE=` controls the kernel cmdline (which can be used to control the
+   boot)
+
+### Usage
+First, ensure you have `tvix-store` in `$PATH`, as that's what `run-tvix-vm`
+expects:
+
+Assuming you ran `cargo build --profile=release-with-debug` before, and are in
+the `tvix` directory:
+
+```
+export PATH=$PATH:$PWD/target/release-with-debug
+```
+
+Secondly, configure tvix to use the local backend:
+
+```
+export BLOB_SERVICE_ADDR=sled://$PWD/blobs.sled
+export DIRECTORY_SERVICE_ADDR=sled://$PWD/directories.sled
+export PATH_INFO_SERVICE_ADDR=sled://$PWD/pathinfo.sled
+```
+
+Potentially copy some data into tvix-store (via nar-bridge):
+
+```
+mg run //tvix:store -- daemon &
+$(mg build //tvix:nar-bridge)/bin/nar-bridge-http &
+rm -Rf ~/.cache/nix; nix copy --to http://localhost:9000\?compression\=none $(mg build //third_party/nixpkgs:hello)
+pkill nar-bridge-http; pkill tvix-store
+```
+
+#### Interactive shell
+Run the VM like this:
+
+```
+CH_CMDLINE=tvix.shell mg run //tvix/boot:runVM --
+```
+
+You'll get dropped into an interactive shell, from which you can do things with
+the store:
+
+```
+  ______      _         ____      _ __
+ /_  __/   __(_)  __   /  _/___  (_) /_
+  / / | | / / / |/_/   / // __ \/ / __/
+ / /  | |/ / />  <   _/ // / / / / /_
+/_/   |___/_/_/|_|  /___/_/ /_/_/\__/
+
+/# ls -la /nix/store/
+dr-xr-xr-x root 0 0   Jan  1 00:00 .
+dr-xr-xr-x root 0 989 Jan  1 00:00 aw2fw9ag10wr9pf0qk4nk5sxi0q0bn56-glibc-2.37-8
+dr-xr-xr-x root 0 3   Jan  1 00:00 jbwb8d8l28lg9z0xzl784wyb9vlbwss6-xgcc-12.3.0-libgcc
+dr-xr-xr-x root 0 82  Jan  1 00:00 k8ivghpggjrq1n49xp8sj116i4sh8lia-libidn2-2.3.4
+dr-xr-xr-x root 0 141 Jan  1 00:00 mdi7lvrn2mx7rfzv3fdq3v5yw8swiks6-hello-2.12.1
+dr-xr-xr-x root 0 5   Jan  1 00:00 s2gi8pfjszy6rq3ydx0z1vwbbskw994i-libunistring-1.1
+```
+
+Once you exit the shell, the VM will power off itself.
+
+#### Execute a specific binary
+Run the VM like this:
+
+```
+hello_cmd=$(mg build //third_party/nixpkgs:hello)/bin/hello
+CH_CMDLINE=tvix.run=$hello_cmd mg run //tvix/boot:runVM --
+```
+
+Observe it executing the file (and closure) from the tvix-store:
+
+```
+[    0.277486] Run /init as init process
+  ______      _         ____      _ __
+ /_  __/   __(_)  __   /  _/___  (_) /_
+  / / | | / / / |/_/   / // __ \/ / __/
+ / /  | |/ / />  <   _/ // / / / / /_
+/_/   |___/_/_/|_|  /___/_/ /_/_/\__/
+
+Hello, world!
+2023/09/24 21:10:19 Nothing left to be done, powering off.
+[    0.299122] ACPI: PM: Preparing to enter system sleep state S5
+[    0.299422] reboot: Power down
+```
+
+#### Execute a NixOS system closure
+It's also possible to invoke a system closure. To do this, tvix-init honors the
+init= cmdline option, and will switch_root to it.
+
+
+```
+CH_CMDLINE=init=/nix/store/…-nixos-system-…/init mg run //tvix/boot:runVM --
+```
+
+```
+  ______      _         ____      _ __
+ /_  __/   __(_)  __   /  _/___  (_) /_
+  / / | | / / / |/_/   / // __ \/ / __/
+ / /  | |/ / />  <   _/ // / / / / /_
+/_/   |___/_/_/|_|  /___/_/ /_/_/\__/
+
+2023/09/24 21:16:43 switch_root: moving mounts
+2023/09/24 21:16:43 switch_root: Skipping "/run" as the dir does not exist
+2023/09/24 21:16:43 switch_root: Changing directory
+2023/09/24 21:16:43 switch_root: Moving /
+2023/09/24 21:16:43 switch_root: Changing root!
+2023/09/24 21:16:43 switch_root: Deleting old /
+2023/09/24 21:16:43 switch_root: executing init
+
+<<< NixOS Stage 2 >>>
+
+[    0.322096] booting system configuration /nix/store/g657sdxinpqfcdv0162zmb8vv9b5c4c5-nixos-system-client-23.11.git.82102fc37da
+running activation script...
+setting up /etc...
+starting systemd...
+[    0.980740] systemd[1]: systemd 253.6 running in system mode (+PAM +AUDIT -SELINUX +APPARMOR +IMA +SMACK +SECCOMP +GCRYPT -GNUTLS +OPENSSL +ACL +BLKID +CURL +ELFUTILS +FIDO2 +IDN2 -IDN +IPTC +KMOD +LIBCRYPTSETUP +LIBFDISK +PCRE2 -PWQUALITY +P11KIT -QRENCODE +TPM2 +BZIP2 +LZ4 +XZ +ZLIB +ZSTD +BPF_FRAMEWORK -XKBCOMMON +UTMP -SYSVINIT default-hierarchy=unified)
+```
+
+This effectively replaces the NixOS Stage 1 entirely.
diff --git a/tvix/boot/default.nix b/tvix/boot/default.nix
new file mode 100644
index 0000000000..85995ffbf2
--- /dev/null
+++ b/tvix/boot/default.nix
@@ -0,0 +1,113 @@
+{ depot, pkgs, ... }:
+
+rec {
+  # A binary that sets up /nix/store from virtiofs, lists all store paths, and
+  # powers off the machine.
+  tvix-init = depot.nix.buildGo.program {
+    name = "tvix-init";
+    srcs = [
+      ./tvix-init.go
+    ];
+  };
+
+  # A kernel with virtiofs support baked in
+  # TODO: make a smaller kernel, we don't need a gazillion filesystems and
+  # device drivers in it.
+  kernel = pkgs.buildLinux ({ } // {
+    inherit (pkgs.linuxPackages_latest.kernel) src version modDirVersion;
+    autoModules = false;
+    kernelPreferBuiltin = true;
+    ignoreConfigErrors = true;
+    kernelPatches = [ ];
+    structuredExtraConfig = with pkgs.lib.kernel; {
+      FUSE_FS = option yes;
+      DAX_DRIVER = option yes;
+      DAX = option yes;
+      FS_DAX = option yes;
+      VIRTIO_FS = option yes;
+      VIRTIO = option yes;
+      ZONE_DEVICE = option yes;
+    };
+  });
+
+  # A build framework for minimal initrds
+  uroot = pkgs.buildGoModule rec {
+    pname = "u-root";
+    version = "0.14.0";
+    src = pkgs.fetchFromGitHub {
+      owner = "u-root";
+      repo = "u-root";
+      rev = "v${version}";
+      hash = "sha256-8zA3pHf45MdUcq/MA/mf0KCTxB1viHieU/oigYwIPgo=";
+    };
+    vendorHash = null;
+
+    doCheck = false; # Some tests invoke /bin/bash
+  };
+
+  # Use u-root to build a initrd with our tvix-init inside.
+  initrd = pkgs.stdenv.mkDerivation {
+    name = "initrd.cpio";
+    nativeBuildInputs = [ pkgs.go ];
+    # https://github.com/u-root/u-root/issues/2466
+    buildCommand = ''
+      mkdir -p /tmp/go/src/github.com/u-root/
+      cp -R ${uroot.src} /tmp/go/src/github.com/u-root/u-root
+      cd /tmp/go/src/github.com/u-root/u-root
+      chmod +w .
+      cp ${tvix-init}/bin/tvix-init tvix-init
+
+      export HOME=$(mktemp -d)
+      export GOROOT="$(go env GOROOT)"
+
+      GO111MODULE=off GOPATH=/tmp/go GOPROXY=off ${uroot}/bin/u-root -files ./tvix-init -initcmd "/tvix-init" -o $out
+    '';
+  };
+
+  # Start a `tvix-store` virtiofs daemon from $PATH, then a cloud-hypervisor
+  # pointed to it.
+  # Supports the following env vars (and defaults)
+  # CH_NUM_CPUS=2
+  # CH_MEM_SIZE=512M
+  # CH_CMDLINE=""
+  runVM = pkgs.writers.writeBashBin "run-tvix-vm" ''
+    tempdir=$(mktemp -d)
+
+    cleanup() {
+      kill $virtiofsd_pid
+      if [[ -n ''${work_dir-} ]]; then
+        chmod -R u+rw "$tempdir"
+        rm -rf "$tempdir"
+      fi
+    }
+    trap cleanup EXIT
+
+    # Spin up the virtiofs daemon
+    tvix-store --otlp=false virtiofs -l $tempdir/tvix.sock &
+    virtiofsd_pid=$!
+
+    # Wait for the socket to exist.
+    until [ -e $tempdir/tvix.sock ]; do sleep 0.1; done
+
+    CH_NUM_CPUS="''${CH_NUM_CPUS:-2}"
+    CH_MEM_SIZE="''${CH_MEM_SIZE:-512M}"
+    CH_CMDLINE="''${CH_CMDLINE:-}"
+
+    # spin up cloud_hypervisor
+    ${pkgs.cloud-hypervisor}/bin/cloud-hypervisor \
+     --cpus boot=$CH_NUM_CPU \
+     --memory mergeable=on,shared=on,size=$CH_MEM_SIZE \
+     --console null \
+     --serial tty \
+     --kernel ${kernel.dev}/vmlinux \
+     --initramfs ${initrd} \
+     --cmdline "console=ttyS0 $CH_CMDLINE" \
+     --fs tag=tvix,socket=$tempdir/tvix.sock,num_queues=''${CH_NUM_CPU},queue_size=512
+  '';
+
+  meta.ci.targets = [
+    "initrd"
+    "kernel"
+    "runVM"
+  ];
+}
diff --git a/tvix/boot/tests/default.nix b/tvix/boot/tests/default.nix
new file mode 100644
index 0000000000..d16dba79f1
--- /dev/null
+++ b/tvix/boot/tests/default.nix
@@ -0,0 +1,138 @@
+{ depot, pkgs, lib, ... }:
+
+let
+  # Seed a tvix-store with the tvix docs, then start a VM, ask it to list all
+  # files in /nix/store, and ensure the store path is present, which acts as a
+  # nice smoketest.
+  mkBootTest =
+    { blobServiceAddr ? "memory://"
+    , directoryServiceAddr ? "memory://"
+    , pathInfoServiceAddr ? "memory://"
+
+
+      # The path to import.
+    , path
+
+      # Whether the path should be imported as a closure.
+      # If false, importPathName must be specified.
+    , isClosure ? false
+    , importPathName ? null
+
+      # The cmdline to pass to the VM.
+      # Defaults to tvix.find, which lists all files in the store.
+    , vmCmdline ? "tvix.find"
+      # The string we expect to find in the VM output.
+      # Defaults the value of `path` (the store path we upload).
+    , assertVMOutput ? path
+    }:
+
+      assert isClosure -> importPathName == null;
+      assert (!isClosure) -> importPathName != null;
+
+      pkgs.stdenv.mkDerivation {
+        name = "run-vm";
+
+        __structuredAttrs = true;
+        exportReferencesGraph.closure = [ path ];
+
+        nativeBuildInputs = [
+          depot.tvix.store
+          depot.tvix.boot.runVM
+        ];
+        buildCommand = ''
+          touch $out
+
+          # Start the tvix daemon, listening on a unix socket.
+          BLOB_SERVICE_ADDR=${blobServiceAddr} \
+            DIRECTORY_SERVICE_ADDR=${directoryServiceAddr} \
+            PATH_INFO_SERVICE_ADDR=${pathInfoServiceAddr} \
+            tvix-store \
+              --otlp=false \
+              daemon -l $PWD/tvix-store.sock &
+
+          # Wait for the socket to be created.
+          while [ ! -e $PWD/tvix-store.sock ]; do sleep 1; done
+
+          # Export env vars so that subsequent tvix-store commands will talk to
+          # our tvix-store daemon over the unix socket.
+          export BLOB_SERVICE_ADDR=grpc+unix://$PWD/tvix-store.sock
+          export DIRECTORY_SERVICE_ADDR=grpc+unix://$PWD/tvix-store.sock
+          export PATH_INFO_SERVICE_ADDR=grpc+unix://$PWD/tvix-store.sock
+        '' + lib.optionalString (!isClosure) ''
+          echo "Importing ${path} into tvix-store with name ${importPathName}…"
+          cp -R ${path} ${importPathName}
+          outpath=$(tvix-store import ${importPathName})
+
+          echo "imported to $outpath"
+        '' + lib.optionalString (isClosure) ''
+          echo "Copying closure ${path}…"
+          # This picks up the `closure` key in `$NIX_ATTRS_JSON_FILE` automatically.
+          tvix-store --otlp=false copy
+        '' + ''
+          # Invoke a VM using tvix as the backing store, ensure the outpath appears in its listing.
+          echo "Starting VM…"
+
+          CH_CMDLINE="${vmCmdline}" run-tvix-vm 2>&1 | tee output.txt
+          grep "${assertVMOutput}" output.txt
+        '';
+        requiredSystemFeatures = [ "kvm" ];
+      };
+
+  systemFor = sys: (depot.ops.nixos.nixosFor sys).system;
+
+  testSystem = systemFor ({ modulesPath, pkgs, ... }: {
+    # Set some options necessary to evaluate.
+    boot.loader.systemd-boot.enable = true;
+    # TODO: figure out how to disable this without causing eval to fail
+    fileSystems."/" = {
+      device = "/dev/root";
+      fsType = "tmpfs";
+    };
+
+    services.getty.helpLine = "Onwards and upwards.";
+    systemd.services.do-shutdown = {
+      after = [ "getty.target" ];
+      description = "Shut down again";
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig.Type = "oneshot";
+      script = "/run/current-system/sw/bin/systemctl poweroff --when=+10s";
+    };
+
+    # Don't warn about stateVersion.
+    system.stateVersion = "24.05";
+  });
+
+in
+depot.nix.readTree.drvTargets
+{
+  docs-memory = (mkBootTest {
+    path = ../../docs;
+    importPathName = "docs";
+  });
+  docs-sled = (mkBootTest {
+    blobServiceAddr = "sled://$PWD/blobs.sled";
+    directoryServiceAddr = "sled://$PWD/directories.sled";
+    pathInfoServiceAddr = "sled://$PWD/pathinfo.sled";
+    path = ../../docs;
+    importPathName = "docs";
+  });
+  docs-objectstore-local = (mkBootTest {
+    blobServiceAddr = "objectstore+file://$PWD/blobs";
+    path = ../../docs;
+    importPathName = "docs";
+  });
+
+  closure-tvix = (mkBootTest {
+    blobServiceAddr = "objectstore+file://$PWD/blobs";
+    path = depot.tvix.store;
+    isClosure = true;
+  });
+
+  closure-nixos = (mkBootTest {
+    blobServiceAddr = "objectstore+file://$PWD/blobs";
+    path = testSystem;
+    isClosure = true;
+    vmCmdline = "init=${testSystem}/init panic=-1"; # reboot immediately on panic
+    assertVMOutput = "Onwards and upwards.";
+  });
+}
diff --git a/tvix/boot/tvix-init.go b/tvix/boot/tvix-init.go
new file mode 100644
index 0000000000..97a24bab35
--- /dev/null
+++ b/tvix/boot/tvix-init.go
@@ -0,0 +1,138 @@
+package main
+
+import (
+	"fmt"
+	"log"
+	"os"
+	"os/exec"
+	"strings"
+	"syscall"
+)
+
+// run the given command, connecting std{in,err,out} with the OS one.
+func run(args ...string) error {
+	cmd := exec.Command(args[0], args[1:]...)
+	cmd.Stdin = os.Stdin
+	cmd.Stderr = os.Stderr
+	cmd.Stdout = os.Stdout
+
+	return cmd.Run()
+}
+
+// parse the cmdline, return a map[string]string.
+func parseCmdline(cmdline string) map[string]string {
+	line := strings.TrimSuffix(cmdline, "\n")
+	fields := strings.Fields(line)
+	out := make(map[string]string, 0)
+
+	for _, arg := range fields {
+		kv := strings.SplitN(arg, "=", 2)
+		switch len(kv) {
+		case 1:
+			out[kv[0]] = ""
+		case 2:
+			out[kv[0]] = kv[1]
+		}
+	}
+
+	return out
+}
+
+// mounts the nix store from the virtiofs tag to the given destination,
+// creating the destination if it doesn't exist already.
+func mountTvixStore(dest string) error {
+	if err := os.MkdirAll(dest, os.ModePerm); err != nil {
+		return fmt.Errorf("unable to mkdir dest: %w", err)
+	}
+	if err := run("mount", "-t", "virtiofs", "tvix", dest, "-o", "ro"); err != nil {
+		return fmt.Errorf("unable to run mount: %w", err)
+	}
+
+	return nil
+}
+
+func main() {
+	fmt.Print(`
+  ______      _         ____      _ __
+ /_  __/   __(_)  __   /  _/___  (_) /_
+  / / | | / / / |/_/   / // __ \/ / __/
+ / /  | |/ / />  <   _/ // / / / / /_
+/_/   |___/_/_/|_|  /___/_/ /_/_/\__/
+
+`)
+
+	// Set PATH to "/bbin", so we can find the u-root tools
+	os.Setenv("PATH", "/bbin")
+
+	if err := run("mount", "-t", "proc", "none", "/proc"); err != nil {
+		log.Printf("Failed to mount /proc: %v\n", err)
+	}
+	if err := run("mount", "-t", "sysfs", "none", "/sys"); err != nil {
+		log.Printf("Failed to mount /sys: %v\n", err)
+	}
+	if err := run("mount", "-t", "devtmpfs", "devtmpfs", "/dev"); err != nil {
+		log.Printf("Failed to mount /dev: %v\n", err)
+	}
+
+	cmdline, err := os.ReadFile("/proc/cmdline")
+	if err != nil {
+		log.Printf("Failed to read cmdline: %s\n", err)
+	}
+	cmdlineFields := parseCmdline(string(cmdline))
+
+	if _, ok := cmdlineFields["tvix.find"]; ok {
+		// If tvix.find is set, invoke find /nix/store
+		if err := mountTvixStore("/nix/store"); err != nil {
+			log.Printf("Failed to mount tvix store: %v\n", err)
+		}
+
+		if err := run("find", "/nix/store"); err != nil {
+			log.Printf("Failed to run find command: %s\n", err)
+		}
+	} else if _, ok := cmdlineFields["tvix.shell"]; ok {
+		// If tvix.shell is set, mount the nix store to /nix/store directly,
+		// then invoke the elvish shell
+		if err := mountTvixStore("/nix/store"); err != nil {
+			log.Printf("Failed to mount tvix store: %v\n", err)
+		}
+
+		if err := run("elvish"); err != nil {
+			log.Printf("Failed to run shell: %s\n", err)
+		}
+	} else if v, ok := cmdlineFields["tvix.run"]; ok {
+		// If tvix.run is set, mount the nix store to /nix/store directly,
+		// then invoke the command.
+		if err := mountTvixStore("/nix/store"); err != nil {
+			log.Printf("Failed to mount tvix store: %v\n", err)
+		}
+
+		if err := run(v); err != nil {
+			log.Printf("Failed to run command: %s\n", err)
+		}
+	} else if v, ok := cmdlineFields["init"]; ok {
+		// If init is set, invoke the binary specified (with switch_root),
+		// and prepare /fs beforehand as well.
+		os.Mkdir("/fs", os.ModePerm)
+		if err := run("mount", "-t", "tmpfs", "none", "/fs"); err != nil {
+			log.Fatalf("Failed to mount /fs tmpfs: %s\n", err)
+		}
+
+		// Mount /fs/nix/store
+		if err := mountTvixStore("/fs/nix/store"); err != nil {
+			log.Fatalf("Failed to mount tvix store: %v\n", err)
+		}
+
+		// Invoke switch_root, which will take care of moving /proc, /sys and /dev.
+		if err := syscall.Exec("/bbin/switch_root", []string{"switch_root", "/fs", v}, []string{}); err != nil {
+			log.Printf("Failed to switch root: %s\n", err)
+		}
+	} else {
+		log.Printf("No command detected, not knowing what to do!")
+	}
+
+	// This is only reached in the non switch_root case.
+	log.Printf("Nothing left to be done, powering off.")
+	if err := run("poweroff"); err != nil {
+		log.Printf("Failed to run poweroff command: %v\n", err)
+	}
+}
diff --git a/tvix/proto/LICENSE b/tvix/build-go/LICENSE
index 36878fe4cb..2034ada6fd 100644
--- a/tvix/proto/LICENSE
+++ b/tvix/build-go/LICENSE
@@ -1,4 +1,4 @@
-Copyright © 2021 The Tvix Authors
+Copyright © The Tvix Authors
 
 Permission is hereby granted, free of charge, to any person obtaining
 a copy of this software and associated documentation files (the
diff --git a/tvix/build-go/README.md b/tvix/build-go/README.md
new file mode 100644
index 0000000000..19edfced01
--- /dev/null
+++ b/tvix/build-go/README.md
@@ -0,0 +1,10 @@
+# build-go
+
+This directory contains generated golang bindings, both for the `tvix-build`
+data models, as well as the gRPC bindings.
+
+They are generated with `mg run //tvix/build-go:regenerate`.
+These files end with `.pb.go`, and are ensured to be up to date by a CI check.
+
+Additionally, code useful when interacting with these data structures
+(ending just with `.go`) is provided.
diff --git a/tvix/build-go/build.pb.go b/tvix/build-go/build.pb.go
new file mode 100644
index 0000000000..9c6bd5f248
--- /dev/null
+++ b/tvix/build-go/build.pb.go
@@ -0,0 +1,670 @@
+// SPDX-License-Identifier: MIT
+// Copyright © 2022 The Tvix Authors
+
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// 	protoc-gen-go v1.33.0
+// 	protoc        (unknown)
+// source: tvix/build/protos/build.proto
+
+package buildv1
+
+import (
+	castore_go "code.tvl.fyi/tvix/castore-go"
+	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+	reflect "reflect"
+	sync "sync"
+)
+
+const (
+	// Verify that this generated code is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+	// Verify that runtime/protoimpl is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+// A BuildRequest describes the request of something to be run on the builder.
+// It is distinct from an actual [Build] that has already happened, or might be
+// currently ongoing.
+//
+// A BuildRequest can be seen as a more normalized version of a Derivation
+// (parsed from A-Term), "writing out" some of the Nix-internal details about
+// how e.g. environment variables in the build are set.
+//
+// Nix has some impurities when building a Derivation, for example the --cores option
+// ends up as an environment variable in the build, that's not part of the ATerm.
+//
+// As of now, we serialize this into the BuildRequest, so builders can stay dumb.
+// This might change in the future.
+//
+// There's also a big difference when it comes to how inputs are modelled:
+//   - Nix only uses store path (strings) to describe the inputs.
+//     As store paths can be input-addressed, a certain store path can contain
+//     different contents (as not all store paths are binary reproducible).
+//     This requires that for every input-addressed input, the builder has access
+//     to either the input's deriver (and needs to build it) or else a trusted
+//     source for the built input.
+//     to upload input-addressed paths, requiring the trusted users concept.
+//   - tvix-build records a list of tvix.castore.v1.Node as inputs.
+//     These map from the store path base name to their contents, relieving the
+//     builder from having to "trust" any input-addressed paths, contrary to Nix.
+//
+// While this approach gives a better hermeticity, it has one downside:
+// A BuildRequest can only be sent once the contents of all its inputs are known.
+//
+// As of now, we're okay to accept this, but it prevents uploading an
+// entirely-non-IFD subgraph of BuildRequests eagerly.
+//
+// FUTUREWORK: We might be introducing another way to refer to inputs, to
+// support "send all BuildRequest for a nixpkgs eval to a remote builder and put
+// the laptop to sleep" usecases later.
+type BuildRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// The list of all root nodes that should be visible in `inputs_dir` at the
+	// time of the build.
+	// As root nodes are content-addressed, no additional signatures are needed
+	// to substitute / make these available in the build environment.
+	// Inputs MUST be sorted by their names.
+	Inputs []*castore_go.Node `protobuf:"bytes,1,rep,name=inputs,proto3" json:"inputs,omitempty"`
+	// The command (and its args) executed as the build script.
+	// In the case of a Nix derivation, this is usually
+	// ["/path/to/some-bash/bin/bash", "-e", "/path/to/some/builder.sh"].
+	CommandArgs []string `protobuf:"bytes,2,rep,name=command_args,json=commandArgs,proto3" json:"command_args,omitempty"`
+	// The working dir of the command, relative to the build root.
+	// "build", in the case of Nix.
+	// This MUST be a clean relative path, without any ".", "..", or superfluous
+	// slashes.
+	WorkingDir string `protobuf:"bytes,3,opt,name=working_dir,json=workingDir,proto3" json:"working_dir,omitempty"`
+	// A list of "scratch" paths, relative to the build root.
+	// These will be write-able during the build.
+	// [build, nix/store] in the case of Nix.
+	// These MUST be clean relative paths, without any ".", "..", or superfluous
+	// slashes, and sorted.
+	ScratchPaths []string `protobuf:"bytes,4,rep,name=scratch_paths,json=scratchPaths,proto3" json:"scratch_paths,omitempty"`
+	// The path where the castore input nodes will be located at,
+	// "nix/store" in case of Nix.
+	// Builds might also write into here (Nix builds do that).
+	// This MUST be a clean relative path, without any ".", "..", or superfluous
+	// slashes.
+	InputsDir string `protobuf:"bytes,5,opt,name=inputs_dir,json=inputsDir,proto3" json:"inputs_dir,omitempty"`
+	// The list of output paths the build is expected to produce,
+	// relative to the root.
+	// If the path is not produced, the build is considered to have failed.
+	// These MUST be clean relative paths, without any ".", "..", or superfluous
+	// slashes, and sorted.
+	Outputs []string `protobuf:"bytes,6,rep,name=outputs,proto3" json:"outputs,omitempty"`
+	// The list of environment variables and their values that should be set
+	// inside the build environment.
+	// This includes both environment vars set inside the derivation, as well as
+	// more "ephemeral" ones like NIX_BUILD_CORES, controlled by the `--cores`
+	// CLI option of `nix-build`.
+	// For now, we consume this as an option when turning a Derivation into a BuildRequest,
+	// similar to how Nix has a `--cores` option.
+	// We don't want to bleed these very nix-specific sandbox impl details into
+	// (dumber) builders if we don't have to.
+	// Environment variables are sorted by their keys.
+	EnvironmentVars []*BuildRequest_EnvVar `protobuf:"bytes,7,rep,name=environment_vars,json=environmentVars,proto3" json:"environment_vars,omitempty"`
+	// A set of constraints that need to be satisfied on a build host before a
+	// Build can be started.
+	Constraints *BuildRequest_BuildConstraints `protobuf:"bytes,8,opt,name=constraints,proto3" json:"constraints,omitempty"`
+	// Additional (small) files and their contents that should be placed into the
+	// build environment, but outside inputs_dir.
+	// Used for passAsFile and structuredAttrs in Nix.
+	AdditionalFiles []*BuildRequest_AdditionalFile `protobuf:"bytes,9,rep,name=additional_files,json=additionalFiles,proto3" json:"additional_files,omitempty"`
+}
+
+func (x *BuildRequest) Reset() {
+	*x = BuildRequest{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_tvix_build_protos_build_proto_msgTypes[0]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *BuildRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*BuildRequest) ProtoMessage() {}
+
+func (x *BuildRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_tvix_build_protos_build_proto_msgTypes[0]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use BuildRequest.ProtoReflect.Descriptor instead.
+func (*BuildRequest) Descriptor() ([]byte, []int) {
+	return file_tvix_build_protos_build_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *BuildRequest) GetInputs() []*castore_go.Node {
+	if x != nil {
+		return x.Inputs
+	}
+	return nil
+}
+
+func (x *BuildRequest) GetCommandArgs() []string {
+	if x != nil {
+		return x.CommandArgs
+	}
+	return nil
+}
+
+func (x *BuildRequest) GetWorkingDir() string {
+	if x != nil {
+		return x.WorkingDir
+	}
+	return ""
+}
+
+func (x *BuildRequest) GetScratchPaths() []string {
+	if x != nil {
+		return x.ScratchPaths
+	}
+	return nil
+}
+
+func (x *BuildRequest) GetInputsDir() string {
+	if x != nil {
+		return x.InputsDir
+	}
+	return ""
+}
+
+func (x *BuildRequest) GetOutputs() []string {
+	if x != nil {
+		return x.Outputs
+	}
+	return nil
+}
+
+func (x *BuildRequest) GetEnvironmentVars() []*BuildRequest_EnvVar {
+	if x != nil {
+		return x.EnvironmentVars
+	}
+	return nil
+}
+
+func (x *BuildRequest) GetConstraints() *BuildRequest_BuildConstraints {
+	if x != nil {
+		return x.Constraints
+	}
+	return nil
+}
+
+func (x *BuildRequest) GetAdditionalFiles() []*BuildRequest_AdditionalFile {
+	if x != nil {
+		return x.AdditionalFiles
+	}
+	return nil
+}
+
+// A Build is (one possible) outcome of executing a [BuildRequest].
+type Build struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// The orginal build request producing the build.
+	BuildRequest *BuildRequest `protobuf:"bytes,1,opt,name=build_request,json=buildRequest,proto3" json:"build_request,omitempty"` // <- TODO: define hashing scheme for BuildRequest, refer to it by hash?
+	// The outputs that were produced after successfully building.
+	// They are sorted by their names.
+	Outputs []*castore_go.Node `protobuf:"bytes,2,rep,name=outputs,proto3" json:"outputs,omitempty"`
+}
+
+func (x *Build) Reset() {
+	*x = Build{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_tvix_build_protos_build_proto_msgTypes[1]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *Build) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Build) ProtoMessage() {}
+
+func (x *Build) ProtoReflect() protoreflect.Message {
+	mi := &file_tvix_build_protos_build_proto_msgTypes[1]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use Build.ProtoReflect.Descriptor instead.
+func (*Build) Descriptor() ([]byte, []int) {
+	return file_tvix_build_protos_build_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *Build) GetBuildRequest() *BuildRequest {
+	if x != nil {
+		return x.BuildRequest
+	}
+	return nil
+}
+
+func (x *Build) GetOutputs() []*castore_go.Node {
+	if x != nil {
+		return x.Outputs
+	}
+	return nil
+}
+
+type BuildRequest_EnvVar struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// name of the environment variable. Must not contain =.
+	Key   string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
+	Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"`
+}
+
+func (x *BuildRequest_EnvVar) Reset() {
+	*x = BuildRequest_EnvVar{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_tvix_build_protos_build_proto_msgTypes[2]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *BuildRequest_EnvVar) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*BuildRequest_EnvVar) ProtoMessage() {}
+
+func (x *BuildRequest_EnvVar) ProtoReflect() protoreflect.Message {
+	mi := &file_tvix_build_protos_build_proto_msgTypes[2]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use BuildRequest_EnvVar.ProtoReflect.Descriptor instead.
+func (*BuildRequest_EnvVar) Descriptor() ([]byte, []int) {
+	return file_tvix_build_protos_build_proto_rawDescGZIP(), []int{0, 0}
+}
+
+func (x *BuildRequest_EnvVar) GetKey() string {
+	if x != nil {
+		return x.Key
+	}
+	return ""
+}
+
+func (x *BuildRequest_EnvVar) GetValue() []byte {
+	if x != nil {
+		return x.Value
+	}
+	return nil
+}
+
+// BuildConstraints represents certain conditions that must be fulfilled
+// inside the build environment to be able to build this.
+// Constraints can be things like required architecture and minimum amount of memory.
+// The required input paths are *not* represented in here, because it
+// wouldn't be hermetic enough - see the comment around inputs too.
+type BuildRequest_BuildConstraints struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// The system that's needed to execute the build.
+	// Must not be empty.
+	System string `protobuf:"bytes,1,opt,name=system,proto3" json:"system,omitempty"`
+	// The amount of memory required to be available for the build, in bytes.
+	MinMemory uint64 `protobuf:"varint,2,opt,name=min_memory,json=minMemory,proto3" json:"min_memory,omitempty"`
+	// A list of (absolute) paths that need to be available in the build
+	// environment, like `/dev/kvm`.
+	// This is distinct from the castore nodes in inputs.
+	// TODO: check if these should be individual constraints instead.
+	// These MUST be clean absolute paths, without any ".", "..", or superfluous
+	// slashes, and sorted.
+	AvailableRoPaths []string `protobuf:"bytes,3,rep,name=available_ro_paths,json=availableRoPaths,proto3" json:"available_ro_paths,omitempty"`
+	// Whether the build should be able to access the network,
+	NetworkAccess bool `protobuf:"varint,4,opt,name=network_access,json=networkAccess,proto3" json:"network_access,omitempty"`
+	// Whether to provide a /bin/sh inside the build environment, usually a static bash.
+	ProvideBinSh bool `protobuf:"varint,5,opt,name=provide_bin_sh,json=provideBinSh,proto3" json:"provide_bin_sh,omitempty"`
+}
+
+func (x *BuildRequest_BuildConstraints) Reset() {
+	*x = BuildRequest_BuildConstraints{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_tvix_build_protos_build_proto_msgTypes[3]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *BuildRequest_BuildConstraints) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*BuildRequest_BuildConstraints) ProtoMessage() {}
+
+func (x *BuildRequest_BuildConstraints) ProtoReflect() protoreflect.Message {
+	mi := &file_tvix_build_protos_build_proto_msgTypes[3]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use BuildRequest_BuildConstraints.ProtoReflect.Descriptor instead.
+func (*BuildRequest_BuildConstraints) Descriptor() ([]byte, []int) {
+	return file_tvix_build_protos_build_proto_rawDescGZIP(), []int{0, 1}
+}
+
+func (x *BuildRequest_BuildConstraints) GetSystem() string {
+	if x != nil {
+		return x.System
+	}
+	return ""
+}
+
+func (x *BuildRequest_BuildConstraints) GetMinMemory() uint64 {
+	if x != nil {
+		return x.MinMemory
+	}
+	return 0
+}
+
+func (x *BuildRequest_BuildConstraints) GetAvailableRoPaths() []string {
+	if x != nil {
+		return x.AvailableRoPaths
+	}
+	return nil
+}
+
+func (x *BuildRequest_BuildConstraints) GetNetworkAccess() bool {
+	if x != nil {
+		return x.NetworkAccess
+	}
+	return false
+}
+
+func (x *BuildRequest_BuildConstraints) GetProvideBinSh() bool {
+	if x != nil {
+		return x.ProvideBinSh
+	}
+	return false
+}
+
+type BuildRequest_AdditionalFile struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Path     string `protobuf:"bytes,1,opt,name=path,proto3" json:"path,omitempty"`
+	Contents []byte `protobuf:"bytes,2,opt,name=contents,proto3" json:"contents,omitempty"`
+}
+
+func (x *BuildRequest_AdditionalFile) Reset() {
+	*x = BuildRequest_AdditionalFile{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_tvix_build_protos_build_proto_msgTypes[4]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *BuildRequest_AdditionalFile) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*BuildRequest_AdditionalFile) ProtoMessage() {}
+
+func (x *BuildRequest_AdditionalFile) ProtoReflect() protoreflect.Message {
+	mi := &file_tvix_build_protos_build_proto_msgTypes[4]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use BuildRequest_AdditionalFile.ProtoReflect.Descriptor instead.
+func (*BuildRequest_AdditionalFile) Descriptor() ([]byte, []int) {
+	return file_tvix_build_protos_build_proto_rawDescGZIP(), []int{0, 2}
+}
+
+func (x *BuildRequest_AdditionalFile) GetPath() string {
+	if x != nil {
+		return x.Path
+	}
+	return ""
+}
+
+func (x *BuildRequest_AdditionalFile) GetContents() []byte {
+	if x != nil {
+		return x.Contents
+	}
+	return nil
+}
+
+var File_tvix_build_protos_build_proto protoreflect.FileDescriptor
+
+var file_tvix_build_protos_build_proto_rawDesc = []byte{
+	0x0a, 0x1d, 0x74, 0x76, 0x69, 0x78, 0x2f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x2f, 0x70, 0x72, 0x6f,
+	0x74, 0x6f, 0x73, 0x2f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12,
+	0x0d, 0x74, 0x76, 0x69, 0x78, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x2e, 0x76, 0x31, 0x1a, 0x21,
+	0x74, 0x76, 0x69, 0x78, 0x2f, 0x63, 0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f,
+	0x74, 0x6f, 0x73, 0x2f, 0x63, 0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
+	0x6f, 0x22, 0x90, 0x06, 0x0a, 0x0c, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65,
+	0x73, 0x74, 0x12, 0x2d, 0x0a, 0x06, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03,
+	0x28, 0x0b, 0x32, 0x15, 0x2e, 0x74, 0x76, 0x69, 0x78, 0x2e, 0x63, 0x61, 0x73, 0x74, 0x6f, 0x72,
+	0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x06, 0x69, 0x6e, 0x70, 0x75, 0x74,
+	0x73, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x61, 0x72, 0x67,
+	0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64,
+	0x41, 0x72, 0x67, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x77, 0x6f, 0x72, 0x6b, 0x69, 0x6e, 0x67, 0x5f,
+	0x64, 0x69, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x77, 0x6f, 0x72, 0x6b, 0x69,
+	0x6e, 0x67, 0x44, 0x69, 0x72, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x63, 0x72, 0x61, 0x74, 0x63, 0x68,
+	0x5f, 0x70, 0x61, 0x74, 0x68, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x73, 0x63,
+	0x72, 0x61, 0x74, 0x63, 0x68, 0x50, 0x61, 0x74, 0x68, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x69, 0x6e,
+	0x70, 0x75, 0x74, 0x73, 0x5f, 0x64, 0x69, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09,
+	0x69, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x44, 0x69, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x6f, 0x75, 0x74,
+	0x70, 0x75, 0x74, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x6f, 0x75, 0x74, 0x70,
+	0x75, 0x74, 0x73, 0x12, 0x4d, 0x0a, 0x10, 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65,
+	0x6e, 0x74, 0x5f, 0x76, 0x61, 0x72, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e,
+	0x74, 0x76, 0x69, 0x78, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x75,
+	0x69, 0x6c, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x45, 0x6e, 0x76, 0x56, 0x61,
+	0x72, 0x52, 0x0f, 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x56, 0x61,
+	0x72, 0x73, 0x12, 0x4e, 0x0a, 0x0b, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74,
+	0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x74, 0x76, 0x69, 0x78, 0x2e, 0x62,
+	0x75, 0x69, 0x6c, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x52, 0x65, 0x71,
+	0x75, 0x65, 0x73, 0x74, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72,
+	0x61, 0x69, 0x6e, 0x74, 0x73, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e,
+	0x74, 0x73, 0x12, 0x55, 0x0a, 0x10, 0x61, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c,
+	0x5f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x74,
+	0x76, 0x69, 0x78, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x75, 0x69,
+	0x6c, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x41, 0x64, 0x64, 0x69, 0x74, 0x69,
+	0x6f, 0x6e, 0x61, 0x6c, 0x46, 0x69, 0x6c, 0x65, 0x52, 0x0f, 0x61, 0x64, 0x64, 0x69, 0x74, 0x69,
+	0x6f, 0x6e, 0x61, 0x6c, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x1a, 0x30, 0x0a, 0x06, 0x45, 0x6e, 0x76,
+	0x56, 0x61, 0x72, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
+	0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02,
+	0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0xc4, 0x01, 0x0a, 0x10,
+	0x42, 0x75, 0x69, 0x6c, 0x64, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x73,
+	0x12, 0x16, 0x0a, 0x06, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
+	0x52, 0x06, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x69, 0x6e, 0x5f,
+	0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x6d, 0x69,
+	0x6e, 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x12, 0x2c, 0x0a, 0x12, 0x61, 0x76, 0x61, 0x69, 0x6c,
+	0x61, 0x62, 0x6c, 0x65, 0x5f, 0x72, 0x6f, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x73, 0x18, 0x03, 0x20,
+	0x03, 0x28, 0x09, 0x52, 0x10, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x6f,
+	0x50, 0x61, 0x74, 0x68, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b,
+	0x5f, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x6e,
+	0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x12, 0x24, 0x0a, 0x0e,
+	0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x5f, 0x62, 0x69, 0x6e, 0x5f, 0x73, 0x68, 0x18, 0x05,
+	0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x42, 0x69, 0x6e,
+	0x53, 0x68, 0x1a, 0x40, 0x0a, 0x0e, 0x41, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c,
+	0x46, 0x69, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01,
+	0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x6f, 0x6e, 0x74,
+	0x65, 0x6e, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x63, 0x6f, 0x6e, 0x74,
+	0x65, 0x6e, 0x74, 0x73, 0x22, 0x7a, 0x0a, 0x05, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x12, 0x40, 0x0a,
+	0x0d, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x01,
+	0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x76, 0x69, 0x78, 0x2e, 0x62, 0x75, 0x69, 0x6c,
+	0x64, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
+	0x74, 0x52, 0x0c, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
+	0x2f, 0x0a, 0x07, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b,
+	0x32, 0x15, 0x2e, 0x74, 0x76, 0x69, 0x78, 0x2e, 0x63, 0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e,
+	0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x07, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73,
+	0x42, 0x24, 0x5a, 0x22, 0x63, 0x6f, 0x64, 0x65, 0x2e, 0x74, 0x76, 0x6c, 0x2e, 0x66, 0x79, 0x69,
+	0x2f, 0x74, 0x76, 0x69, 0x78, 0x2f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x2d, 0x67, 0x6f, 0x3b, 0x62,
+	0x75, 0x69, 0x6c, 0x64, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+}
+
+var (
+	file_tvix_build_protos_build_proto_rawDescOnce sync.Once
+	file_tvix_build_protos_build_proto_rawDescData = file_tvix_build_protos_build_proto_rawDesc
+)
+
+func file_tvix_build_protos_build_proto_rawDescGZIP() []byte {
+	file_tvix_build_protos_build_proto_rawDescOnce.Do(func() {
+		file_tvix_build_protos_build_proto_rawDescData = protoimpl.X.CompressGZIP(file_tvix_build_protos_build_proto_rawDescData)
+	})
+	return file_tvix_build_protos_build_proto_rawDescData
+}
+
+var file_tvix_build_protos_build_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
+var file_tvix_build_protos_build_proto_goTypes = []interface{}{
+	(*BuildRequest)(nil),                  // 0: tvix.build.v1.BuildRequest
+	(*Build)(nil),                         // 1: tvix.build.v1.Build
+	(*BuildRequest_EnvVar)(nil),           // 2: tvix.build.v1.BuildRequest.EnvVar
+	(*BuildRequest_BuildConstraints)(nil), // 3: tvix.build.v1.BuildRequest.BuildConstraints
+	(*BuildRequest_AdditionalFile)(nil),   // 4: tvix.build.v1.BuildRequest.AdditionalFile
+	(*castore_go.Node)(nil),               // 5: tvix.castore.v1.Node
+}
+var file_tvix_build_protos_build_proto_depIdxs = []int32{
+	5, // 0: tvix.build.v1.BuildRequest.inputs:type_name -> tvix.castore.v1.Node
+	2, // 1: tvix.build.v1.BuildRequest.environment_vars:type_name -> tvix.build.v1.BuildRequest.EnvVar
+	3, // 2: tvix.build.v1.BuildRequest.constraints:type_name -> tvix.build.v1.BuildRequest.BuildConstraints
+	4, // 3: tvix.build.v1.BuildRequest.additional_files:type_name -> tvix.build.v1.BuildRequest.AdditionalFile
+	0, // 4: tvix.build.v1.Build.build_request:type_name -> tvix.build.v1.BuildRequest
+	5, // 5: tvix.build.v1.Build.outputs:type_name -> tvix.castore.v1.Node
+	6, // [6:6] is the sub-list for method output_type
+	6, // [6:6] is the sub-list for method input_type
+	6, // [6:6] is the sub-list for extension type_name
+	6, // [6:6] is the sub-list for extension extendee
+	0, // [0:6] is the sub-list for field type_name
+}
+
+func init() { file_tvix_build_protos_build_proto_init() }
+func file_tvix_build_protos_build_proto_init() {
+	if File_tvix_build_protos_build_proto != nil {
+		return
+	}
+	if !protoimpl.UnsafeEnabled {
+		file_tvix_build_protos_build_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*BuildRequest); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_tvix_build_protos_build_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*Build); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_tvix_build_protos_build_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*BuildRequest_EnvVar); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_tvix_build_protos_build_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*BuildRequest_BuildConstraints); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_tvix_build_protos_build_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*BuildRequest_AdditionalFile); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+	}
+	type x struct{}
+	out := protoimpl.TypeBuilder{
+		File: protoimpl.DescBuilder{
+			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+			RawDescriptor: file_tvix_build_protos_build_proto_rawDesc,
+			NumEnums:      0,
+			NumMessages:   5,
+			NumExtensions: 0,
+			NumServices:   0,
+		},
+		GoTypes:           file_tvix_build_protos_build_proto_goTypes,
+		DependencyIndexes: file_tvix_build_protos_build_proto_depIdxs,
+		MessageInfos:      file_tvix_build_protos_build_proto_msgTypes,
+	}.Build()
+	File_tvix_build_protos_build_proto = out.File
+	file_tvix_build_protos_build_proto_rawDesc = nil
+	file_tvix_build_protos_build_proto_goTypes = nil
+	file_tvix_build_protos_build_proto_depIdxs = nil
+}
diff --git a/tvix/build-go/default.nix b/tvix/build-go/default.nix
new file mode 100644
index 0000000000..62cd01b0f9
--- /dev/null
+++ b/tvix/build-go/default.nix
@@ -0,0 +1,31 @@
+{ depot, pkgs, ... }:
+
+let
+  regenerate = pkgs.writeShellScript "regenerate" ''
+    (cd $(git rev-parse --show-toplevel)/tvix/build-go && rm *.pb.go && cp ${depot.tvix.build.protos.go-bindings}/*.pb.go . && chmod +w *.pb.go)
+  '';
+in
+(pkgs.buildGoModule {
+  name = "build-go";
+  src = depot.third_party.gitignoreSource ./.;
+  vendorHash = "sha256-BprOPkgyT1F6TNToCN2uSHlkCXMdmv/QK+lTvA6O/rM=";
+}).overrideAttrs (_: {
+  meta.ci.extraSteps = {
+    check = {
+      label = ":water_buffalo: ensure generated protobuf files match";
+      needsOutput = true;
+      command = pkgs.writeShellScript "pb-go-check" ''
+        ${regenerate}
+        if [[ -n "$(git status --porcelain -unormal)" ]]; then
+            echo "-----------------------------"
+            echo ".pb.go files need to be updated, mg run //tvix/build-go/regenerate"
+            echo "-----------------------------"
+            git status -unormal
+            exit 1
+        fi
+      '';
+      alwaysRun = true;
+    };
+  };
+  passthru.regenerate = regenerate;
+})
diff --git a/tvix/build-go/go.mod b/tvix/build-go/go.mod
new file mode 100644
index 0000000000..1454b5cada
--- /dev/null
+++ b/tvix/build-go/go.mod
@@ -0,0 +1,19 @@
+module code.tvl.fyi/tvix/build-go
+
+go 1.19
+
+require (
+	code.tvl.fyi/tvix/castore-go v0.0.0-20231105151352-990d6ba2175e
+	google.golang.org/grpc v1.51.0
+	google.golang.org/protobuf v1.31.0
+)
+
+require (
+	github.com/golang/protobuf v1.5.2 // indirect
+	github.com/klauspost/cpuid/v2 v2.0.9 // indirect
+	golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect
+	golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect
+	golang.org/x/text v0.4.0 // indirect
+	google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect
+	lukechampine.com/blake3 v1.1.7 // indirect
+)
diff --git a/tvix/build-go/go.sum b/tvix/build-go/go.sum
new file mode 100644
index 0000000000..cd64b9966b
--- /dev/null
+++ b/tvix/build-go/go.sum
@@ -0,0 +1,88 @@
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+code.tvl.fyi/tvix/castore-go v0.0.0-20231105151352-990d6ba2175e h1:Nj+anfyEYeEdhnIo2BG/N1ZwQl1IvI7AH3TbNDLwUOA=
+code.tvl.fyi/tvix/castore-go v0.0.0-20231105151352-990d6ba2175e/go.mod h1:+vKbozsa04yy2TWh3kUVU568jaza3Hf0p1jAEoMoCwA=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
+github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
+github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
+github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
+github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
+golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=
+google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.51.0 h1:E1eGv1FTqoLIdnBCZufiSHgKjlqG6fKFf6pPWtMTh8U=
+google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww=
+google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
+google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
+google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
+google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
+google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+lukechampine.com/blake3 v1.1.7 h1:GgRMhmdsuK8+ii6UZFDL8Nb+VyMwadAgcJyfYHxG6n0=
+lukechampine.com/blake3 v1.1.7/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA=
diff --git a/tvix/build-go/rpc_build.pb.go b/tvix/build-go/rpc_build.pb.go
new file mode 100644
index 0000000000..aae0cd9d47
--- /dev/null
+++ b/tvix/build-go/rpc_build.pb.go
@@ -0,0 +1,80 @@
+// SPDX-License-Identifier: MIT
+// Copyright © 2022 The Tvix Authors
+
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// 	protoc-gen-go v1.33.0
+// 	protoc        (unknown)
+// source: tvix/build/protos/rpc_build.proto
+
+package buildv1
+
+import (
+	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+	reflect "reflect"
+)
+
+const (
+	// Verify that this generated code is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+	// Verify that runtime/protoimpl is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+var File_tvix_build_protos_rpc_build_proto protoreflect.FileDescriptor
+
+var file_tvix_build_protos_rpc_build_proto_rawDesc = []byte{
+	0x0a, 0x21, 0x74, 0x76, 0x69, 0x78, 0x2f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x2f, 0x70, 0x72, 0x6f,
+	0x74, 0x6f, 0x73, 0x2f, 0x72, 0x70, 0x63, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x2e, 0x70, 0x72,
+	0x6f, 0x74, 0x6f, 0x12, 0x0d, 0x74, 0x76, 0x69, 0x78, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x2e,
+	0x76, 0x31, 0x1a, 0x1d, 0x74, 0x76, 0x69, 0x78, 0x2f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x2f, 0x70,
+	0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x2e, 0x70, 0x72, 0x6f, 0x74,
+	0x6f, 0x32, 0x4c, 0x0a, 0x0c, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63,
+	0x65, 0x12, 0x3c, 0x0a, 0x07, 0x44, 0x6f, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x12, 0x1b, 0x2e, 0x74,
+	0x76, 0x69, 0x78, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x75, 0x69,
+	0x6c, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x74, 0x76, 0x69, 0x78,
+	0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x42,
+	0x24, 0x5a, 0x22, 0x63, 0x6f, 0x64, 0x65, 0x2e, 0x74, 0x76, 0x6c, 0x2e, 0x66, 0x79, 0x69, 0x2f,
+	0x74, 0x76, 0x69, 0x78, 0x2f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x2d, 0x67, 0x6f, 0x3b, 0x62, 0x75,
+	0x69, 0x6c, 0x64, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+}
+
+var file_tvix_build_protos_rpc_build_proto_goTypes = []interface{}{
+	(*BuildRequest)(nil), // 0: tvix.build.v1.BuildRequest
+	(*Build)(nil),        // 1: tvix.build.v1.Build
+}
+var file_tvix_build_protos_rpc_build_proto_depIdxs = []int32{
+	0, // 0: tvix.build.v1.BuildService.DoBuild:input_type -> tvix.build.v1.BuildRequest
+	1, // 1: tvix.build.v1.BuildService.DoBuild:output_type -> tvix.build.v1.Build
+	1, // [1:2] is the sub-list for method output_type
+	0, // [0:1] is the sub-list for method input_type
+	0, // [0:0] is the sub-list for extension type_name
+	0, // [0:0] is the sub-list for extension extendee
+	0, // [0:0] is the sub-list for field type_name
+}
+
+func init() { file_tvix_build_protos_rpc_build_proto_init() }
+func file_tvix_build_protos_rpc_build_proto_init() {
+	if File_tvix_build_protos_rpc_build_proto != nil {
+		return
+	}
+	file_tvix_build_protos_build_proto_init()
+	type x struct{}
+	out := protoimpl.TypeBuilder{
+		File: protoimpl.DescBuilder{
+			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+			RawDescriptor: file_tvix_build_protos_rpc_build_proto_rawDesc,
+			NumEnums:      0,
+			NumMessages:   0,
+			NumExtensions: 0,
+			NumServices:   1,
+		},
+		GoTypes:           file_tvix_build_protos_rpc_build_proto_goTypes,
+		DependencyIndexes: file_tvix_build_protos_rpc_build_proto_depIdxs,
+	}.Build()
+	File_tvix_build_protos_rpc_build_proto = out.File
+	file_tvix_build_protos_rpc_build_proto_rawDesc = nil
+	file_tvix_build_protos_rpc_build_proto_goTypes = nil
+	file_tvix_build_protos_rpc_build_proto_depIdxs = nil
+}
diff --git a/tvix/build-go/rpc_build_grpc.pb.go b/tvix/build-go/rpc_build_grpc.pb.go
new file mode 100644
index 0000000000..0ef5855982
--- /dev/null
+++ b/tvix/build-go/rpc_build_grpc.pb.go
@@ -0,0 +1,112 @@
+// SPDX-License-Identifier: MIT
+// Copyright © 2022 The Tvix Authors
+
+// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
+// versions:
+// - protoc-gen-go-grpc v1.3.0
+// - protoc             (unknown)
+// source: tvix/build/protos/rpc_build.proto
+
+package buildv1
+
+import (
+	context "context"
+	grpc "google.golang.org/grpc"
+	codes "google.golang.org/grpc/codes"
+	status "google.golang.org/grpc/status"
+)
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the grpc package it is being compiled against.
+// Requires gRPC-Go v1.32.0 or later.
+const _ = grpc.SupportPackageIsVersion7
+
+const (
+	BuildService_DoBuild_FullMethodName = "/tvix.build.v1.BuildService/DoBuild"
+)
+
+// BuildServiceClient is the client API for BuildService service.
+//
+// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
+type BuildServiceClient interface {
+	DoBuild(ctx context.Context, in *BuildRequest, opts ...grpc.CallOption) (*Build, error)
+}
+
+type buildServiceClient struct {
+	cc grpc.ClientConnInterface
+}
+
+func NewBuildServiceClient(cc grpc.ClientConnInterface) BuildServiceClient {
+	return &buildServiceClient{cc}
+}
+
+func (c *buildServiceClient) DoBuild(ctx context.Context, in *BuildRequest, opts ...grpc.CallOption) (*Build, error) {
+	out := new(Build)
+	err := c.cc.Invoke(ctx, BuildService_DoBuild_FullMethodName, in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+// BuildServiceServer is the server API for BuildService service.
+// All implementations must embed UnimplementedBuildServiceServer
+// for forward compatibility
+type BuildServiceServer interface {
+	DoBuild(context.Context, *BuildRequest) (*Build, error)
+	mustEmbedUnimplementedBuildServiceServer()
+}
+
+// UnimplementedBuildServiceServer must be embedded to have forward compatible implementations.
+type UnimplementedBuildServiceServer struct {
+}
+
+func (UnimplementedBuildServiceServer) DoBuild(context.Context, *BuildRequest) (*Build, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method DoBuild not implemented")
+}
+func (UnimplementedBuildServiceServer) mustEmbedUnimplementedBuildServiceServer() {}
+
+// UnsafeBuildServiceServer may be embedded to opt out of forward compatibility for this service.
+// Use of this interface is not recommended, as added methods to BuildServiceServer will
+// result in compilation errors.
+type UnsafeBuildServiceServer interface {
+	mustEmbedUnimplementedBuildServiceServer()
+}
+
+func RegisterBuildServiceServer(s grpc.ServiceRegistrar, srv BuildServiceServer) {
+	s.RegisterService(&BuildService_ServiceDesc, srv)
+}
+
+func _BuildService_DoBuild_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(BuildRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(BuildServiceServer).DoBuild(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: BuildService_DoBuild_FullMethodName,
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(BuildServiceServer).DoBuild(ctx, req.(*BuildRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+// BuildService_ServiceDesc is the grpc.ServiceDesc for BuildService service.
+// It's only intended for direct use with grpc.RegisterService,
+// and not to be introspected or modified (even as a copy)
+var BuildService_ServiceDesc = grpc.ServiceDesc{
+	ServiceName: "tvix.build.v1.BuildService",
+	HandlerType: (*BuildServiceServer)(nil),
+	Methods: []grpc.MethodDesc{
+		{
+			MethodName: "DoBuild",
+			Handler:    _BuildService_DoBuild_Handler,
+		},
+	},
+	Streams:  []grpc.StreamDesc{},
+	Metadata: "tvix/build/protos/rpc_build.proto",
+}
diff --git a/tvix/build/Cargo.toml b/tvix/build/Cargo.toml
new file mode 100644
index 0000000000..626fd35d77
--- /dev/null
+++ b/tvix/build/Cargo.toml
@@ -0,0 +1,33 @@
+[package]
+name = "tvix-build"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+bytes = "1.4.0"
+clap = { version = "4.0", features = ["derive", "env"] }
+itertools = "0.12.0"
+prost = "0.12.1"
+thiserror = "1.0.56"
+tokio = { version = "1.32.0" }
+tokio-listener = { version = "0.3.2", features = [ "tonic011" ] }
+tonic = { version = "0.11.0", features = ["tls", "tls-roots"] }
+tvix-castore = { path = "../castore" }
+tracing = "0.1.37"
+tracing-subscriber = { version = "0.3.16", features = ["json"] }
+url = "2.4.0"
+
+[dependencies.tonic-reflection]
+optional = true
+version = "0.11.0"
+
+[build-dependencies]
+prost-build = "0.12.1"
+tonic-build = "0.11.0"
+
+[features]
+default = []
+tonic-reflection = ["dep:tonic-reflection"]
+
+[dev-dependencies]
+rstest = "0.19.0"
diff --git a/tvix/build/build.rs b/tvix/build/build.rs
new file mode 100644
index 0000000000..c3518ea877
--- /dev/null
+++ b/tvix/build/build.rs
@@ -0,0 +1,38 @@
+use std::io::Result;
+
+fn main() -> Result<()> {
+    #[allow(unused_mut)]
+    let mut builder = tonic_build::configure();
+
+    #[cfg(feature = "tonic-reflection")]
+    {
+        let out_dir = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap());
+        let descriptor_path = out_dir.join("tvix.build.v1.bin");
+
+        builder = builder.file_descriptor_set_path(descriptor_path);
+    };
+
+    // https://github.com/hyperium/tonic/issues/908
+    let mut config = prost_build::Config::new();
+    config.bytes(["."]);
+    config.extern_path(".tvix.castore.v1", "::tvix_castore::proto");
+
+    builder
+        .build_server(true)
+        .build_client(true)
+        .emit_rerun_if_changed(false)
+        .compile_with_config(
+            config,
+            &[
+                "tvix/build/protos/build.proto",
+                "tvix/build/protos/rpc_build.proto",
+            ],
+            // If we are in running `cargo build` manually, using `../..` works fine,
+            // but in case we run inside a nix build, we need to instead point PROTO_ROOT
+            // to a sparseTree containing that structure.
+            &[match std::env::var_os("PROTO_ROOT") {
+                Some(proto_root) => proto_root.to_str().unwrap().to_owned(),
+                None => "../..".to_string(),
+            }],
+        )
+}
diff --git a/tvix/build/default.nix b/tvix/build/default.nix
new file mode 100644
index 0000000000..a2a3bea0c5
--- /dev/null
+++ b/tvix/build/default.nix
@@ -0,0 +1,5 @@
+{ depot, pkgs, ... }:
+
+depot.tvix.crates.workspaceMembers.tvix-build.build.override {
+  runTests = true;
+}
diff --git a/tvix/build/protos/LICENSE b/tvix/build/protos/LICENSE
new file mode 100644
index 0000000000..2034ada6fd
--- /dev/null
+++ b/tvix/build/protos/LICENSE
@@ -0,0 +1,21 @@
+Copyright © The Tvix Authors
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+“Software”), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
diff --git a/tvix/build/protos/build.proto b/tvix/build/protos/build.proto
new file mode 100644
index 0000000000..f1f6bf0b05
--- /dev/null
+++ b/tvix/build/protos/build.proto
@@ -0,0 +1,163 @@
+// SPDX-License-Identifier: MIT
+// Copyright © 2022 The Tvix Authors
+
+syntax = "proto3";
+
+package tvix.build.v1;
+
+import "tvix/castore/protos/castore.proto";
+
+option go_package = "code.tvl.fyi/tvix/build-go;buildv1";
+
+// A BuildRequest describes the request of something to be run on the builder.
+// It is distinct from an actual [Build] that has already happened, or might be
+// currently ongoing.
+//
+// A BuildRequest can be seen as a more normalized version of a Derivation
+// (parsed from A-Term), "writing out" some of the Nix-internal details about
+// how e.g. environment variables in the build are set.
+//
+// Nix has some impurities when building a Derivation, for example the --cores option
+// ends up as an environment variable in the build, that's not part of the ATerm.
+//
+// As of now, we serialize this into the BuildRequest, so builders can stay dumb.
+// This might change in the future.
+//
+// There's also a big difference when it comes to how inputs are modelled:
+//  - Nix only uses store path (strings) to describe the inputs.
+//    As store paths can be input-addressed, a certain store path can contain
+//    different contents (as not all store paths are binary reproducible).
+//    This requires that for every input-addressed input, the builder has access
+//    to either the input's deriver (and needs to build it) or else a trusted
+//    source for the built input.
+//    to upload input-addressed paths, requiring the trusted users concept.
+//  - tvix-build records a list of tvix.castore.v1.Node as inputs.
+//    These map from the store path base name to their contents, relieving the
+//    builder from having to "trust" any input-addressed paths, contrary to Nix.
+//
+// While this approach gives a better hermeticity, it has one downside:
+// A BuildRequest can only be sent once the contents of all its inputs are known.
+//
+// As of now, we're okay to accept this, but it prevents uploading an
+// entirely-non-IFD subgraph of BuildRequests eagerly.
+//
+// FUTUREWORK: We might be introducing another way to refer to inputs, to
+// support "send all BuildRequest for a nixpkgs eval to a remote builder and put
+// the laptop to sleep" usecases later.
+message BuildRequest {
+  // The list of all root nodes that should be visible in `inputs_dir` at the
+  // time of the build.
+  // As root nodes are content-addressed, no additional signatures are needed
+  // to substitute / make these available in the build environment.
+  // Inputs MUST be sorted by their names.
+  repeated tvix.castore.v1.Node inputs = 1;
+
+  // The command (and its args) executed as the build script.
+  // In the case of a Nix derivation, this is usually
+  // ["/path/to/some-bash/bin/bash", "-e", "/path/to/some/builder.sh"].
+  repeated string command_args = 2;
+
+  // The working dir of the command, relative to the build root.
+  // "build", in the case of Nix.
+  // This MUST be a clean relative path, without any ".", "..", or superfluous
+  // slashes.
+  string working_dir = 3;
+
+  // A list of "scratch" paths, relative to the build root.
+  // These will be write-able during the build.
+  // [build, nix/store] in the case of Nix.
+  // These MUST be clean relative paths, without any ".", "..", or superfluous
+  // slashes, and sorted.
+  repeated string scratch_paths = 4;
+
+  // The path where the castore input nodes will be located at,
+  // "nix/store" in case of Nix.
+  // Builds might also write into here (Nix builds do that).
+  // This MUST be a clean relative path, without any ".", "..", or superfluous
+  // slashes.
+  string inputs_dir = 5;
+
+  // The list of output paths the build is expected to produce,
+  // relative to the root.
+  // If the path is not produced, the build is considered to have failed.
+  // These MUST be clean relative paths, without any ".", "..", or superfluous
+  // slashes, and sorted.
+  repeated string outputs = 6;
+
+  // The list of environment variables and their values that should be set
+  // inside the build environment.
+  // This includes both environment vars set inside the derivation, as well as
+  // more "ephemeral" ones like NIX_BUILD_CORES, controlled by the `--cores`
+  // CLI option of `nix-build`.
+  // For now, we consume this as an option when turning a Derivation into a BuildRequest,
+  // similar to how Nix has a `--cores` option.
+  // We don't want to bleed these very nix-specific sandbox impl details into
+  // (dumber) builders if we don't have to.
+  // Environment variables are sorted by their keys.
+  repeated EnvVar environment_vars = 7;
+
+  message EnvVar {
+    // name of the environment variable. Must not contain =.
+    string key = 1;
+    bytes value = 2;
+  }
+
+  // A set of constraints that need to be satisfied on a build host before a
+  // Build can be started.
+  BuildConstraints constraints = 8;
+
+  // BuildConstraints represents certain conditions that must be fulfilled
+  // inside the build environment to be able to build this.
+  // Constraints can be things like required architecture and minimum amount of memory.
+  // The required input paths are *not* represented in here, because it
+  // wouldn't be hermetic enough - see the comment around inputs too.
+  message BuildConstraints {
+    // The system that's needed to execute the build.
+    // Must not be empty.
+    string system = 1;
+
+    // The amount of memory required to be available for the build, in bytes.
+    uint64 min_memory = 2;
+
+    // A list of (absolute) paths that need to be available in the build
+    // environment, like `/dev/kvm`.
+    // This is distinct from the castore nodes in inputs.
+    // TODO: check if these should be individual constraints instead.
+    // These MUST be clean absolute paths, without any ".", "..", or superfluous
+    // slashes, and sorted.
+    repeated string available_ro_paths = 3;
+
+    // Whether the build should be able to access the network,
+    bool network_access = 4;
+
+    // Whether to provide a /bin/sh inside the build environment, usually a static bash.
+    bool provide_bin_sh = 5;
+  }
+
+  // Additional (small) files and their contents that should be placed into the
+  // build environment, but outside inputs_dir.
+  // Used for passAsFile and structuredAttrs in Nix.
+  repeated AdditionalFile additional_files = 9;
+
+  message AdditionalFile {
+    string path = 1;
+    bytes contents = 2;
+  }
+
+  // TODO: allow describing something like "preferLocal", to influence composition?
+}
+
+// A Build is (one possible) outcome of executing a [BuildRequest].
+message Build {
+  // The orginal build request producing the build.
+  BuildRequest build_request = 1; // <- TODO: define hashing scheme for BuildRequest, refer to it by hash?
+
+  // The outputs that were produced after successfully building.
+  // They are sorted by their names.
+  repeated tvix.castore.v1.Node outputs = 2;
+
+  // TODO: where did this run, how long, logs, …
+}
+
+/// TODO: check remarkable notes on constraints again
+/// TODO: https://github.com/adisbladis/go-nix/commit/603df5db86ab97ba29f6f94d74f4e51642c56834
diff --git a/tvix/build/protos/default.nix b/tvix/build/protos/default.nix
new file mode 100644
index 0000000000..790655ac75
--- /dev/null
+++ b/tvix/build/protos/default.nix
@@ -0,0 +1,56 @@
+{ depot, pkgs, ... }:
+let
+  protos = depot.nix.sparseTree {
+    name = "build-protos";
+    root = depot.path.origSrc;
+    paths = [
+      # We need to include castore.proto (only), as it's referred.
+      ../../castore/protos/castore.proto
+      ./build.proto
+      ./rpc_build.proto
+      ../../../buf.yaml
+      ../../../buf.gen.yaml
+    ];
+  };
+in
+depot.nix.readTree.drvTargets {
+  inherit protos;
+
+  # Lints and ensures formatting of the proto files.
+  check = pkgs.stdenv.mkDerivation {
+    name = "proto-check";
+    src = protos;
+
+    nativeBuildInputs = [
+      pkgs.buf
+    ];
+
+    buildPhase = ''
+      export HOME=$TMPDIR
+      buf lint
+      buf format -d --exit-code
+      touch $out
+    '';
+  };
+
+  # Produces the golang bindings.
+  go-bindings = pkgs.stdenv.mkDerivation {
+    name = "go-bindings";
+
+    src = protos;
+
+    nativeBuildInputs = [
+      pkgs.buf
+      pkgs.protoc-gen-go
+      pkgs.protoc-gen-go-grpc
+    ];
+
+    buildPhase = ''
+      export HOME=$TMPDIR
+      buf generate
+
+      mkdir -p $out
+      cp tvix/build/protos/*.pb.go $out/
+    '';
+  };
+}
diff --git a/tvix/build/protos/rpc_build.proto b/tvix/build/protos/rpc_build.proto
new file mode 100644
index 0000000000..73eebf78fe
--- /dev/null
+++ b/tvix/build/protos/rpc_build.proto
@@ -0,0 +1,13 @@
+// SPDX-License-Identifier: MIT
+// Copyright © 2022 The Tvix Authors
+syntax = "proto3";
+
+package tvix.build.v1;
+
+import "tvix/build/protos/build.proto";
+
+option go_package = "code.tvl.fyi/tvix/build-go;buildv1";
+
+service BuildService {
+  rpc DoBuild(BuildRequest) returns (Build);
+}
diff --git a/tvix/build/src/bin/tvix-build.rs b/tvix/build/src/bin/tvix-build.rs
new file mode 100644
index 0000000000..ed36c8933c
--- /dev/null
+++ b/tvix/build/src/bin/tvix-build.rs
@@ -0,0 +1,132 @@
+use std::sync::Arc;
+
+use clap::Parser;
+use clap::Subcommand;
+use tokio_listener::Listener;
+use tokio_listener::SystemOptions;
+use tokio_listener::UserOptions;
+use tonic::{self, transport::Server};
+use tracing::{info, Level};
+use tracing_subscriber::prelude::*;
+use tvix_build::{
+    buildservice,
+    proto::{build_service_server::BuildServiceServer, GRPCBuildServiceWrapper},
+};
+use tvix_castore::blobservice;
+use tvix_castore::directoryservice;
+
+#[cfg(feature = "tonic-reflection")]
+use tvix_build::proto::FILE_DESCRIPTOR_SET;
+#[cfg(feature = "tonic-reflection")]
+use tvix_castore::proto::FILE_DESCRIPTOR_SET as CASTORE_FILE_DESCRIPTOR_SET;
+
+#[derive(Parser)]
+#[command(author, version, about, long_about = None)]
+struct Cli {
+    /// Whether to log in JSON
+    #[arg(long)]
+    json: bool,
+
+    #[arg(long)]
+    log_level: Option<Level>,
+
+    #[command(subcommand)]
+    command: Commands,
+}
+#[derive(Subcommand)]
+enum Commands {
+    /// Runs the tvix-build daemon.
+    Daemon {
+        #[arg(long, short = 'l')]
+        listen_address: Option<String>,
+
+        #[arg(long, env, default_value = "grpc+http://[::1]:8000")]
+        blob_service_addr: String,
+
+        #[arg(long, env, default_value = "grpc+http://[::1]:8000")]
+        directory_service_addr: String,
+
+        #[arg(long, env, default_value = "dummy://")]
+        build_service_addr: String,
+    },
+}
+
+#[tokio::main]
+async fn main() -> Result<(), Box<dyn std::error::Error>> {
+    let cli = Cli::parse();
+
+    // configure log settings
+    let level = cli.log_level.unwrap_or(Level::INFO);
+
+    let subscriber = tracing_subscriber::registry()
+        .with(
+            cli.json.then_some(
+                tracing_subscriber::fmt::Layer::new()
+                    .with_writer(std::io::stderr.with_max_level(level))
+                    .json(),
+            ),
+        )
+        .with(
+            (!cli.json).then_some(
+                tracing_subscriber::fmt::Layer::new()
+                    .with_writer(std::io::stderr.with_max_level(level))
+                    .pretty(),
+            ),
+        );
+
+    tracing::subscriber::set_global_default(subscriber).expect("Unable to set global subscriber");
+
+    match cli.command {
+        Commands::Daemon {
+            listen_address,
+            blob_service_addr,
+            directory_service_addr,
+            build_service_addr,
+        } => {
+            // initialize stores
+            let blob_service = blobservice::from_addr(&blob_service_addr).await?;
+            let directory_service = directoryservice::from_addr(&directory_service_addr).await?;
+
+            let build_service = buildservice::from_addr(
+                &build_service_addr,
+                Arc::from(blob_service),
+                Arc::from(directory_service),
+            )
+            .await?;
+
+            let listen_address = listen_address
+                .unwrap_or_else(|| "[::]:8000".to_string())
+                .parse()
+                .unwrap();
+
+            let mut server = Server::builder();
+
+            #[allow(unused_mut)]
+            let mut router = server.add_service(BuildServiceServer::new(
+                GRPCBuildServiceWrapper::new(build_service),
+            ));
+
+            #[cfg(feature = "tonic-reflection")]
+            {
+                let reflection_svc = tonic_reflection::server::Builder::configure()
+                    .register_encoded_file_descriptor_set(CASTORE_FILE_DESCRIPTOR_SET)
+                    .register_encoded_file_descriptor_set(FILE_DESCRIPTOR_SET)
+                    .build()?;
+                router = router.add_service(reflection_svc);
+            }
+
+            info!(listen_address=%listen_address, "listening");
+
+            let listener = Listener::bind(
+                &listen_address,
+                &SystemOptions::default(),
+                &UserOptions::default(),
+            )
+            .await?;
+
+            router.serve_with_incoming(listener).await?;
+        }
+    }
+
+    Ok(())
+}
diff --git a/tvix/build/src/buildservice/dummy.rs b/tvix/build/src/buildservice/dummy.rs
new file mode 100644
index 0000000000..d20444755e
--- /dev/null
+++ b/tvix/build/src/buildservice/dummy.rs
@@ -0,0 +1,19 @@
+use tonic::async_trait;
+use tracing::instrument;
+
+use super::BuildService;
+use crate::proto::{Build, BuildRequest};
+
+#[derive(Default)]
+pub struct DummyBuildService {}
+
+#[async_trait]
+impl BuildService for DummyBuildService {
+    #[instrument(skip(self), ret, err)]
+    async fn do_build(&self, _request: BuildRequest) -> std::io::Result<Build> {
+        Err(std::io::Error::new(
+            std::io::ErrorKind::Other,
+            "builds are not supported with DummyBuildService",
+        ))
+    }
+}
diff --git a/tvix/build/src/buildservice/from_addr.rs b/tvix/build/src/buildservice/from_addr.rs
new file mode 100644
index 0000000000..cc5403edef
--- /dev/null
+++ b/tvix/build/src/buildservice/from_addr.rs
@@ -0,0 +1,90 @@
+use super::{grpc::GRPCBuildService, BuildService, DummyBuildService};
+use tvix_castore::{blobservice::BlobService, directoryservice::DirectoryService};
+use url::Url;
+
+/// Constructs a new instance of a [BuildService] from an URI.
+///
+/// The following schemes are supported by the following services:
+/// - `dummy://` ([DummyBuildService])
+/// - `grpc+*://` ([GRPCBuildService])
+///
+/// As some of these [BuildService] need to talk to a [BlobService] and
+/// [DirectoryService], these also need to be passed in.
+pub async fn from_addr<BS, DS>(
+    uri: &str,
+    _blob_service: BS,
+    _directory_service: DS,
+) -> std::io::Result<Box<dyn BuildService>>
+where
+    BS: AsRef<dyn BlobService> + Send + Sync + Clone + 'static,
+    DS: AsRef<dyn DirectoryService> + Send + Sync + Clone + 'static,
+{
+    let url = Url::parse(uri)
+        .map_err(|e| std::io::Error::other(format!("unable to parse url: {}", e)))?;
+
+    Ok(match url.scheme() {
+        // dummy doesn't care about parameters.
+        "dummy" => Box::<DummyBuildService>::default(),
+        scheme => {
+            if scheme.starts_with("grpc+") {
+                let client = crate::proto::build_service_client::BuildServiceClient::new(
+                    tvix_castore::tonic::channel_from_url(&url)
+                        .await
+                        .map_err(std::io::Error::other)?,
+                );
+                // FUTUREWORK: also allow responding to {blob,directory}_service
+                // requests from the remote BuildService?
+                Box::new(GRPCBuildService::from_client(client))
+            } else {
+                Err(std::io::Error::other(format!(
+                    "unknown scheme: {}",
+                    url.scheme()
+                )))?
+            }
+        }
+    })
+}
+
+#[cfg(test)]
+mod tests {
+    use std::sync::Arc;
+
+    use super::from_addr;
+    use rstest::rstest;
+    use tvix_castore::{
+        blobservice::{BlobService, MemoryBlobService},
+        directoryservice::{DirectoryService, MemoryDirectoryService},
+    };
+
+    #[rstest]
+    /// This uses an unsupported scheme.
+    #[case::unsupported_scheme("http://foo.example/test", false)]
+    /// This configures dummy
+    #[case::valid_dummy("dummy://", true)]
+    /// Correct scheme to connect to a unix socket.
+    #[case::grpc_valid_unix_socket("grpc+unix:///path/to/somewhere", true)]
+    /// Correct scheme for unix socket, but setting a host too, which is invalid.
+    #[case::grpc_invalid_unix_socket_and_host("grpc+unix://host.example/path/to/somewhere", false)]
+    /// Correct scheme to connect to localhost, with port 12345
+    #[case::grpc_valid_ipv6_localhost_port_12345("grpc+http://[::1]:12345", true)]
+    /// Correct scheme to connect to localhost over http, without specifying a port.
+    #[case::grpc_valid_http_host_without_port("grpc+http://localhost", true)]
+    /// Correct scheme to connect to localhost over http, without specifying a port.
+    #[case::grpc_valid_https_host_without_port("grpc+https://localhost", true)]
+    /// Correct scheme to connect to localhost over http, but with additional path, which is invalid.
+    #[case::grpc_invalid_host_and_path("grpc+http://localhost/some-path", false)]
+    #[tokio::test]
+    async fn test_from_addr(#[case] uri_str: &str, #[case] exp_succeed: bool) {
+        let blob_service: Arc<dyn BlobService> = Arc::from(MemoryBlobService::default());
+        let directory_service: Arc<dyn DirectoryService> =
+            Arc::from(MemoryDirectoryService::default());
+
+        let resp = from_addr(uri_str, blob_service, directory_service).await;
+
+        if exp_succeed {
+            resp.expect("should succeed");
+        } else {
+            assert!(resp.is_err(), "should fail");
+        }
+    }
+}
diff --git a/tvix/build/src/buildservice/grpc.rs b/tvix/build/src/buildservice/grpc.rs
new file mode 100644
index 0000000000..9d22d8397a
--- /dev/null
+++ b/tvix/build/src/buildservice/grpc.rs
@@ -0,0 +1,28 @@
+use tonic::{async_trait, transport::Channel};
+
+use crate::proto::{build_service_client::BuildServiceClient, Build, BuildRequest};
+
+use super::BuildService;
+
+pub struct GRPCBuildService {
+    client: BuildServiceClient<Channel>,
+}
+
+impl GRPCBuildService {
+    #[allow(dead_code)]
+    pub fn from_client(client: BuildServiceClient<Channel>) -> Self {
+        Self { client }
+    }
+}
+
+#[async_trait]
+impl BuildService for GRPCBuildService {
+    async fn do_build(&self, request: BuildRequest) -> std::io::Result<Build> {
+        let mut client = self.client.clone();
+        client
+            .do_build(request)
+            .await
+            .map(|resp| resp.into_inner())
+            .map_err(std::io::Error::other)
+    }
+}
diff --git a/tvix/build/src/buildservice/mod.rs b/tvix/build/src/buildservice/mod.rs
new file mode 100644
index 0000000000..a61d782919
--- /dev/null
+++ b/tvix/build/src/buildservice/mod.rs
@@ -0,0 +1,16 @@
+use tonic::async_trait;
+
+use crate::proto::{Build, BuildRequest};
+
+mod dummy;
+mod from_addr;
+mod grpc;
+
+pub use dummy::DummyBuildService;
+pub use from_addr::from_addr;
+
+#[async_trait]
+pub trait BuildService: Send + Sync {
+    /// TODO: document
+    async fn do_build(&self, request: BuildRequest) -> std::io::Result<Build>;
+}
diff --git a/tvix/build/src/lib.rs b/tvix/build/src/lib.rs
new file mode 100644
index 0000000000..b173657e43
--- /dev/null
+++ b/tvix/build/src/lib.rs
@@ -0,0 +1,2 @@
+pub mod buildservice;
+pub mod proto;
diff --git a/tvix/build/src/proto/grpc_buildservice_wrapper.rs b/tvix/build/src/proto/grpc_buildservice_wrapper.rs
new file mode 100644
index 0000000000..024f075de9
--- /dev/null
+++ b/tvix/build/src/proto/grpc_buildservice_wrapper.rs
@@ -0,0 +1,35 @@
+use crate::buildservice::BuildService;
+use std::ops::Deref;
+use tonic::async_trait;
+
+use super::{Build, BuildRequest};
+
+/// Implements the gRPC server trait ([crate::proto::build_service_server::BuildService]
+/// for anything implementing [BuildService].
+pub struct GRPCBuildServiceWrapper<BUILD> {
+    inner: BUILD,
+}
+
+impl<BUILD> GRPCBuildServiceWrapper<BUILD> {
+    pub fn new(build_service: BUILD) -> Self {
+        Self {
+            inner: build_service,
+        }
+    }
+}
+
+#[async_trait]
+impl<BUILD> crate::proto::build_service_server::BuildService for GRPCBuildServiceWrapper<BUILD>
+where
+    BUILD: Deref<Target = dyn BuildService> + Send + Sync + 'static,
+{
+    async fn do_build(
+        &self,
+        request: tonic::Request<BuildRequest>,
+    ) -> Result<tonic::Response<Build>, tonic::Status> {
+        match self.inner.do_build(request.into_inner()).await {
+            Ok(resp) => Ok(tonic::Response::new(resp)),
+            Err(e) => Err(tonic::Status::internal(e.to_string())),
+        }
+    }
+}
diff --git a/tvix/build/src/proto/mod.rs b/tvix/build/src/proto/mod.rs
new file mode 100644
index 0000000000..e359b5b5b7
--- /dev/null
+++ b/tvix/build/src/proto/mod.rs
@@ -0,0 +1,264 @@
+use std::path::{Path, PathBuf};
+
+use itertools::Itertools;
+use tvix_castore::proto::{NamedNode, ValidateNodeError};
+
+mod grpc_buildservice_wrapper;
+
+pub use grpc_buildservice_wrapper::GRPCBuildServiceWrapper;
+
+tonic::include_proto!("tvix.build.v1");
+
+#[cfg(feature = "tonic-reflection")]
+/// Compiled file descriptors for implementing [gRPC
+/// reflection](https://github.com/grpc/grpc/blob/master/doc/server-reflection.md) with e.g.
+/// [`tonic_reflection`](https://docs.rs/tonic-reflection).
+pub const FILE_DESCRIPTOR_SET: &[u8] = tonic::include_file_descriptor_set!("tvix.build.v1");
+
+/// Errors that occur during the validation of [BuildRequest] messages.
+#[derive(Debug, thiserror::Error)]
+pub enum ValidateBuildRequestError {
+    #[error("invalid input node at position {0}: {1}")]
+    InvalidInputNode(usize, ValidateNodeError),
+
+    #[error("input nodes are not sorted by name")]
+    InputNodesNotSorted,
+
+    #[error("invalid working_dir")]
+    InvalidWorkingDir,
+
+    #[error("scratch_paths not sorted")]
+    ScratchPathsNotSorted,
+
+    #[error("invalid scratch path at position {0}")]
+    InvalidScratchPath(usize),
+
+    #[error("invalid inputs_dir")]
+    InvalidInputsDir,
+
+    #[error("invalid output path at position {0}")]
+    InvalidOutputPath(usize),
+
+    #[error("outputs not sorted")]
+    OutputsNotSorted,
+
+    #[error("invalid environment variable at position {0}")]
+    InvalidEnvVar(usize),
+
+    #[error("EnvVar not sorted by their keys")]
+    EnvVarNotSorted,
+
+    #[error("invalid build constraints: {0}")]
+    InvalidBuildConstraints(ValidateBuildConstraintsError),
+
+    #[error("invalid additional file path at position: {0}")]
+    InvalidAdditionalFilePath(usize),
+
+    #[error("additional_files not sorted")]
+    AdditionalFilesNotSorted,
+}
+
+/// Checks a path to be without any '..' components, and clean (no superfluous
+/// slashes).
+fn is_clean_path<P: AsRef<Path>>(p: P) -> bool {
+    let p = p.as_ref();
+
+    // Look at all components, bail in case of ".", ".." and empty normal
+    // segments (superfluous slashes)
+    // We still need to assemble a cleaned PathBuf, and compare the OsString
+    // later, as .components() already does do some normalization before
+    // yielding.
+    let mut cleaned_p = PathBuf::new();
+    for component in p.components() {
+        match component {
+            std::path::Component::Prefix(_) => {}
+            std::path::Component::RootDir => {}
+            std::path::Component::CurDir => return false,
+            std::path::Component::ParentDir => return false,
+            std::path::Component::Normal(a) => {
+                if a.is_empty() {
+                    return false;
+                }
+            }
+        }
+        cleaned_p.push(component);
+    }
+
+    // if cleaned_p looks like p, we're good.
+    if cleaned_p.as_os_str() != p.as_os_str() {
+        return false;
+    }
+
+    true
+}
+
+fn is_clean_relative_path<P: AsRef<Path>>(p: P) -> bool {
+    if p.as_ref().is_absolute() {
+        return false;
+    }
+
+    is_clean_path(p)
+}
+
+fn is_clean_absolute_path<P: AsRef<Path>>(p: P) -> bool {
+    if !p.as_ref().is_absolute() {
+        return false;
+    }
+
+    is_clean_path(p)
+}
+
+/// Checks if a given list is sorted.
+fn is_sorted<I>(data: I) -> bool
+where
+    I: Iterator,
+    I::Item: Ord + Clone,
+{
+    data.tuple_windows().all(|(a, b)| a <= b)
+}
+
+impl BuildRequest {
+    /// Ensures the build request is valid.
+    /// This means, all input nodes need to be valid, paths in lists need to be sorted,
+    /// and all restrictions around paths themselves (relative, clean, …) need
+    // to be fulfilled.
+    pub fn validate(&self) -> Result<(), ValidateBuildRequestError> {
+        // validate all input nodes
+        for (i, n) in self.inputs.iter().enumerate() {
+            // ensure the input node itself is valid
+            n.validate()
+                .map_err(|e| ValidateBuildRequestError::InvalidInputNode(i, e))?;
+        }
+
+        // now we can look at the names, and make sure they're sorted.
+        if !is_sorted(
+            self.inputs
+                .iter()
+                .map(|e| e.node.as_ref().unwrap().get_name()),
+        ) {
+            Err(ValidateBuildRequestError::InputNodesNotSorted)?
+        }
+
+        // validate working_dir
+        if !is_clean_relative_path(&self.working_dir) {
+            Err(ValidateBuildRequestError::InvalidWorkingDir)?;
+        }
+
+        // validate scratch paths
+        for (i, p) in self.scratch_paths.iter().enumerate() {
+            if !is_clean_relative_path(p) {
+                Err(ValidateBuildRequestError::InvalidScratchPath(i))?
+            }
+        }
+        if !is_sorted(self.scratch_paths.iter().map(|e| e.as_bytes())) {
+            Err(ValidateBuildRequestError::ScratchPathsNotSorted)?;
+        }
+
+        // validate inputs_dir
+        if !is_clean_relative_path(&self.inputs_dir) {
+            Err(ValidateBuildRequestError::InvalidInputsDir)?;
+        }
+
+        // validate outputs
+        for (i, p) in self.outputs.iter().enumerate() {
+            if !is_clean_relative_path(p) {
+                Err(ValidateBuildRequestError::InvalidOutputPath(i))?
+            }
+        }
+        if !is_sorted(self.outputs.iter().map(|e| e.as_bytes())) {
+            Err(ValidateBuildRequestError::OutputsNotSorted)?;
+        }
+
+        // validate environment_vars.
+        for (i, e) in self.environment_vars.iter().enumerate() {
+            if e.key.is_empty() || e.key.contains('=') {
+                Err(ValidateBuildRequestError::InvalidEnvVar(i))?
+            }
+        }
+        if !is_sorted(self.environment_vars.iter().map(|e| e.key.as_bytes())) {
+            Err(ValidateBuildRequestError::EnvVarNotSorted)?;
+        }
+
+        // validate build constraints
+        if let Some(constraints) = self.constraints.as_ref() {
+            constraints
+                .validate()
+                .map_err(ValidateBuildRequestError::InvalidBuildConstraints)?;
+        }
+
+        // validate additional_files
+        for (i, additional_file) in self.additional_files.iter().enumerate() {
+            if !is_clean_relative_path(&additional_file.path) {
+                Err(ValidateBuildRequestError::InvalidAdditionalFilePath(i))?
+            }
+        }
+        if !is_sorted(self.additional_files.iter().map(|e| e.path.as_bytes())) {
+            Err(ValidateBuildRequestError::AdditionalFilesNotSorted)?;
+        }
+
+        Ok(())
+    }
+}
+
+/// Errors that occur during the validation of
+/// [build_request::BuildConstraints] messages.
+#[derive(Debug, thiserror::Error)]
+pub enum ValidateBuildConstraintsError {
+    #[error("invalid system")]
+    InvalidSystem,
+
+    #[error("invalid available_ro_paths at position {0}")]
+    InvalidAvailableRoPaths(usize),
+
+    #[error("available_ro_paths not sorted")]
+    AvailableRoPathsNotSorted,
+}
+
+impl build_request::BuildConstraints {
+    pub fn validate(&self) -> Result<(), ValidateBuildConstraintsError> {
+        // validate system
+        if self.system.is_empty() {
+            Err(ValidateBuildConstraintsError::InvalidSystem)?;
+        }
+        // validate available_ro_paths
+        for (i, p) in self.available_ro_paths.iter().enumerate() {
+            if !is_clean_absolute_path(p) {
+                Err(ValidateBuildConstraintsError::InvalidAvailableRoPaths(i))?
+            }
+        }
+        if !is_sorted(self.available_ro_paths.iter().map(|e| e.as_bytes())) {
+            Err(ValidateBuildConstraintsError::AvailableRoPathsNotSorted)?;
+        }
+
+        Ok(())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::{is_clean_path, is_clean_relative_path};
+    use rstest::rstest;
+
+    #[rstest]
+    #[case::fail_trailing_slash("foo/bar/", false)]
+    #[case::fail_dotdot("foo/../bar", false)]
+    #[case::fail_singledot("foo/./bar", false)]
+    #[case::fail_unnecessary_slashes("foo//bar", false)]
+    #[case::fail_absolute_unnecessary_slashes("//foo/bar", false)]
+    #[case::ok_empty("", true)]
+    #[case::ok_relative("foo/bar", true)]
+    #[case::ok_absolute("/", true)]
+    #[case::ok_absolute2("/foo/bar", true)]
+    fn test_is_clean_path(#[case] s: &str, #[case] expected: bool) {
+        assert_eq!(is_clean_path(s), expected);
+    }
+
+    #[rstest]
+    #[case::fail_absolute("/", false)]
+    #[case::ok_relative("foo/bar", true)]
+    fn test_is_clean_relative_path(#[case] s: &str, #[case] expected: bool) {
+        assert_eq!(is_clean_relative_path(s), expected);
+    }
+
+    // TODO: add tests for BuildRequest validation itself
+}
diff --git a/tvix/buildkite.yml b/tvix/buildkite.yml
new file mode 100644
index 0000000000..2a24ca4264
--- /dev/null
+++ b/tvix/buildkite.yml
@@ -0,0 +1,10 @@
+# Build pipeline for the filtered //views/tvix workspace of depot. This
+# pipeline is triggered by each build of canon.
+#
+# Pipeline status is visible on https://buildkite.com/tvl/tvix
+
+steps:
+  - label: ":crab: cargo build"
+    command: |
+      nix-shell --run "cargo build && cargo test"
+    timeout_in_minutes: 10
diff --git a/tvix/castore-go/LICENSE b/tvix/castore-go/LICENSE
new file mode 100644
index 0000000000..2034ada6fd
--- /dev/null
+++ b/tvix/castore-go/LICENSE
@@ -0,0 +1,21 @@
+Copyright © The Tvix Authors
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+“Software”), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
diff --git a/tvix/castore-go/README.md b/tvix/castore-go/README.md
new file mode 100644
index 0000000000..c7a2205ae8
--- /dev/null
+++ b/tvix/castore-go/README.md
@@ -0,0 +1,10 @@
+# castore-go
+
+This directory contains generated golang bindings, both for the `tvix-castore`
+data models, as well as the gRPC bindings.
+
+They are generated with `mg run //tvix:castore-go:regenerate`.
+These files end with `.pb.go`, and are ensured to be up to date by a CI check.
+
+Additionally, code useful when interacting with these data structures
+(ending just with `.go`) is provided.
diff --git a/tvix/castore-go/castore.go b/tvix/castore-go/castore.go
new file mode 100644
index 0000000000..40aeaf1911
--- /dev/null
+++ b/tvix/castore-go/castore.go
@@ -0,0 +1,212 @@
+package castorev1
+
+import (
+	"bytes"
+	"encoding/base64"
+	"fmt"
+
+	"google.golang.org/protobuf/proto"
+	"lukechampine.com/blake3"
+)
+
+// The size of a directory is calculated by summing up the numbers of
+// `directories`, `files` and `symlinks`, and for each directory, its size
+// field.
+func (d *Directory) Size() uint64 {
+	var size uint64
+	size = uint64(len(d.Files) + len(d.Symlinks))
+	for _, d := range d.Directories {
+		size += 1 + d.Size
+	}
+	return size
+}
+
+func (d *Directory) Digest() ([]byte, error) {
+	b, err := proto.MarshalOptions{
+		Deterministic: true,
+	}.Marshal(d)
+
+	if err != nil {
+		return nil, fmt.Errorf("error while marshalling directory: %w", err)
+	}
+
+	h := blake3.New(32, nil)
+
+	_, err = h.Write(b)
+	if err != nil {
+		return nil, fmt.Errorf("error writing to hasher: %w", err)
+	}
+
+	return h.Sum(nil), nil
+}
+
+// isValidName checks a name for validity.
+// We disallow slashes, null bytes, '.', '..' and the empty string.
+// Depending on the context, a *Node message with an empty string as name is
+// allowed, but they don't occur inside a Directory message.
+func isValidName(n []byte) bool {
+	if len(n) == 0 || bytes.Equal(n, []byte("..")) || bytes.Equal(n, []byte{'.'}) || bytes.Contains(n, []byte{'\x00'}) || bytes.Contains(n, []byte{'/'}) {
+		return false
+	}
+	return true
+}
+
+// Validate ensures a DirectoryNode has a valid name and correct digest len.
+func (n *DirectoryNode) Validate() error {
+	if len(n.Digest) != 32 {
+		return fmt.Errorf("invalid digest length for %s, expected %d, got %d", n.Name, 32, len(n.Digest))
+	}
+
+	if !isValidName(n.Name) {
+		return fmt.Errorf("invalid node name: %s", n.Name)
+	}
+
+	return nil
+}
+
+// Validate ensures a FileNode has a valid name and correct digest len.
+func (n *FileNode) Validate() error {
+	if len(n.Digest) != 32 {
+		return fmt.Errorf("invalid digest length for %s, expected %d, got %d", n.Name, 32, len(n.Digest))
+	}
+
+	if !isValidName(n.Name) {
+		return fmt.Errorf("invalid node name: %s", n.Name)
+	}
+
+	return nil
+}
+
+// Validate ensures a SymlinkNode has a valid name and target.
+func (n *SymlinkNode) Validate() error {
+	if len(n.Target) == 0 || bytes.Contains(n.Target, []byte{0}) {
+		return fmt.Errorf("invalid symlink target: %s", n.Target)
+	}
+
+	if !isValidName(n.Name) {
+		return fmt.Errorf("invalid node name: %s", n.Name)
+	}
+
+	return nil
+}
+
+// Validate ensures a node is valid, by dispatching to the per-type validation functions.
+func (n *Node) Validate() error {
+	if node := n.GetDirectory(); node != nil {
+		if err := node.Validate(); err != nil {
+			return fmt.Errorf("SymlinkNode failed validation: %w", err)
+		}
+	} else if node := n.GetFile(); node != nil {
+		if err := node.Validate(); err != nil {
+			return fmt.Errorf("FileNode failed validation: %w", err)
+		}
+	} else if node := n.GetSymlink(); node != nil {
+		if err := node.Validate(); err != nil {
+			return fmt.Errorf("SymlinkNode failed validation: %w", err)
+		}
+
+	} else {
+		// this would only happen if we introduced a new type
+		return fmt.Errorf("no specific node found")
+	}
+
+	return nil
+}
+
+// Validate thecks the Directory message for invalid data, such as:
+// - violations of name restrictions
+// - invalid digest lengths
+// - not properly sorted lists
+// - duplicate names in the three lists
+func (d *Directory) Validate() error {
+	// seenNames contains all seen names so far.
+	// We populate this to ensure node names are unique across all three lists.
+	seenNames := make(map[string]interface{})
+
+	// We also track the last seen name in each of the three lists,
+	// to ensure nodes are sorted by their names.
+	var lastDirectoryName, lastFileName, lastSymlinkName []byte
+
+	// helper function to only insert in sorted order.
+	// used with the three lists above.
+	// Note this consumes a *pointer to* a string,  as it mutates it.
+	insertIfGt := func(lastName *[]byte, name []byte) error {
+		// update if it's greater than the previous name
+		if bytes.Compare(name, *lastName) == 1 {
+			*lastName = name
+			return nil
+		} else {
+			return fmt.Errorf("%v is not in sorted order", name)
+		}
+	}
+
+	// insertOnce inserts into seenNames if the key doesn't exist yet.
+	insertOnce := func(name []byte) error {
+		encoded := base64.StdEncoding.EncodeToString(name)
+		if _, found := seenNames[encoded]; found {
+			return fmt.Errorf("duplicate name: %v", string(name))
+		}
+		seenNames[encoded] = nil
+		return nil
+	}
+
+	// Loop over all Directories, Files and Symlinks individually,
+	// check them for validity, then check for sorting in the current list, and
+	// uniqueness across all three lists.
+	for _, directoryNode := range d.Directories {
+		directoryName := directoryNode.GetName()
+
+		if err := directoryNode.Validate(); err != nil {
+			return fmt.Errorf("DirectoryNode %s failed validation: %w", directoryName, err)
+		}
+
+		// ensure names are sorted
+		if err := insertIfGt(&lastDirectoryName, directoryName); err != nil {
+			return err
+		}
+
+		// add to seenNames
+		if err := insertOnce(directoryName); err != nil {
+			return err
+		}
+
+	}
+
+	for _, fileNode := range d.Files {
+		fileName := fileNode.GetName()
+
+		if err := fileNode.Validate(); err != nil {
+			return fmt.Errorf("FileNode %s failed validation: %w", fileName, err)
+		}
+
+		// ensure names are sorted
+		if err := insertIfGt(&lastFileName, fileName); err != nil {
+			return err
+		}
+
+		// add to seenNames
+		if err := insertOnce(fileName); err != nil {
+			return err
+		}
+	}
+
+	for _, symlinkNode := range d.Symlinks {
+		symlinkName := symlinkNode.GetName()
+
+		if err := symlinkNode.Validate(); err != nil {
+			return fmt.Errorf("SymlinkNode %s failed validation: %w", symlinkName, err)
+		}
+
+		// ensure names are sorted
+		if err := insertIfGt(&lastSymlinkName, symlinkName); err != nil {
+			return err
+		}
+
+		// add to seenNames
+		if err := insertOnce(symlinkName); err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
diff --git a/tvix/castore-go/castore.pb.go b/tvix/castore-go/castore.pb.go
new file mode 100644
index 0000000000..464f1d4a41
--- /dev/null
+++ b/tvix/castore-go/castore.pb.go
@@ -0,0 +1,580 @@
+// SPDX-FileCopyrightText: edef <edef@unfathomable.blue>
+// SPDX-License-Identifier: OSL-3.0 OR MIT OR Apache-2.0
+
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// 	protoc-gen-go v1.33.0
+// 	protoc        (unknown)
+// source: tvix/castore/protos/castore.proto
+
+package castorev1
+
+import (
+	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+	reflect "reflect"
+	sync "sync"
+)
+
+const (
+	// Verify that this generated code is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+	// Verify that runtime/protoimpl is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+// A Directory can contain Directory, File or Symlink nodes.
+// Each of these nodes have a name attribute, which is the basename in that
+// directory and node type specific attributes.
+// The name attribute:
+//   - MUST not contain slashes or null bytes
+//   - MUST not be '.' or '..'
+//   - MUST be unique across all three lists
+//
+// Elements in each list need to be lexicographically ordered by the name
+// attribute.
+type Directory struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Directories []*DirectoryNode `protobuf:"bytes,1,rep,name=directories,proto3" json:"directories,omitempty"`
+	Files       []*FileNode      `protobuf:"bytes,2,rep,name=files,proto3" json:"files,omitempty"`
+	Symlinks    []*SymlinkNode   `protobuf:"bytes,3,rep,name=symlinks,proto3" json:"symlinks,omitempty"`
+}
+
+func (x *Directory) Reset() {
+	*x = Directory{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_tvix_castore_protos_castore_proto_msgTypes[0]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *Directory) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Directory) ProtoMessage() {}
+
+func (x *Directory) ProtoReflect() protoreflect.Message {
+	mi := &file_tvix_castore_protos_castore_proto_msgTypes[0]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use Directory.ProtoReflect.Descriptor instead.
+func (*Directory) Descriptor() ([]byte, []int) {
+	return file_tvix_castore_protos_castore_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *Directory) GetDirectories() []*DirectoryNode {
+	if x != nil {
+		return x.Directories
+	}
+	return nil
+}
+
+func (x *Directory) GetFiles() []*FileNode {
+	if x != nil {
+		return x.Files
+	}
+	return nil
+}
+
+func (x *Directory) GetSymlinks() []*SymlinkNode {
+	if x != nil {
+		return x.Symlinks
+	}
+	return nil
+}
+
+// A DirectoryNode represents a directory in a Directory.
+type DirectoryNode struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// The (base)name of the directory
+	Name []byte `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
+	// The blake3 hash of a Directory message, serialized in protobuf canonical form.
+	Digest []byte `protobuf:"bytes,2,opt,name=digest,proto3" json:"digest,omitempty"`
+	// Number of child elements in the Directory referred to by `digest`.
+	// Calculated by summing up the numbers of `directories`, `files` and
+	// `symlinks`, and for each directory, its size field. Used for inode number
+	// calculation.
+	// This field is precisely as verifiable as any other Merkle tree edge.
+	// Resolve `digest`, and you can compute it incrementally. Resolve the entire
+	// tree, and you can fully compute it from scratch.
+	// A credulous implementation won't reject an excessive size, but this is
+	// harmless: you'll have some ordinals without nodes. Undersizing is obvious
+	// and easy to reject: you won't have an ordinal for some nodes.
+	Size uint64 `protobuf:"varint,3,opt,name=size,proto3" json:"size,omitempty"`
+}
+
+func (x *DirectoryNode) Reset() {
+	*x = DirectoryNode{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_tvix_castore_protos_castore_proto_msgTypes[1]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *DirectoryNode) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*DirectoryNode) ProtoMessage() {}
+
+func (x *DirectoryNode) ProtoReflect() protoreflect.Message {
+	mi := &file_tvix_castore_protos_castore_proto_msgTypes[1]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use DirectoryNode.ProtoReflect.Descriptor instead.
+func (*DirectoryNode) Descriptor() ([]byte, []int) {
+	return file_tvix_castore_protos_castore_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *DirectoryNode) GetName() []byte {
+	if x != nil {
+		return x.Name
+	}
+	return nil
+}
+
+func (x *DirectoryNode) GetDigest() []byte {
+	if x != nil {
+		return x.Digest
+	}
+	return nil
+}
+
+func (x *DirectoryNode) GetSize() uint64 {
+	if x != nil {
+		return x.Size
+	}
+	return 0
+}
+
+// A FileNode represents a regular or executable file in a Directory.
+type FileNode struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// The (base)name of the file
+	Name []byte `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
+	// The blake3 digest of the file contents
+	Digest []byte `protobuf:"bytes,2,opt,name=digest,proto3" json:"digest,omitempty"`
+	// The file content size
+	Size uint64 `protobuf:"varint,3,opt,name=size,proto3" json:"size,omitempty"`
+	// Whether the file is executable
+	Executable bool `protobuf:"varint,4,opt,name=executable,proto3" json:"executable,omitempty"`
+}
+
+func (x *FileNode) Reset() {
+	*x = FileNode{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_tvix_castore_protos_castore_proto_msgTypes[2]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *FileNode) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*FileNode) ProtoMessage() {}
+
+func (x *FileNode) ProtoReflect() protoreflect.Message {
+	mi := &file_tvix_castore_protos_castore_proto_msgTypes[2]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use FileNode.ProtoReflect.Descriptor instead.
+func (*FileNode) Descriptor() ([]byte, []int) {
+	return file_tvix_castore_protos_castore_proto_rawDescGZIP(), []int{2}
+}
+
+func (x *FileNode) GetName() []byte {
+	if x != nil {
+		return x.Name
+	}
+	return nil
+}
+
+func (x *FileNode) GetDigest() []byte {
+	if x != nil {
+		return x.Digest
+	}
+	return nil
+}
+
+func (x *FileNode) GetSize() uint64 {
+	if x != nil {
+		return x.Size
+	}
+	return 0
+}
+
+func (x *FileNode) GetExecutable() bool {
+	if x != nil {
+		return x.Executable
+	}
+	return false
+}
+
+// A SymlinkNode represents a symbolic link in a Directory.
+type SymlinkNode struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// The (base)name of the symlink
+	Name []byte `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
+	// The target of the symlink.
+	Target []byte `protobuf:"bytes,2,opt,name=target,proto3" json:"target,omitempty"`
+}
+
+func (x *SymlinkNode) Reset() {
+	*x = SymlinkNode{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_tvix_castore_protos_castore_proto_msgTypes[3]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *SymlinkNode) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*SymlinkNode) ProtoMessage() {}
+
+func (x *SymlinkNode) ProtoReflect() protoreflect.Message {
+	mi := &file_tvix_castore_protos_castore_proto_msgTypes[3]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use SymlinkNode.ProtoReflect.Descriptor instead.
+func (*SymlinkNode) Descriptor() ([]byte, []int) {
+	return file_tvix_castore_protos_castore_proto_rawDescGZIP(), []int{3}
+}
+
+func (x *SymlinkNode) GetName() []byte {
+	if x != nil {
+		return x.Name
+	}
+	return nil
+}
+
+func (x *SymlinkNode) GetTarget() []byte {
+	if x != nil {
+		return x.Target
+	}
+	return nil
+}
+
+// A Node is either a DirectoryNode, FileNode or SymlinkNode.
+type Node struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// Types that are assignable to Node:
+	//
+	//	*Node_Directory
+	//	*Node_File
+	//	*Node_Symlink
+	Node isNode_Node `protobuf_oneof:"node"`
+}
+
+func (x *Node) Reset() {
+	*x = Node{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_tvix_castore_protos_castore_proto_msgTypes[4]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *Node) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Node) ProtoMessage() {}
+
+func (x *Node) ProtoReflect() protoreflect.Message {
+	mi := &file_tvix_castore_protos_castore_proto_msgTypes[4]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use Node.ProtoReflect.Descriptor instead.
+func (*Node) Descriptor() ([]byte, []int) {
+	return file_tvix_castore_protos_castore_proto_rawDescGZIP(), []int{4}
+}
+
+func (m *Node) GetNode() isNode_Node {
+	if m != nil {
+		return m.Node
+	}
+	return nil
+}
+
+func (x *Node) GetDirectory() *DirectoryNode {
+	if x, ok := x.GetNode().(*Node_Directory); ok {
+		return x.Directory
+	}
+	return nil
+}
+
+func (x *Node) GetFile() *FileNode {
+	if x, ok := x.GetNode().(*Node_File); ok {
+		return x.File
+	}
+	return nil
+}
+
+func (x *Node) GetSymlink() *SymlinkNode {
+	if x, ok := x.GetNode().(*Node_Symlink); ok {
+		return x.Symlink
+	}
+	return nil
+}
+
+type isNode_Node interface {
+	isNode_Node()
+}
+
+type Node_Directory struct {
+	Directory *DirectoryNode `protobuf:"bytes,1,opt,name=directory,proto3,oneof"`
+}
+
+type Node_File struct {
+	File *FileNode `protobuf:"bytes,2,opt,name=file,proto3,oneof"`
+}
+
+type Node_Symlink struct {
+	Symlink *SymlinkNode `protobuf:"bytes,3,opt,name=symlink,proto3,oneof"`
+}
+
+func (*Node_Directory) isNode_Node() {}
+
+func (*Node_File) isNode_Node() {}
+
+func (*Node_Symlink) isNode_Node() {}
+
+var File_tvix_castore_protos_castore_proto protoreflect.FileDescriptor
+
+var file_tvix_castore_protos_castore_proto_rawDesc = []byte{
+	0x0a, 0x21, 0x74, 0x76, 0x69, 0x78, 0x2f, 0x63, 0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2f, 0x70,
+	0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x63, 0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72,
+	0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x74, 0x76, 0x69, 0x78, 0x2e, 0x63, 0x61, 0x73, 0x74, 0x6f, 0x72,
+	0x65, 0x2e, 0x76, 0x31, 0x22, 0xb8, 0x01, 0x0a, 0x09, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f,
+	0x72, 0x79, 0x12, 0x40, 0x0a, 0x0b, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x69, 0x65,
+	0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x74, 0x76, 0x69, 0x78, 0x2e, 0x63,
+	0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74,
+	0x6f, 0x72, 0x79, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x0b, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f,
+	0x72, 0x69, 0x65, 0x73, 0x12, 0x2f, 0x0a, 0x05, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x02, 0x20,
+	0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x74, 0x76, 0x69, 0x78, 0x2e, 0x63, 0x61, 0x73, 0x74, 0x6f,
+	0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x05,
+	0x66, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x38, 0x0a, 0x08, 0x73, 0x79, 0x6d, 0x6c, 0x69, 0x6e, 0x6b,
+	0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x74, 0x76, 0x69, 0x78, 0x2e, 0x63,
+	0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x79, 0x6d, 0x6c, 0x69, 0x6e,
+	0x6b, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x08, 0x73, 0x79, 0x6d, 0x6c, 0x69, 0x6e, 0x6b, 0x73, 0x22,
+	0x4f, 0x0a, 0x0d, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x4e, 0x6f, 0x64, 0x65,
+	0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04,
+	0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x18, 0x02,
+	0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04,
+	0x73, 0x69, 0x7a, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65,
+	0x22, 0x6a, 0x0a, 0x08, 0x46, 0x69, 0x6c, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x12, 0x12, 0x0a, 0x04,
+	0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65,
+	0x12, 0x16, 0x0a, 0x06, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c,
+	0x52, 0x06, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65,
+	0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x1e, 0x0a, 0x0a,
+	0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08,
+	0x52, 0x0a, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x22, 0x39, 0x0a, 0x0b,
+	0x53, 0x79, 0x6d, 0x6c, 0x69, 0x6e, 0x6b, 0x4e, 0x6f, 0x64, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e,
+	0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12,
+	0x16, 0x0a, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52,
+	0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x22, 0xb9, 0x01, 0x0a, 0x04, 0x4e, 0x6f, 0x64, 0x65,
+	0x12, 0x3e, 0x0a, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x01, 0x20,
+	0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x74, 0x76, 0x69, 0x78, 0x2e, 0x63, 0x61, 0x73, 0x74, 0x6f,
+	0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x4e,
+	0x6f, 0x64, 0x65, 0x48, 0x00, 0x52, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79,
+	0x12, 0x2f, 0x0a, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19,
+	0x2e, 0x74, 0x76, 0x69, 0x78, 0x2e, 0x63, 0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31,
+	0x2e, 0x46, 0x69, 0x6c, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x48, 0x00, 0x52, 0x04, 0x66, 0x69, 0x6c,
+	0x65, 0x12, 0x38, 0x0a, 0x07, 0x73, 0x79, 0x6d, 0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x03, 0x20, 0x01,
+	0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x74, 0x76, 0x69, 0x78, 0x2e, 0x63, 0x61, 0x73, 0x74, 0x6f, 0x72,
+	0x65, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x79, 0x6d, 0x6c, 0x69, 0x6e, 0x6b, 0x4e, 0x6f, 0x64, 0x65,
+	0x48, 0x00, 0x52, 0x07, 0x73, 0x79, 0x6d, 0x6c, 0x69, 0x6e, 0x6b, 0x42, 0x06, 0x0a, 0x04, 0x6e,
+	0x6f, 0x64, 0x65, 0x42, 0x28, 0x5a, 0x26, 0x63, 0x6f, 0x64, 0x65, 0x2e, 0x74, 0x76, 0x6c, 0x2e,
+	0x66, 0x79, 0x69, 0x2f, 0x74, 0x76, 0x69, 0x78, 0x2f, 0x63, 0x61, 0x73, 0x74, 0x6f, 0x72, 0x65,
+	0x2d, 0x67, 0x6f, 0x3b, 0x63, 0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x76, 0x31, 0x62, 0x06, 0x70,
+	0x72, 0x6f, 0x74, 0x6f, 0x33,
+}
+
+var (
+	file_tvix_castore_protos_castore_proto_rawDescOnce sync.Once
+	file_tvix_castore_protos_castore_proto_rawDescData = file_tvix_castore_protos_castore_proto_rawDesc
+)
+
+func file_tvix_castore_protos_castore_proto_rawDescGZIP() []byte {
+	file_tvix_castore_protos_castore_proto_rawDescOnce.Do(func() {
+		file_tvix_castore_protos_castore_proto_rawDescData = protoimpl.X.CompressGZIP(file_tvix_castore_protos_castore_proto_rawDescData)
+	})
+	return file_tvix_castore_protos_castore_proto_rawDescData
+}
+
+var file_tvix_castore_protos_castore_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
+var file_tvix_castore_protos_castore_proto_goTypes = []interface{}{
+	(*Directory)(nil),     // 0: tvix.castore.v1.Directory
+	(*DirectoryNode)(nil), // 1: tvix.castore.v1.DirectoryNode
+	(*FileNode)(nil),      // 2: tvix.castore.v1.FileNode
+	(*SymlinkNode)(nil),   // 3: tvix.castore.v1.SymlinkNode
+	(*Node)(nil),          // 4: tvix.castore.v1.Node
+}
+var file_tvix_castore_protos_castore_proto_depIdxs = []int32{
+	1, // 0: tvix.castore.v1.Directory.directories:type_name -> tvix.castore.v1.DirectoryNode
+	2, // 1: tvix.castore.v1.Directory.files:type_name -> tvix.castore.v1.FileNode
+	3, // 2: tvix.castore.v1.Directory.symlinks:type_name -> tvix.castore.v1.SymlinkNode
+	1, // 3: tvix.castore.v1.Node.directory:type_name -> tvix.castore.v1.DirectoryNode
+	2, // 4: tvix.castore.v1.Node.file:type_name -> tvix.castore.v1.FileNode
+	3, // 5: tvix.castore.v1.Node.symlink:type_name -> tvix.castore.v1.SymlinkNode
+	6, // [6:6] is the sub-list for method output_type
+	6, // [6:6] is the sub-list for method input_type
+	6, // [6:6] is the sub-list for extension type_name
+	6, // [6:6] is the sub-list for extension extendee
+	0, // [0:6] is the sub-list for field type_name
+}
+
+func init() { file_tvix_castore_protos_castore_proto_init() }
+func file_tvix_castore_protos_castore_proto_init() {
+	if File_tvix_castore_protos_castore_proto != nil {
+		return
+	}
+	if !protoimpl.UnsafeEnabled {
+		file_tvix_castore_protos_castore_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*Directory); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_tvix_castore_protos_castore_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*DirectoryNode); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_tvix_castore_protos_castore_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*FileNode); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_tvix_castore_protos_castore_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*SymlinkNode); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_tvix_castore_protos_castore_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*Node); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+	}
+	file_tvix_castore_protos_castore_proto_msgTypes[4].OneofWrappers = []interface{}{
+		(*Node_Directory)(nil),
+		(*Node_File)(nil),
+		(*Node_Symlink)(nil),
+	}
+	type x struct{}
+	out := protoimpl.TypeBuilder{
+		File: protoimpl.DescBuilder{
+			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+			RawDescriptor: file_tvix_castore_protos_castore_proto_rawDesc,
+			NumEnums:      0,
+			NumMessages:   5,
+			NumExtensions: 0,
+			NumServices:   0,
+		},
+		GoTypes:           file_tvix_castore_protos_castore_proto_goTypes,
+		DependencyIndexes: file_tvix_castore_protos_castore_proto_depIdxs,
+		MessageInfos:      file_tvix_castore_protos_castore_proto_msgTypes,
+	}.Build()
+	File_tvix_castore_protos_castore_proto = out.File
+	file_tvix_castore_protos_castore_proto_rawDesc = nil
+	file_tvix_castore_protos_castore_proto_goTypes = nil
+	file_tvix_castore_protos_castore_proto_depIdxs = nil
+}
diff --git a/tvix/castore-go/castore_test.go b/tvix/castore-go/castore_test.go
new file mode 100644
index 0000000000..c237442f4e
--- /dev/null
+++ b/tvix/castore-go/castore_test.go
@@ -0,0 +1,298 @@
+package castorev1_test
+
+import (
+	"testing"
+
+	castorev1pb "code.tvl.fyi/tvix/castore-go"
+	"github.com/stretchr/testify/assert"
+)
+
+var (
+	dummyDigest = []byte{
+		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		0x00, 0x00, 0x00, 0x00,
+	}
+)
+
+func TestDirectorySize(t *testing.T) {
+	t.Run("empty", func(t *testing.T) {
+		d := castorev1pb.Directory{
+			Directories: []*castorev1pb.DirectoryNode{},
+			Files:       []*castorev1pb.FileNode{},
+			Symlinks:    []*castorev1pb.SymlinkNode{},
+		}
+
+		assert.Equal(t, uint64(0), d.Size())
+	})
+
+	t.Run("containing single empty directory", func(t *testing.T) {
+		d := castorev1pb.Directory{
+			Directories: []*castorev1pb.DirectoryNode{{
+				Name:   []byte([]byte("foo")),
+				Digest: dummyDigest,
+				Size:   0,
+			}},
+			Files:    []*castorev1pb.FileNode{},
+			Symlinks: []*castorev1pb.SymlinkNode{},
+		}
+
+		assert.Equal(t, uint64(1), d.Size())
+	})
+
+	t.Run("containing single non-empty directory", func(t *testing.T) {
+		d := castorev1pb.Directory{
+			Directories: []*castorev1pb.DirectoryNode{{
+				Name:   []byte("foo"),
+				Digest: dummyDigest,
+				Size:   4,
+			}},
+			Files:    []*castorev1pb.FileNode{},
+			Symlinks: []*castorev1pb.SymlinkNode{},
+		}
+
+		assert.Equal(t, uint64(5), d.Size())
+	})
+
+	t.Run("containing single file", func(t *testing.T) {
+		d := castorev1pb.Directory{
+			Directories: []*castorev1pb.DirectoryNode{},
+			Files: []*castorev1pb.FileNode{{
+				Name:       []byte("foo"),
+				Digest:     dummyDigest,
+				Size:       42,
+				Executable: false,
+			}},
+			Symlinks: []*castorev1pb.SymlinkNode{},
+		}
+
+		assert.Equal(t, uint64(1), d.Size())
+	})
+
+	t.Run("containing single symlink", func(t *testing.T) {
+		d := castorev1pb.Directory{
+			Directories: []*castorev1pb.DirectoryNode{},
+			Files:       []*castorev1pb.FileNode{},
+			Symlinks: []*castorev1pb.SymlinkNode{{
+				Name:   []byte("foo"),
+				Target: []byte("bar"),
+			}},
+		}
+
+		assert.Equal(t, uint64(1), d.Size())
+	})
+
+}
+func TestDirectoryDigest(t *testing.T) {
+	d := castorev1pb.Directory{
+		Directories: []*castorev1pb.DirectoryNode{},
+		Files:       []*castorev1pb.FileNode{},
+		Symlinks:    []*castorev1pb.SymlinkNode{},
+	}
+
+	dgst, err := d.Digest()
+	assert.NoError(t, err, "calling Digest() on a directory shouldn't error")
+	assert.Equal(t, []byte{
+		0xaf, 0x13, 0x49, 0xb9, 0xf5, 0xf9, 0xa1, 0xa6, 0xa0, 0x40, 0x4d, 0xea, 0x36, 0xdc,
+		0xc9, 0x49, 0x9b, 0xcb, 0x25, 0xc9, 0xad, 0xc1, 0x12, 0xb7, 0xcc, 0x9a, 0x93, 0xca,
+		0xe4, 0x1f, 0x32, 0x62,
+	}, dgst)
+}
+
+func TestDirectoryValidate(t *testing.T) {
+	t.Run("empty", func(t *testing.T) {
+		d := castorev1pb.Directory{
+			Directories: []*castorev1pb.DirectoryNode{},
+			Files:       []*castorev1pb.FileNode{},
+			Symlinks:    []*castorev1pb.SymlinkNode{},
+		}
+
+		assert.NoError(t, d.Validate())
+	})
+
+	t.Run("invalid names", func(t *testing.T) {
+		{
+			d := castorev1pb.Directory{
+				Directories: []*castorev1pb.DirectoryNode{{
+					Name:   []byte{},
+					Digest: dummyDigest,
+					Size:   42,
+				}},
+				Files:    []*castorev1pb.FileNode{},
+				Symlinks: []*castorev1pb.SymlinkNode{},
+			}
+
+			assert.ErrorContains(t, d.Validate(), "invalid node name")
+		}
+		{
+			d := castorev1pb.Directory{
+				Directories: []*castorev1pb.DirectoryNode{{
+					Name:   []byte("."),
+					Digest: dummyDigest,
+					Size:   42,
+				}},
+				Files:    []*castorev1pb.FileNode{},
+				Symlinks: []*castorev1pb.SymlinkNode{},
+			}
+
+			assert.ErrorContains(t, d.Validate(), "invalid node name")
+		}
+		{
+			d := castorev1pb.Directory{
+				Directories: []*castorev1pb.DirectoryNode{},
+				Files: []*castorev1pb.FileNode{{
+					Name:       []byte(".."),
+					Digest:     dummyDigest,
+					Size:       42,
+					Executable: false,
+				}},
+				Symlinks: []*castorev1pb.SymlinkNode{},
+			}
+
+			assert.ErrorContains(t, d.Validate(), "invalid node name")
+		}
+		{
+			d := castorev1pb.Directory{
+				Directories: []*castorev1pb.DirectoryNode{},
+				Files:       []*castorev1pb.FileNode{},
+				Symlinks: []*castorev1pb.SymlinkNode{{
+					Name:   []byte("\x00"),
+					Target: []byte("foo"),
+				}},
+			}
+
+			assert.ErrorContains(t, d.Validate(), "invalid node name")
+		}
+		{
+			d := castorev1pb.Directory{
+				Directories: []*castorev1pb.DirectoryNode{},
+				Files:       []*castorev1pb.FileNode{},
+				Symlinks: []*castorev1pb.SymlinkNode{{
+					Name:   []byte("foo/bar"),
+					Target: []byte("foo"),
+				}},
+			}
+
+			assert.ErrorContains(t, d.Validate(), "invalid node name")
+		}
+	})
+
+	t.Run("invalid digest", func(t *testing.T) {
+		d := castorev1pb.Directory{
+			Directories: []*castorev1pb.DirectoryNode{{
+				Name:   []byte("foo"),
+				Digest: nil,
+				Size:   42,
+			}},
+			Files:    []*castorev1pb.FileNode{},
+			Symlinks: []*castorev1pb.SymlinkNode{},
+		}
+
+		assert.ErrorContains(t, d.Validate(), "invalid digest length")
+	})
+
+	t.Run("invalid symlink targets", func(t *testing.T) {
+		{
+			d := castorev1pb.Directory{
+				Directories: []*castorev1pb.DirectoryNode{},
+				Files:       []*castorev1pb.FileNode{},
+				Symlinks: []*castorev1pb.SymlinkNode{{
+					Name:   []byte("foo"),
+					Target: []byte{},
+				}},
+			}
+
+			assert.ErrorContains(t, d.Validate(), "invalid symlink target")
+		}
+		{
+			d := castorev1pb.Directory{
+				Directories: []*castorev1pb.DirectoryNode{},
+				Files:       []*castorev1pb.FileNode{},
+				Symlinks: []*castorev1pb.SymlinkNode{{
+					Name:   []byte("foo"),
+					Target: []byte{0x66, 0x6f, 0x6f, 0},
+				}},
+			}
+
+			assert.ErrorContains(t, d.Validate(), "invalid symlink target")
+		}
+	})
+
+	t.Run("sorting", func(t *testing.T) {
+		// "b" comes before "a", bad.
+		{
+			d := castorev1pb.Directory{
+				Directories: []*castorev1pb.DirectoryNode{{
+					Name:   []byte("b"),
+					Digest: dummyDigest,
+					Size:   42,
+				}, {
+					Name:   []byte("a"),
+					Digest: dummyDigest,
+					Size:   42,
+				}},
+				Files:    []*castorev1pb.FileNode{},
+				Symlinks: []*castorev1pb.SymlinkNode{},
+			}
+			assert.ErrorContains(t, d.Validate(), "is not in sorted order")
+		}
+
+		// "a" exists twice, bad.
+		{
+			d := castorev1pb.Directory{
+				Directories: []*castorev1pb.DirectoryNode{{
+					Name:   []byte("a"),
+					Digest: dummyDigest,
+					Size:   42,
+				}},
+				Files: []*castorev1pb.FileNode{{
+					Name:       []byte("a"),
+					Digest:     dummyDigest,
+					Size:       42,
+					Executable: false,
+				}},
+				Symlinks: []*castorev1pb.SymlinkNode{},
+			}
+			assert.ErrorContains(t, d.Validate(), "duplicate name")
+		}
+
+		// "a" comes before "b", all good.
+		{
+			d := castorev1pb.Directory{
+				Directories: []*castorev1pb.DirectoryNode{{
+					Name:   []byte("a"),
+					Digest: dummyDigest,
+					Size:   42,
+				}, {
+					Name:   []byte("b"),
+					Digest: dummyDigest,
+					Size:   42,
+				}},
+				Files:    []*castorev1pb.FileNode{},
+				Symlinks: []*castorev1pb.SymlinkNode{},
+			}
+			assert.NoError(t, d.Validate(), "shouldn't error")
+		}
+
+		// [b, c] and [a] are both properly sorted.
+		{
+			d := castorev1pb.Directory{
+				Directories: []*castorev1pb.DirectoryNode{{
+					Name:   []byte("b"),
+					Digest: dummyDigest,
+					Size:   42,
+				}, {
+					Name:   []byte("c"),
+					Digest: dummyDigest,
+					Size:   42,
+				}},
+				Files: []*castorev1pb.FileNode{},
+				Symlinks: []*castorev1pb.SymlinkNode{{
+					Name:   []byte("a"),
+					Target: []byte("foo"),
+				}},
+			}
+			assert.NoError(t, d.Validate(), "shouldn't error")
+		}
+	})
+}
diff --git a/tvix/castore-go/default.nix b/tvix/castore-go/default.nix
new file mode 100644
index 0000000000..6999e90ccd
--- /dev/null
+++ b/tvix/castore-go/default.nix
@@ -0,0 +1,31 @@
+{ depot, pkgs, ... }:
+
+let
+  regenerate = pkgs.writeShellScript "regenerate" ''
+    (cd $(git rev-parse --show-toplevel)/tvix/castore-go && rm *.pb.go && cp ${depot.tvix.castore.protos.go-bindings}/*.pb.go . && chmod +w *.pb.go)
+  '';
+in
+(pkgs.buildGoModule {
+  name = "castore-go";
+  src = depot.third_party.gitignoreSource ./.;
+  vendorHash = "sha256-ZNtSSW+oCxMsBtURSrea9/GyUHDagtGefM+Ii+VkgCA=";
+}).overrideAttrs (_: {
+  meta.ci.extraSteps = {
+    check = {
+      label = ":water_buffalo: ensure generated protobuf files match";
+      needsOutput = true;
+      command = pkgs.writeShellScript "pb-go-check" ''
+        ${regenerate}
+        if [[ -n "$(git status --porcelain -unormal)" ]]; then
+            echo "-----------------------------"
+            echo ".pb.go files need to be updated, mg run //tvix/castore-go/regenerate"
+            echo "-----------------------------"
+            git status -unormal
+            exit 1
+        fi
+      '';
+      alwaysRun = true;
+    };
+  };
+  passthru.regenerate = regenerate;
+})
diff --git a/tvix/castore-go/go.mod b/tvix/castore-go/go.mod
new file mode 100644
index 0000000000..94310dd2aa
--- /dev/null
+++ b/tvix/castore-go/go.mod
@@ -0,0 +1,22 @@
+module code.tvl.fyi/tvix/castore-go
+
+go 1.19
+
+require (
+	github.com/stretchr/testify v1.8.1
+	google.golang.org/grpc v1.51.0
+	google.golang.org/protobuf v1.31.0
+	lukechampine.com/blake3 v1.1.7
+)
+
+require (
+	github.com/davecgh/go-spew v1.1.1 // indirect
+	github.com/golang/protobuf v1.5.2 // indirect
+	github.com/klauspost/cpuid/v2 v2.0.9 // indirect
+	github.com/pmezard/go-difflib v1.0.0 // indirect
+	golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect
+	golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect
+	golang.org/x/text v0.4.0 // indirect
+	google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect
+	gopkg.in/yaml.v3 v3.0.1 // indirect
+)
diff --git a/tvix/castore-go/go.sum b/tvix/castore-go/go.sum
new file mode 100644
index 0000000000..535b8e32f0
--- /dev/null
+++ b/tvix/castore-go/go.sum
@@ -0,0 +1,99 @@
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
+github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
+github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
+github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
+github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
+github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
+golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=
+google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.51.0 h1:E1eGv1FTqoLIdnBCZufiSHgKjlqG6fKFf6pPWtMTh8U=
+google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww=
+google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
+google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
+google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
+google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
+google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+lukechampine.com/blake3 v1.1.7 h1:GgRMhmdsuK8+ii6UZFDL8Nb+VyMwadAgcJyfYHxG6n0=
+lukechampine.com/blake3 v1.1.7/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA=
diff --git a/tvix/castore-go/rename_node.go b/tvix/castore-go/rename_node.go
new file mode 100644
index 0000000000..80537b16d3
--- /dev/null
+++ b/tvix/castore-go/rename_node.go
@@ -0,0 +1,38 @@
+package castorev1
+
+// RenamedNode returns a node with a new name.
+func RenamedNode(node *Node, name string) *Node {
+	if directoryNode := node.GetDirectory(); directoryNode != nil {
+		return &Node{
+			Node: &Node_Directory{
+				Directory: &DirectoryNode{
+					Name:   []byte(name),
+					Digest: directoryNode.GetDigest(),
+					Size:   directoryNode.GetSize(),
+				},
+			},
+		}
+	} else if fileNode := node.GetFile(); fileNode != nil {
+		return &Node{
+			Node: &Node_File{
+				File: &FileNode{
+					Name:       []byte(name),
+					Digest:     fileNode.GetDigest(),
+					Size:       fileNode.GetSize(),
+					Executable: fileNode.GetExecutable(),
+				},
+			},
+		}
+	} else if symlinkNode := node.GetSymlink(); symlinkNode != nil {
+		return &Node{
+			Node: &Node_Symlink{
+				Symlink: &SymlinkNode{
+					Name:   []byte(name),
+					Target: symlinkNode.GetTarget(),
+				},
+			},
+		}
+	} else {
+		panic("unreachable")
+	}
+}
diff --git a/tvix/castore-go/rpc_blobstore.pb.go b/tvix/castore-go/rpc_blobstore.pb.go
new file mode 100644
index 0000000000..3607a65bbe
--- /dev/null
+++ b/tvix/castore-go/rpc_blobstore.pb.go
@@ -0,0 +1,538 @@
+// SPDX-License-Identifier: MIT
+// Copyright © 2022 The Tvix Authors
+
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// 	protoc-gen-go v1.33.0
+// 	protoc        (unknown)
+// source: tvix/castore/protos/rpc_blobstore.proto
+
+package castorev1
+
+import (
+	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+	reflect "reflect"
+	sync "sync"
+)
+
+const (
+	// Verify that this generated code is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+	// Verify that runtime/protoimpl is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type StatBlobRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// The blake3 digest of the blob requested
+	Digest []byte `protobuf:"bytes,1,opt,name=digest,proto3" json:"digest,omitempty"`
+	// Whether the server should reply with a list of more granular chunks.
+	SendChunks bool `protobuf:"varint,2,opt,name=send_chunks,json=sendChunks,proto3" json:"send_chunks,omitempty"`
+	// Whether the server should reply with a bao.
+	SendBao bool `protobuf:"varint,3,opt,name=send_bao,json=sendBao,proto3" json:"send_bao,omitempty"`
+}
+
+func (x *StatBlobRequest) Reset() {
+	*x = StatBlobRequest{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_tvix_castore_protos_rpc_blobstore_proto_msgTypes[0]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *StatBlobRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*StatBlobRequest) ProtoMessage() {}
+
+func (x *StatBlobRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_tvix_castore_protos_rpc_blobstore_proto_msgTypes[0]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use StatBlobRequest.ProtoReflect.Descriptor instead.
+func (*StatBlobRequest) Descriptor() ([]byte, []int) {
+	return file_tvix_castore_protos_rpc_blobstore_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *StatBlobRequest) GetDigest() []byte {
+	if x != nil {
+		return x.Digest
+	}
+	return nil
+}
+
+func (x *StatBlobRequest) GetSendChunks() bool {
+	if x != nil {
+		return x.SendChunks
+	}
+	return false
+}
+
+func (x *StatBlobRequest) GetSendBao() bool {
+	if x != nil {
+		return x.SendBao
+	}
+	return false
+}
+
+type StatBlobResponse struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// If `send_chunks` was set to true, this MAY contain a list of more
+	// granular chunks, which then may be read individually via the `Read`
+	// method.
+	Chunks []*StatBlobResponse_ChunkMeta `protobuf:"bytes,2,rep,name=chunks,proto3" json:"chunks,omitempty"`
+	// If `send_bao` was set to true, this MAY contain a outboard bao.
+	// The exact format and message types here will still be fleshed out.
+	Bao []byte `protobuf:"bytes,3,opt,name=bao,proto3" json:"bao,omitempty"`
+}
+
+func (x *StatBlobResponse) Reset() {
+	*x = StatBlobResponse{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_tvix_castore_protos_rpc_blobstore_proto_msgTypes[1]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *StatBlobResponse) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*StatBlobResponse) ProtoMessage() {}
+
+func (x *StatBlobResponse) ProtoReflect() protoreflect.Message {
+	mi := &file_tvix_castore_protos_rpc_blobstore_proto_msgTypes[1]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use StatBlobResponse.ProtoReflect.Descriptor instead.
+func (*StatBlobResponse) Descriptor() ([]byte, []int) {
+	return file_tvix_castore_protos_rpc_blobstore_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *StatBlobResponse) GetChunks() []*StatBlobResponse_ChunkMeta {
+	if x != nil {
+		return x.Chunks
+	}
+	return nil
+}
+
+func (x *StatBlobResponse) GetBao() []byte {
+	if x != nil {
+		return x.Bao
+	}
+	return nil
+}
+
+type ReadBlobRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// The blake3 digest of the blob or chunk requested
+	Digest []byte `protobuf:"bytes,1,opt,name=digest,proto3" json:"digest,omitempty"`
+}
+
+func (x *ReadBlobRequest) Reset() {
+	*x = ReadBlobRequest{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_tvix_castore_protos_rpc_blobstore_proto_msgTypes[2]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *ReadBlobRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ReadBlobRequest) ProtoMessage() {}
+
+func (x *ReadBlobRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_tvix_castore_protos_rpc_blobstore_proto_msgTypes[2]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use ReadBlobRequest.ProtoReflect.Descriptor instead.
+func (*ReadBlobRequest) Descriptor() ([]byte, []int) {
+	return file_tvix_castore_protos_rpc_blobstore_proto_rawDescGZIP(), []int{2}
+}
+
+func (x *ReadBlobRequest) GetDigest() []byte {
+	if x != nil {
+		return x.Digest
+	}
+	return nil
+}
+
+// This represents some bytes of a blob.
+// Blobs are sent in smaller chunks to keep message sizes manageable.
+type BlobChunk struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"`
+}
+
+func (x *BlobChunk) Reset() {
+	*x = BlobChunk{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_tvix_castore_protos_rpc_blobstore_proto_msgTypes[3]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *BlobChunk) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*BlobChunk) ProtoMessage() {}
+
+func (x *BlobChunk) ProtoReflect() protoreflect.Message {
+	mi := &file_tvix_castore_protos_rpc_blobstore_proto_msgTypes[3]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use BlobChunk.ProtoReflect.Descriptor instead.
+func (*BlobChunk) Descriptor() ([]byte, []int) {
+	return file_tvix_castore_protos_rpc_blobstore_proto_rawDescGZIP(), []int{3}
+}
+
+func (x *BlobChunk) GetData() []byte {
+	if x != nil {
+		return x.Data
+	}
+	return nil
+}
+
+type PutBlobResponse struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// The blake3 digest of the data that was sent.
+	Digest []byte `protobuf:"bytes,1,opt,name=digest,proto3" json:"digest,omitempty"`
+}
+
+func (x *PutBlobResponse) Reset() {
+	*x = PutBlobResponse{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_tvix_castore_protos_rpc_blobstore_proto_msgTypes[4]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *PutBlobResponse) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*PutBlobResponse) ProtoMessage() {}
+
+func (x *PutBlobResponse) ProtoReflect() protoreflect.Message {
+	mi := &file_tvix_castore_protos_rpc_blobstore_proto_msgTypes[4]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use PutBlobResponse.ProtoReflect.Descriptor instead.
+func (*PutBlobResponse) Descriptor() ([]byte, []int) {
+	return file_tvix_castore_protos_rpc_blobstore_proto_rawDescGZIP(), []int{4}
+}
+
+func (x *PutBlobResponse) GetDigest() []byte {
+	if x != nil {
+		return x.Digest
+	}
+	return nil
+}
+
+type StatBlobResponse_ChunkMeta struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// Digest of that specific chunk
+	Digest []byte `protobuf:"bytes,1,opt,name=digest,proto3" json:"digest,omitempty"`
+	// Length of that chunk, in bytes.
+	Size uint64 `protobuf:"varint,2,opt,name=size,proto3" json:"size,omitempty"`
+}
+
+func (x *StatBlobResponse_ChunkMeta) Reset() {
+	*x = StatBlobResponse_ChunkMeta{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_tvix_castore_protos_rpc_blobstore_proto_msgTypes[5]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *StatBlobResponse_ChunkMeta) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*StatBlobResponse_ChunkMeta) ProtoMessage() {}
+
+func (x *StatBlobResponse_ChunkMeta) ProtoReflect() protoreflect.Message {
+	mi := &file_tvix_castore_protos_rpc_blobstore_proto_msgTypes[5]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use StatBlobResponse_ChunkMeta.ProtoReflect.Descriptor instead.
+func (*StatBlobResponse_ChunkMeta) Descriptor() ([]byte, []int) {
+	return file_tvix_castore_protos_rpc_blobstore_proto_rawDescGZIP(), []int{1, 0}
+}
+
+func (x *StatBlobResponse_ChunkMeta) GetDigest() []byte {
+	if x != nil {
+		return x.Digest
+	}
+	return nil
+}
+
+func (x *StatBlobResponse_ChunkMeta) GetSize() uint64 {
+	if x != nil {
+		return x.Size
+	}
+	return 0
+}
+
+var File_tvix_castore_protos_rpc_blobstore_proto protoreflect.FileDescriptor
+
+var file_tvix_castore_protos_rpc_blobstore_proto_rawDesc = []byte{
+	0x0a, 0x27, 0x74, 0x76, 0x69, 0x78, 0x2f, 0x63, 0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2f, 0x70,
+	0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x72, 0x70, 0x63, 0x5f, 0x62, 0x6c, 0x6f, 0x62, 0x73, 0x74,
+	0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x74, 0x76, 0x69, 0x78, 0x2e,
+	0x63, 0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x22, 0x65, 0x0a, 0x0f, 0x53, 0x74,
+	0x61, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a,
+	0x06, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x64,
+	0x69, 0x67, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x65, 0x6e, 0x64, 0x5f, 0x63, 0x68,
+	0x75, 0x6e, 0x6b, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x73, 0x65, 0x6e, 0x64,
+	0x43, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x65, 0x6e, 0x64, 0x5f, 0x62,
+	0x61, 0x6f, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x65, 0x6e, 0x64, 0x42, 0x61,
+	0x6f, 0x22, 0xa2, 0x01, 0x0a, 0x10, 0x53, 0x74, 0x61, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65,
+	0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x43, 0x0a, 0x06, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x73,
+	0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x74, 0x76, 0x69, 0x78, 0x2e, 0x63, 0x61,
+	0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x42, 0x6c, 0x6f,
+	0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x4d,
+	0x65, 0x74, 0x61, 0x52, 0x06, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x62,
+	0x61, 0x6f, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x62, 0x61, 0x6f, 0x1a, 0x37, 0x0a,
+	0x09, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x69,
+	0x67, 0x65, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x64, 0x69, 0x67, 0x65,
+	0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04,
+	0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x22, 0x29, 0x0a, 0x0f, 0x52, 0x65, 0x61, 0x64, 0x42, 0x6c,
+	0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x69, 0x67,
+	0x65, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x64, 0x69, 0x67, 0x65, 0x73,
+	0x74, 0x22, 0x1f, 0x0a, 0x09, 0x42, 0x6c, 0x6f, 0x62, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x12, 0x12,
+	0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61,
+	0x74, 0x61, 0x22, 0x29, 0x0a, 0x0f, 0x50, 0x75, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x73,
+	0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x18,
+	0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x32, 0xe9, 0x01,
+	0x0a, 0x0b, 0x42, 0x6c, 0x6f, 0x62, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x4b, 0x0a,
+	0x04, 0x53, 0x74, 0x61, 0x74, 0x12, 0x20, 0x2e, 0x74, 0x76, 0x69, 0x78, 0x2e, 0x63, 0x61, 0x73,
+	0x74, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x42, 0x6c, 0x6f, 0x62,
+	0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x74, 0x76, 0x69, 0x78, 0x2e, 0x63,
+	0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x42, 0x6c,
+	0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x46, 0x0a, 0x04, 0x52, 0x65,
+	0x61, 0x64, 0x12, 0x20, 0x2e, 0x74, 0x76, 0x69, 0x78, 0x2e, 0x63, 0x61, 0x73, 0x74, 0x6f, 0x72,
+	0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x61, 0x64, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x71,
+	0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x74, 0x76, 0x69, 0x78, 0x2e, 0x63, 0x61, 0x73, 0x74,
+	0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x6c, 0x6f, 0x62, 0x43, 0x68, 0x75, 0x6e, 0x6b,
+	0x30, 0x01, 0x12, 0x45, 0x0a, 0x03, 0x50, 0x75, 0x74, 0x12, 0x1a, 0x2e, 0x74, 0x76, 0x69, 0x78,
+	0x2e, 0x63, 0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x6c, 0x6f, 0x62,
+	0x43, 0x68, 0x75, 0x6e, 0x6b, 0x1a, 0x20, 0x2e, 0x74, 0x76, 0x69, 0x78, 0x2e, 0x63, 0x61, 0x73,
+	0x74, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x75, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52,
+	0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x42, 0x28, 0x5a, 0x26, 0x63, 0x6f, 0x64,
+	0x65, 0x2e, 0x74, 0x76, 0x6c, 0x2e, 0x66, 0x79, 0x69, 0x2f, 0x74, 0x76, 0x69, 0x78, 0x2f, 0x63,
+	0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2d, 0x67, 0x6f, 0x3b, 0x63, 0x61, 0x73, 0x74, 0x6f, 0x72,
+	0x65, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+}
+
+var (
+	file_tvix_castore_protos_rpc_blobstore_proto_rawDescOnce sync.Once
+	file_tvix_castore_protos_rpc_blobstore_proto_rawDescData = file_tvix_castore_protos_rpc_blobstore_proto_rawDesc
+)
+
+func file_tvix_castore_protos_rpc_blobstore_proto_rawDescGZIP() []byte {
+	file_tvix_castore_protos_rpc_blobstore_proto_rawDescOnce.Do(func() {
+		file_tvix_castore_protos_rpc_blobstore_proto_rawDescData = protoimpl.X.CompressGZIP(file_tvix_castore_protos_rpc_blobstore_proto_rawDescData)
+	})
+	return file_tvix_castore_protos_rpc_blobstore_proto_rawDescData
+}
+
+var file_tvix_castore_protos_rpc_blobstore_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
+var file_tvix_castore_protos_rpc_blobstore_proto_goTypes = []interface{}{
+	(*StatBlobRequest)(nil),            // 0: tvix.castore.v1.StatBlobRequest
+	(*StatBlobResponse)(nil),           // 1: tvix.castore.v1.StatBlobResponse
+	(*ReadBlobRequest)(nil),            // 2: tvix.castore.v1.ReadBlobRequest
+	(*BlobChunk)(nil),                  // 3: tvix.castore.v1.BlobChunk
+	(*PutBlobResponse)(nil),            // 4: tvix.castore.v1.PutBlobResponse
+	(*StatBlobResponse_ChunkMeta)(nil), // 5: tvix.castore.v1.StatBlobResponse.ChunkMeta
+}
+var file_tvix_castore_protos_rpc_blobstore_proto_depIdxs = []int32{
+	5, // 0: tvix.castore.v1.StatBlobResponse.chunks:type_name -> tvix.castore.v1.StatBlobResponse.ChunkMeta
+	0, // 1: tvix.castore.v1.BlobService.Stat:input_type -> tvix.castore.v1.StatBlobRequest
+	2, // 2: tvix.castore.v1.BlobService.Read:input_type -> tvix.castore.v1.ReadBlobRequest
+	3, // 3: tvix.castore.v1.BlobService.Put:input_type -> tvix.castore.v1.BlobChunk
+	1, // 4: tvix.castore.v1.BlobService.Stat:output_type -> tvix.castore.v1.StatBlobResponse
+	3, // 5: tvix.castore.v1.BlobService.Read:output_type -> tvix.castore.v1.BlobChunk
+	4, // 6: tvix.castore.v1.BlobService.Put:output_type -> tvix.castore.v1.PutBlobResponse
+	4, // [4:7] is the sub-list for method output_type
+	1, // [1:4] is the sub-list for method input_type
+	1, // [1:1] is the sub-list for extension type_name
+	1, // [1:1] is the sub-list for extension extendee
+	0, // [0:1] is the sub-list for field type_name
+}
+
+func init() { file_tvix_castore_protos_rpc_blobstore_proto_init() }
+func file_tvix_castore_protos_rpc_blobstore_proto_init() {
+	if File_tvix_castore_protos_rpc_blobstore_proto != nil {
+		return
+	}
+	if !protoimpl.UnsafeEnabled {
+		file_tvix_castore_protos_rpc_blobstore_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*StatBlobRequest); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_tvix_castore_protos_rpc_blobstore_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*StatBlobResponse); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_tvix_castore_protos_rpc_blobstore_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*ReadBlobRequest); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_tvix_castore_protos_rpc_blobstore_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*BlobChunk); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_tvix_castore_protos_rpc_blobstore_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*PutBlobResponse); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_tvix_castore_protos_rpc_blobstore_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*StatBlobResponse_ChunkMeta); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+	}
+	type x struct{}
+	out := protoimpl.TypeBuilder{
+		File: protoimpl.DescBuilder{
+			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+			RawDescriptor: file_tvix_castore_protos_rpc_blobstore_proto_rawDesc,
+			NumEnums:      0,
+			NumMessages:   6,
+			NumExtensions: 0,
+			NumServices:   1,
+		},
+		GoTypes:           file_tvix_castore_protos_rpc_blobstore_proto_goTypes,
+		DependencyIndexes: file_tvix_castore_protos_rpc_blobstore_proto_depIdxs,
+		MessageInfos:      file_tvix_castore_protos_rpc_blobstore_proto_msgTypes,
+	}.Build()
+	File_tvix_castore_protos_rpc_blobstore_proto = out.File
+	file_tvix_castore_protos_rpc_blobstore_proto_rawDesc = nil
+	file_tvix_castore_protos_rpc_blobstore_proto_goTypes = nil
+	file_tvix_castore_protos_rpc_blobstore_proto_depIdxs = nil
+}
diff --git a/tvix/castore-go/rpc_blobstore_grpc.pb.go b/tvix/castore-go/rpc_blobstore_grpc.pb.go
new file mode 100644
index 0000000000..d63a6cae96
--- /dev/null
+++ b/tvix/castore-go/rpc_blobstore_grpc.pb.go
@@ -0,0 +1,288 @@
+// SPDX-License-Identifier: MIT
+// Copyright © 2022 The Tvix Authors
+
+// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
+// versions:
+// - protoc-gen-go-grpc v1.3.0
+// - protoc             (unknown)
+// source: tvix/castore/protos/rpc_blobstore.proto
+
+package castorev1
+
+import (
+	context "context"
+	grpc "google.golang.org/grpc"
+	codes "google.golang.org/grpc/codes"
+	status "google.golang.org/grpc/status"
+)
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the grpc package it is being compiled against.
+// Requires gRPC-Go v1.32.0 or later.
+const _ = grpc.SupportPackageIsVersion7
+
+const (
+	BlobService_Stat_FullMethodName = "/tvix.castore.v1.BlobService/Stat"
+	BlobService_Read_FullMethodName = "/tvix.castore.v1.BlobService/Read"
+	BlobService_Put_FullMethodName  = "/tvix.castore.v1.BlobService/Put"
+)
+
+// BlobServiceClient is the client API for BlobService service.
+//
+// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
+type BlobServiceClient interface {
+	// Stat can be used to check for the existence of a blob, as well as
+	// gathering more data about it, like more granular chunking information
+	// or baos.
+	// Server implementations are not required to provide more granular chunking
+	// information, especially if the digest specified in `StatBlobRequest` is
+	// already a chunk of a blob.
+	Stat(ctx context.Context, in *StatBlobRequest, opts ...grpc.CallOption) (*StatBlobResponse, error)
+	// Read allows reading (all) data of a blob/chunk by the BLAKE3 digest of
+	// its contents.
+	// If the backend communicated more granular chunks in the `Stat` request,
+	// this can also be used to read chunks.
+	// This request returns a stream of BlobChunk, which is just a container for
+	// a stream of bytes.
+	// The server may decide on whatever chunking it may seem fit as a size for
+	// the individual BlobChunk sent in the response stream, this is mostly to
+	// keep individual messages at a manageable size.
+	Read(ctx context.Context, in *ReadBlobRequest, opts ...grpc.CallOption) (BlobService_ReadClient, error)
+	// Put uploads a Blob, by reading a stream of bytes.
+	//
+	// The way the data is chunked up in individual BlobChunk messages sent in
+	// the stream has no effect on how the server ends up chunking blobs up, if
+	// it does at all.
+	Put(ctx context.Context, opts ...grpc.CallOption) (BlobService_PutClient, error)
+}
+
+type blobServiceClient struct {
+	cc grpc.ClientConnInterface
+}
+
+func NewBlobServiceClient(cc grpc.ClientConnInterface) BlobServiceClient {
+	return &blobServiceClient{cc}
+}
+
+func (c *blobServiceClient) Stat(ctx context.Context, in *StatBlobRequest, opts ...grpc.CallOption) (*StatBlobResponse, error) {
+	out := new(StatBlobResponse)
+	err := c.cc.Invoke(ctx, BlobService_Stat_FullMethodName, in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+func (c *blobServiceClient) Read(ctx context.Context, in *ReadBlobRequest, opts ...grpc.CallOption) (BlobService_ReadClient, error) {
+	stream, err := c.cc.NewStream(ctx, &BlobService_ServiceDesc.Streams[0], BlobService_Read_FullMethodName, opts...)
+	if err != nil {
+		return nil, err
+	}
+	x := &blobServiceReadClient{stream}
+	if err := x.ClientStream.SendMsg(in); err != nil {
+		return nil, err
+	}
+	if err := x.ClientStream.CloseSend(); err != nil {
+		return nil, err
+	}
+	return x, nil
+}
+
+type BlobService_ReadClient interface {
+	Recv() (*BlobChunk, error)
+	grpc.ClientStream
+}
+
+type blobServiceReadClient struct {
+	grpc.ClientStream
+}
+
+func (x *blobServiceReadClient) Recv() (*BlobChunk, error) {
+	m := new(BlobChunk)
+	if err := x.ClientStream.RecvMsg(m); err != nil {
+		return nil, err
+	}
+	return m, nil
+}
+
+func (c *blobServiceClient) Put(ctx context.Context, opts ...grpc.CallOption) (BlobService_PutClient, error) {
+	stream, err := c.cc.NewStream(ctx, &BlobService_ServiceDesc.Streams[1], BlobService_Put_FullMethodName, opts...)
+	if err != nil {
+		return nil, err
+	}
+	x := &blobServicePutClient{stream}
+	return x, nil
+}
+
+type BlobService_PutClient interface {
+	Send(*BlobChunk) error
+	CloseAndRecv() (*PutBlobResponse, error)
+	grpc.ClientStream
+}
+
+type blobServicePutClient struct {
+	grpc.ClientStream
+}
+
+func (x *blobServicePutClient) Send(m *BlobChunk) error {
+	return x.ClientStream.SendMsg(m)
+}
+
+func (x *blobServicePutClient) CloseAndRecv() (*PutBlobResponse, error) {
+	if err := x.ClientStream.CloseSend(); err != nil {
+		return nil, err
+	}
+	m := new(PutBlobResponse)
+	if err := x.ClientStream.RecvMsg(m); err != nil {
+		return nil, err
+	}
+	return m, nil
+}
+
+// BlobServiceServer is the server API for BlobService service.
+// All implementations must embed UnimplementedBlobServiceServer
+// for forward compatibility
+type BlobServiceServer interface {
+	// Stat can be used to check for the existence of a blob, as well as
+	// gathering more data about it, like more granular chunking information
+	// or baos.
+	// Server implementations are not required to provide more granular chunking
+	// information, especially if the digest specified in `StatBlobRequest` is
+	// already a chunk of a blob.
+	Stat(context.Context, *StatBlobRequest) (*StatBlobResponse, error)
+	// Read allows reading (all) data of a blob/chunk by the BLAKE3 digest of
+	// its contents.
+	// If the backend communicated more granular chunks in the `Stat` request,
+	// this can also be used to read chunks.
+	// This request returns a stream of BlobChunk, which is just a container for
+	// a stream of bytes.
+	// The server may decide on whatever chunking it may seem fit as a size for
+	// the individual BlobChunk sent in the response stream, this is mostly to
+	// keep individual messages at a manageable size.
+	Read(*ReadBlobRequest, BlobService_ReadServer) error
+	// Put uploads a Blob, by reading a stream of bytes.
+	//
+	// The way the data is chunked up in individual BlobChunk messages sent in
+	// the stream has no effect on how the server ends up chunking blobs up, if
+	// it does at all.
+	Put(BlobService_PutServer) error
+	mustEmbedUnimplementedBlobServiceServer()
+}
+
+// UnimplementedBlobServiceServer must be embedded to have forward compatible implementations.
+type UnimplementedBlobServiceServer struct {
+}
+
+func (UnimplementedBlobServiceServer) Stat(context.Context, *StatBlobRequest) (*StatBlobResponse, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method Stat not implemented")
+}
+func (UnimplementedBlobServiceServer) Read(*ReadBlobRequest, BlobService_ReadServer) error {
+	return status.Errorf(codes.Unimplemented, "method Read not implemented")
+}
+func (UnimplementedBlobServiceServer) Put(BlobService_PutServer) error {
+	return status.Errorf(codes.Unimplemented, "method Put not implemented")
+}
+func (UnimplementedBlobServiceServer) mustEmbedUnimplementedBlobServiceServer() {}
+
+// UnsafeBlobServiceServer may be embedded to opt out of forward compatibility for this service.
+// Use of this interface is not recommended, as added methods to BlobServiceServer will
+// result in compilation errors.
+type UnsafeBlobServiceServer interface {
+	mustEmbedUnimplementedBlobServiceServer()
+}
+
+func RegisterBlobServiceServer(s grpc.ServiceRegistrar, srv BlobServiceServer) {
+	s.RegisterService(&BlobService_ServiceDesc, srv)
+}
+
+func _BlobService_Stat_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(StatBlobRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(BlobServiceServer).Stat(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: BlobService_Stat_FullMethodName,
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(BlobServiceServer).Stat(ctx, req.(*StatBlobRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+func _BlobService_Read_Handler(srv interface{}, stream grpc.ServerStream) error {
+	m := new(ReadBlobRequest)
+	if err := stream.RecvMsg(m); err != nil {
+		return err
+	}
+	return srv.(BlobServiceServer).Read(m, &blobServiceReadServer{stream})
+}
+
+type BlobService_ReadServer interface {
+	Send(*BlobChunk) error
+	grpc.ServerStream
+}
+
+type blobServiceReadServer struct {
+	grpc.ServerStream
+}
+
+func (x *blobServiceReadServer) Send(m *BlobChunk) error {
+	return x.ServerStream.SendMsg(m)
+}
+
+func _BlobService_Put_Handler(srv interface{}, stream grpc.ServerStream) error {
+	return srv.(BlobServiceServer).Put(&blobServicePutServer{stream})
+}
+
+type BlobService_PutServer interface {
+	SendAndClose(*PutBlobResponse) error
+	Recv() (*BlobChunk, error)
+	grpc.ServerStream
+}
+
+type blobServicePutServer struct {
+	grpc.ServerStream
+}
+
+func (x *blobServicePutServer) SendAndClose(m *PutBlobResponse) error {
+	return x.ServerStream.SendMsg(m)
+}
+
+func (x *blobServicePutServer) Recv() (*BlobChunk, error) {
+	m := new(BlobChunk)
+	if err := x.ServerStream.RecvMsg(m); err != nil {
+		return nil, err
+	}
+	return m, nil
+}
+
+// BlobService_ServiceDesc is the grpc.ServiceDesc for BlobService service.
+// It's only intended for direct use with grpc.RegisterService,
+// and not to be introspected or modified (even as a copy)
+var BlobService_ServiceDesc = grpc.ServiceDesc{
+	ServiceName: "tvix.castore.v1.BlobService",
+	HandlerType: (*BlobServiceServer)(nil),
+	Methods: []grpc.MethodDesc{
+		{
+			MethodName: "Stat",
+			Handler:    _BlobService_Stat_Handler,
+		},
+	},
+	Streams: []grpc.StreamDesc{
+		{
+			StreamName:    "Read",
+			Handler:       _BlobService_Read_Handler,
+			ServerStreams: true,
+		},
+		{
+			StreamName:    "Put",
+			Handler:       _BlobService_Put_Handler,
+			ClientStreams: true,
+		},
+	},
+	Metadata: "tvix/castore/protos/rpc_blobstore.proto",
+}
diff --git a/tvix/castore-go/rpc_directory.pb.go b/tvix/castore-go/rpc_directory.pb.go
new file mode 100644
index 0000000000..78c4a243e3
--- /dev/null
+++ b/tvix/castore-go/rpc_directory.pb.go
@@ -0,0 +1,272 @@
+// SPDX-License-Identifier: MIT
+// Copyright © 2022 The Tvix Authors
+
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// 	protoc-gen-go v1.33.0
+// 	protoc        (unknown)
+// source: tvix/castore/protos/rpc_directory.proto
+
+package castorev1
+
+import (
+	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+	reflect "reflect"
+	sync "sync"
+)
+
+const (
+	// Verify that this generated code is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+	// Verify that runtime/protoimpl is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type GetDirectoryRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// Types that are assignable to ByWhat:
+	//
+	//	*GetDirectoryRequest_Digest
+	ByWhat isGetDirectoryRequest_ByWhat `protobuf_oneof:"by_what"`
+	// If set to true, recursively resolve all child Directory messages.
+	// Directory messages SHOULD be streamed in a recursive breadth-first walk,
+	// but other orders are also fine, as long as Directory messages are only
+	// sent after they are referred to from previously sent Directory messages.
+	Recursive bool `protobuf:"varint,2,opt,name=recursive,proto3" json:"recursive,omitempty"`
+}
+
+func (x *GetDirectoryRequest) Reset() {
+	*x = GetDirectoryRequest{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_tvix_castore_protos_rpc_directory_proto_msgTypes[0]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *GetDirectoryRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*GetDirectoryRequest) ProtoMessage() {}
+
+func (x *GetDirectoryRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_tvix_castore_protos_rpc_directory_proto_msgTypes[0]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use GetDirectoryRequest.ProtoReflect.Descriptor instead.
+func (*GetDirectoryRequest) Descriptor() ([]byte, []int) {
+	return file_tvix_castore_protos_rpc_directory_proto_rawDescGZIP(), []int{0}
+}
+
+func (m *GetDirectoryRequest) GetByWhat() isGetDirectoryRequest_ByWhat {
+	if m != nil {
+		return m.ByWhat
+	}
+	return nil
+}
+
+func (x *GetDirectoryRequest) GetDigest() []byte {
+	if x, ok := x.GetByWhat().(*GetDirectoryRequest_Digest); ok {
+		return x.Digest
+	}
+	return nil
+}
+
+func (x *GetDirectoryRequest) GetRecursive() bool {
+	if x != nil {
+		return x.Recursive
+	}
+	return false
+}
+
+type isGetDirectoryRequest_ByWhat interface {
+	isGetDirectoryRequest_ByWhat()
+}
+
+type GetDirectoryRequest_Digest struct {
+	// The blake3 hash of the (root) Directory message, serialized in
+	// protobuf canonical form.
+	// Keep in mind this can be a subtree of another root.
+	Digest []byte `protobuf:"bytes,1,opt,name=digest,proto3,oneof"`
+}
+
+func (*GetDirectoryRequest_Digest) isGetDirectoryRequest_ByWhat() {}
+
+type PutDirectoryResponse struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	RootDigest []byte `protobuf:"bytes,1,opt,name=root_digest,json=rootDigest,proto3" json:"root_digest,omitempty"`
+}
+
+func (x *PutDirectoryResponse) Reset() {
+	*x = PutDirectoryResponse{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_tvix_castore_protos_rpc_directory_proto_msgTypes[1]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *PutDirectoryResponse) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*PutDirectoryResponse) ProtoMessage() {}
+
+func (x *PutDirectoryResponse) ProtoReflect() protoreflect.Message {
+	mi := &file_tvix_castore_protos_rpc_directory_proto_msgTypes[1]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use PutDirectoryResponse.ProtoReflect.Descriptor instead.
+func (*PutDirectoryResponse) Descriptor() ([]byte, []int) {
+	return file_tvix_castore_protos_rpc_directory_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *PutDirectoryResponse) GetRootDigest() []byte {
+	if x != nil {
+		return x.RootDigest
+	}
+	return nil
+}
+
+var File_tvix_castore_protos_rpc_directory_proto protoreflect.FileDescriptor
+
+var file_tvix_castore_protos_rpc_directory_proto_rawDesc = []byte{
+	0x0a, 0x27, 0x74, 0x76, 0x69, 0x78, 0x2f, 0x63, 0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2f, 0x70,
+	0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x72, 0x70, 0x63, 0x5f, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74,
+	0x6f, 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x74, 0x76, 0x69, 0x78, 0x2e,
+	0x63, 0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x1a, 0x21, 0x74, 0x76, 0x69, 0x78,
+	0x2f, 0x63, 0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f,
+	0x63, 0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x58, 0x0a,
+	0x13, 0x47, 0x65, 0x74, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71,
+	0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x06, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x18, 0x01,
+	0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x06, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x12, 0x1c,
+	0x0a, 0x09, 0x72, 0x65, 0x63, 0x75, 0x72, 0x73, 0x69, 0x76, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
+	0x08, 0x52, 0x09, 0x72, 0x65, 0x63, 0x75, 0x72, 0x73, 0x69, 0x76, 0x65, 0x42, 0x09, 0x0a, 0x07,
+	0x62, 0x79, 0x5f, 0x77, 0x68, 0x61, 0x74, 0x22, 0x37, 0x0a, 0x14, 0x50, 0x75, 0x74, 0x44, 0x69,
+	0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
+	0x1f, 0x0a, 0x0b, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x18, 0x01,
+	0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x72, 0x6f, 0x6f, 0x74, 0x44, 0x69, 0x67, 0x65, 0x73, 0x74,
+	0x32, 0xa9, 0x01, 0x0a, 0x10, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x53, 0x65,
+	0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x49, 0x0a, 0x03, 0x47, 0x65, 0x74, 0x12, 0x24, 0x2e, 0x74,
+	0x76, 0x69, 0x78, 0x2e, 0x63, 0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47,
+	0x65, 0x74, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65,
+	0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x74, 0x76, 0x69, 0x78, 0x2e, 0x63, 0x61, 0x73, 0x74, 0x6f, 0x72,
+	0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x30, 0x01,
+	0x12, 0x4a, 0x0a, 0x03, 0x50, 0x75, 0x74, 0x12, 0x1a, 0x2e, 0x74, 0x76, 0x69, 0x78, 0x2e, 0x63,
+	0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74,
+	0x6f, 0x72, 0x79, 0x1a, 0x25, 0x2e, 0x74, 0x76, 0x69, 0x78, 0x2e, 0x63, 0x61, 0x73, 0x74, 0x6f,
+	0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x75, 0x74, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f,
+	0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x42, 0x28, 0x5a, 0x26,
+	0x63, 0x6f, 0x64, 0x65, 0x2e, 0x74, 0x76, 0x6c, 0x2e, 0x66, 0x79, 0x69, 0x2f, 0x74, 0x76, 0x69,
+	0x78, 0x2f, 0x63, 0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2d, 0x67, 0x6f, 0x3b, 0x63, 0x61, 0x73,
+	0x74, 0x6f, 0x72, 0x65, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+}
+
+var (
+	file_tvix_castore_protos_rpc_directory_proto_rawDescOnce sync.Once
+	file_tvix_castore_protos_rpc_directory_proto_rawDescData = file_tvix_castore_protos_rpc_directory_proto_rawDesc
+)
+
+func file_tvix_castore_protos_rpc_directory_proto_rawDescGZIP() []byte {
+	file_tvix_castore_protos_rpc_directory_proto_rawDescOnce.Do(func() {
+		file_tvix_castore_protos_rpc_directory_proto_rawDescData = protoimpl.X.CompressGZIP(file_tvix_castore_protos_rpc_directory_proto_rawDescData)
+	})
+	return file_tvix_castore_protos_rpc_directory_proto_rawDescData
+}
+
+var file_tvix_castore_protos_rpc_directory_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
+var file_tvix_castore_protos_rpc_directory_proto_goTypes = []interface{}{
+	(*GetDirectoryRequest)(nil),  // 0: tvix.castore.v1.GetDirectoryRequest
+	(*PutDirectoryResponse)(nil), // 1: tvix.castore.v1.PutDirectoryResponse
+	(*Directory)(nil),            // 2: tvix.castore.v1.Directory
+}
+var file_tvix_castore_protos_rpc_directory_proto_depIdxs = []int32{
+	0, // 0: tvix.castore.v1.DirectoryService.Get:input_type -> tvix.castore.v1.GetDirectoryRequest
+	2, // 1: tvix.castore.v1.DirectoryService.Put:input_type -> tvix.castore.v1.Directory
+	2, // 2: tvix.castore.v1.DirectoryService.Get:output_type -> tvix.castore.v1.Directory
+	1, // 3: tvix.castore.v1.DirectoryService.Put:output_type -> tvix.castore.v1.PutDirectoryResponse
+	2, // [2:4] is the sub-list for method output_type
+	0, // [0:2] is the sub-list for method input_type
+	0, // [0:0] is the sub-list for extension type_name
+	0, // [0:0] is the sub-list for extension extendee
+	0, // [0:0] is the sub-list for field type_name
+}
+
+func init() { file_tvix_castore_protos_rpc_directory_proto_init() }
+func file_tvix_castore_protos_rpc_directory_proto_init() {
+	if File_tvix_castore_protos_rpc_directory_proto != nil {
+		return
+	}
+	file_tvix_castore_protos_castore_proto_init()
+	if !protoimpl.UnsafeEnabled {
+		file_tvix_castore_protos_rpc_directory_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*GetDirectoryRequest); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_tvix_castore_protos_rpc_directory_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*PutDirectoryResponse); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+	}
+	file_tvix_castore_protos_rpc_directory_proto_msgTypes[0].OneofWrappers = []interface{}{
+		(*GetDirectoryRequest_Digest)(nil),
+	}
+	type x struct{}
+	out := protoimpl.TypeBuilder{
+		File: protoimpl.DescBuilder{
+			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+			RawDescriptor: file_tvix_castore_protos_rpc_directory_proto_rawDesc,
+			NumEnums:      0,
+			NumMessages:   2,
+			NumExtensions: 0,
+			NumServices:   1,
+		},
+		GoTypes:           file_tvix_castore_protos_rpc_directory_proto_goTypes,
+		DependencyIndexes: file_tvix_castore_protos_rpc_directory_proto_depIdxs,
+		MessageInfos:      file_tvix_castore_protos_rpc_directory_proto_msgTypes,
+	}.Build()
+	File_tvix_castore_protos_rpc_directory_proto = out.File
+	file_tvix_castore_protos_rpc_directory_proto_rawDesc = nil
+	file_tvix_castore_protos_rpc_directory_proto_goTypes = nil
+	file_tvix_castore_protos_rpc_directory_proto_depIdxs = nil
+}
diff --git a/tvix/castore-go/rpc_directory_grpc.pb.go b/tvix/castore-go/rpc_directory_grpc.pb.go
new file mode 100644
index 0000000000..98789fef83
--- /dev/null
+++ b/tvix/castore-go/rpc_directory_grpc.pb.go
@@ -0,0 +1,248 @@
+// SPDX-License-Identifier: MIT
+// Copyright © 2022 The Tvix Authors
+
+// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
+// versions:
+// - protoc-gen-go-grpc v1.3.0
+// - protoc             (unknown)
+// source: tvix/castore/protos/rpc_directory.proto
+
+package castorev1
+
+import (
+	context "context"
+	grpc "google.golang.org/grpc"
+	codes "google.golang.org/grpc/codes"
+	status "google.golang.org/grpc/status"
+)
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the grpc package it is being compiled against.
+// Requires gRPC-Go v1.32.0 or later.
+const _ = grpc.SupportPackageIsVersion7
+
+const (
+	DirectoryService_Get_FullMethodName = "/tvix.castore.v1.DirectoryService/Get"
+	DirectoryService_Put_FullMethodName = "/tvix.castore.v1.DirectoryService/Put"
+)
+
+// DirectoryServiceClient is the client API for DirectoryService service.
+//
+// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
+type DirectoryServiceClient interface {
+	// Get retrieves a stream of Directory messages, by using the lookup
+	// parameters in GetDirectoryRequest.
+	// Keep in mind multiple DirectoryNodes in different parts of the graph might
+	// have the same digest if they have the same underlying contents,
+	// so sending subsequent ones can be omitted.
+	//
+	// It is okay for certain implementations to only allow retrieval of
+	// Directory digests that are at the "root", aka the last element that's
+	// sent in a Put. This makes sense for implementations bundling closures of
+	// directories together in batches.
+	Get(ctx context.Context, in *GetDirectoryRequest, opts ...grpc.CallOption) (DirectoryService_GetClient, error)
+	// Put uploads a graph of Directory messages.
+	// Individual Directory messages need to be send in an order walking up
+	// from the leaves to the root - a Directory message can only refer to
+	// Directory messages previously sent in the same stream.
+	// Keep in mind multiple DirectoryNodes in different parts of the graph might
+	// have the same digest if they have the same underlying contents,
+	// so sending subsequent ones can be omitted.
+	// We might add a separate method, allowing to send partial graphs at a later
+	// time, if requiring to send the full graph turns out to be a problem.
+	Put(ctx context.Context, opts ...grpc.CallOption) (DirectoryService_PutClient, error)
+}
+
+type directoryServiceClient struct {
+	cc grpc.ClientConnInterface
+}
+
+func NewDirectoryServiceClient(cc grpc.ClientConnInterface) DirectoryServiceClient {
+	return &directoryServiceClient{cc}
+}
+
+func (c *directoryServiceClient) Get(ctx context.Context, in *GetDirectoryRequest, opts ...grpc.CallOption) (DirectoryService_GetClient, error) {
+	stream, err := c.cc.NewStream(ctx, &DirectoryService_ServiceDesc.Streams[0], DirectoryService_Get_FullMethodName, opts...)
+	if err != nil {
+		return nil, err
+	}
+	x := &directoryServiceGetClient{stream}
+	if err := x.ClientStream.SendMsg(in); err != nil {
+		return nil, err
+	}
+	if err := x.ClientStream.CloseSend(); err != nil {
+		return nil, err
+	}
+	return x, nil
+}
+
+type DirectoryService_GetClient interface {
+	Recv() (*Directory, error)
+	grpc.ClientStream
+}
+
+type directoryServiceGetClient struct {
+	grpc.ClientStream
+}
+
+func (x *directoryServiceGetClient) Recv() (*Directory, error) {
+	m := new(Directory)
+	if err := x.ClientStream.RecvMsg(m); err != nil {
+		return nil, err
+	}
+	return m, nil
+}
+
+func (c *directoryServiceClient) Put(ctx context.Context, opts ...grpc.CallOption) (DirectoryService_PutClient, error) {
+	stream, err := c.cc.NewStream(ctx, &DirectoryService_ServiceDesc.Streams[1], DirectoryService_Put_FullMethodName, opts...)
+	if err != nil {
+		return nil, err
+	}
+	x := &directoryServicePutClient{stream}
+	return x, nil
+}
+
+type DirectoryService_PutClient interface {
+	Send(*Directory) error
+	CloseAndRecv() (*PutDirectoryResponse, error)
+	grpc.ClientStream
+}
+
+type directoryServicePutClient struct {
+	grpc.ClientStream
+}
+
+func (x *directoryServicePutClient) Send(m *Directory) error {
+	return x.ClientStream.SendMsg(m)
+}
+
+func (x *directoryServicePutClient) CloseAndRecv() (*PutDirectoryResponse, error) {
+	if err := x.ClientStream.CloseSend(); err != nil {
+		return nil, err
+	}
+	m := new(PutDirectoryResponse)
+	if err := x.ClientStream.RecvMsg(m); err != nil {
+		return nil, err
+	}
+	return m, nil
+}
+
+// DirectoryServiceServer is the server API for DirectoryService service.
+// All implementations must embed UnimplementedDirectoryServiceServer
+// for forward compatibility
+type DirectoryServiceServer interface {
+	// Get retrieves a stream of Directory messages, by using the lookup
+	// parameters in GetDirectoryRequest.
+	// Keep in mind multiple DirectoryNodes in different parts of the graph might
+	// have the same digest if they have the same underlying contents,
+	// so sending subsequent ones can be omitted.
+	//
+	// It is okay for certain implementations to only allow retrieval of
+	// Directory digests that are at the "root", aka the last element that's
+	// sent in a Put. This makes sense for implementations bundling closures of
+	// directories together in batches.
+	Get(*GetDirectoryRequest, DirectoryService_GetServer) error
+	// Put uploads a graph of Directory messages.
+	// Individual Directory messages need to be send in an order walking up
+	// from the leaves to the root - a Directory message can only refer to
+	// Directory messages previously sent in the same stream.
+	// Keep in mind multiple DirectoryNodes in different parts of the graph might
+	// have the same digest if they have the same underlying contents,
+	// so sending subsequent ones can be omitted.
+	// We might add a separate method, allowing to send partial graphs at a later
+	// time, if requiring to send the full graph turns out to be a problem.
+	Put(DirectoryService_PutServer) error
+	mustEmbedUnimplementedDirectoryServiceServer()
+}
+
+// UnimplementedDirectoryServiceServer must be embedded to have forward compatible implementations.
+type UnimplementedDirectoryServiceServer struct {
+}
+
+func (UnimplementedDirectoryServiceServer) Get(*GetDirectoryRequest, DirectoryService_GetServer) error {
+	return status.Errorf(codes.Unimplemented, "method Get not implemented")
+}
+func (UnimplementedDirectoryServiceServer) Put(DirectoryService_PutServer) error {
+	return status.Errorf(codes.Unimplemented, "method Put not implemented")
+}
+func (UnimplementedDirectoryServiceServer) mustEmbedUnimplementedDirectoryServiceServer() {}
+
+// UnsafeDirectoryServiceServer may be embedded to opt out of forward compatibility for this service.
+// Use of this interface is not recommended, as added methods to DirectoryServiceServer will
+// result in compilation errors.
+type UnsafeDirectoryServiceServer interface {
+	mustEmbedUnimplementedDirectoryServiceServer()
+}
+
+func RegisterDirectoryServiceServer(s grpc.ServiceRegistrar, srv DirectoryServiceServer) {
+	s.RegisterService(&DirectoryService_ServiceDesc, srv)
+}
+
+func _DirectoryService_Get_Handler(srv interface{}, stream grpc.ServerStream) error {
+	m := new(GetDirectoryRequest)
+	if err := stream.RecvMsg(m); err != nil {
+		return err
+	}
+	return srv.(DirectoryServiceServer).Get(m, &directoryServiceGetServer{stream})
+}
+
+type DirectoryService_GetServer interface {
+	Send(*Directory) error
+	grpc.ServerStream
+}
+
+type directoryServiceGetServer struct {
+	grpc.ServerStream
+}
+
+func (x *directoryServiceGetServer) Send(m *Directory) error {
+	return x.ServerStream.SendMsg(m)
+}
+
+func _DirectoryService_Put_Handler(srv interface{}, stream grpc.ServerStream) error {
+	return srv.(DirectoryServiceServer).Put(&directoryServicePutServer{stream})
+}
+
+type DirectoryService_PutServer interface {
+	SendAndClose(*PutDirectoryResponse) error
+	Recv() (*Directory, error)
+	grpc.ServerStream
+}
+
+type directoryServicePutServer struct {
+	grpc.ServerStream
+}
+
+func (x *directoryServicePutServer) SendAndClose(m *PutDirectoryResponse) error {
+	return x.ServerStream.SendMsg(m)
+}
+
+func (x *directoryServicePutServer) Recv() (*Directory, error) {
+	m := new(Directory)
+	if err := x.ServerStream.RecvMsg(m); err != nil {
+		return nil, err
+	}
+	return m, nil
+}
+
+// DirectoryService_ServiceDesc is the grpc.ServiceDesc for DirectoryService service.
+// It's only intended for direct use with grpc.RegisterService,
+// and not to be introspected or modified (even as a copy)
+var DirectoryService_ServiceDesc = grpc.ServiceDesc{
+	ServiceName: "tvix.castore.v1.DirectoryService",
+	HandlerType: (*DirectoryServiceServer)(nil),
+	Methods:     []grpc.MethodDesc{},
+	Streams: []grpc.StreamDesc{
+		{
+			StreamName:    "Get",
+			Handler:       _DirectoryService_Get_Handler,
+			ServerStreams: true,
+		},
+		{
+			StreamName:    "Put",
+			Handler:       _DirectoryService_Put_Handler,
+			ClientStreams: true,
+		},
+	},
+	Metadata: "tvix/castore/protos/rpc_directory.proto",
+}
diff --git a/tvix/castore/Cargo.toml b/tvix/castore/Cargo.toml
new file mode 100644
index 0000000000..2797ef08f2
--- /dev/null
+++ b/tvix/castore/Cargo.toml
@@ -0,0 +1,118 @@
+[package]
+name = "tvix-castore"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+async-stream = "0.3.5"
+async-tempfile = "0.4.0"
+blake3 = { version = "1.3.1", features = ["rayon", "std", "traits-preview"] }
+bstr = "1.6.0"
+bytes = "1.4.0"
+data-encoding = "2.3.3"
+digest = "0.10.7"
+fastcdc = { version = "3.1.0", features = ["tokio"] }
+futures = "0.3.30"
+lazy_static = "1.4.0"
+object_store = { version = "0.9.1", features = ["http"] }
+parking_lot = "0.12.1"
+pin-project-lite = "0.2.13"
+prost = "0.12.1"
+sled = { version = "0.34.7" }
+thiserror = "1.0.38"
+tokio-stream = { version = "0.1.14", features = ["fs", "net"] }
+tokio-util = { version = "0.7.9", features = ["io", "io-util"] }
+tokio-tar = "0.3.1"
+tokio = { version = "1.32.0", features = ["fs", "macros", "net", "rt", "rt-multi-thread", "signal"] }
+tonic = "0.11.0"
+tower = "0.4.13"
+tracing = "0.1.37"
+url = "2.4.0"
+walkdir = "2.4.0"
+zstd = "0.13.0"
+serde = { version = "1.0.197", features = [ "derive" ] }
+serde_with = "3.7.0"
+serde_qs = "0.12.0"
+petgraph = "0.6.4"
+
+[dependencies.bigtable_rs]
+optional = true
+# https://github.com/liufuyang/bigtable_rs/pull/72
+git = "https://github.com/flokli/bigtable_rs"
+rev = "0af404741dfc40eb9fa99cf4d4140a09c5c20df7"
+
+[dependencies.fuse-backend-rs]
+optional = true
+version = "0.11.0"
+
+[dependencies.libc]
+optional = true
+version = "0.2.144"
+
+[dependencies.tonic-reflection]
+optional = true
+version = "0.11.0"
+
+[dependencies.vhost]
+optional = true
+version = "0.6"
+
+[dependencies.vhost-user-backend]
+optional = true
+version = "0.8"
+
+[dependencies.virtio-queue]
+optional = true
+version = "0.7"
+
+[dependencies.vm-memory]
+optional = true
+version = "0.10"
+
+[dependencies.vmm-sys-util]
+optional = true
+version = "0.11"
+
+[dependencies.virtio-bindings]
+optional = true
+version = "0.2.1"
+
+[build-dependencies]
+prost-build = "0.12.1"
+tonic-build = "0.11.0"
+
+[dev-dependencies]
+async-process = "2.1.0"
+rstest = "0.19.0"
+tempfile = "3.3.0"
+tokio-retry = "0.3.0"
+hex-literal = "0.4.1"
+rstest_reuse = "0.6.0"
+xattr = "1.3.1"
+
+[features]
+default = []
+cloud = [
+  "dep:bigtable_rs",
+  "object_store/aws",
+  "object_store/azure",
+  "object_store/gcp",
+]
+fs = ["dep:libc", "dep:fuse-backend-rs"]
+virtiofs = [
+  "fs",
+  "dep:vhost",
+  "dep:vhost-user-backend",
+  "dep:virtio-queue",
+  "dep:vm-memory",
+  "dep:vmm-sys-util",
+  "dep:virtio-bindings",
+  "fuse-backend-rs?/vhost-user-fs", # impl FsCacheReqHandler for SlaveFsCacheReq
+  "fuse-backend-rs?/virtiofs",
+]
+fuse = ["fs"]
+tonic-reflection = ["dep:tonic-reflection"]
+# Whether to run the integration tests.
+# Requires the following packages in $PATH:
+# cbtemulator, google-cloud-bigtable-tool
+integration = []
diff --git a/tvix/castore/build.rs b/tvix/castore/build.rs
new file mode 100644
index 0000000000..089c093e71
--- /dev/null
+++ b/tvix/castore/build.rs
@@ -0,0 +1,39 @@
+use std::io::Result;
+
+fn main() -> Result<()> {
+    #[allow(unused_mut)]
+    let mut builder = tonic_build::configure();
+
+    #[cfg(feature = "tonic-reflection")]
+    {
+        let out_dir = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap());
+        let descriptor_path = out_dir.join("tvix.castore.v1.bin");
+
+        builder = builder.file_descriptor_set_path(descriptor_path);
+    };
+
+    // https://github.com/hyperium/tonic/issues/908
+    let mut config = prost_build::Config::new();
+    config.bytes(["."]);
+    config.type_attribute(".", "#[derive(Eq, Hash)]");
+
+    builder
+        .build_server(true)
+        .build_client(true)
+        .emit_rerun_if_changed(false)
+        .compile_with_config(
+            config,
+            &[
+                "tvix/castore/protos/castore.proto",
+                "tvix/castore/protos/rpc_blobstore.proto",
+                "tvix/castore/protos/rpc_directory.proto",
+            ],
+            // If we are in running `cargo build` manually, using `../..` works fine,
+            // but in case we run inside a nix build, we need to instead point PROTO_ROOT
+            // to a sparseTree containing that structure.
+            &[match std::env::var_os("PROTO_ROOT") {
+                Some(proto_root) => proto_root.to_str().unwrap().to_owned(),
+                None => "../..".to_string(),
+            }],
+        )
+}
diff --git a/tvix/castore/default.nix b/tvix/castore/default.nix
new file mode 100644
index 0000000000..641d883760
--- /dev/null
+++ b/tvix/castore/default.nix
@@ -0,0 +1,23 @@
+{ depot, pkgs, ... }:
+
+(depot.tvix.crates.workspaceMembers.tvix-castore.build.override {
+  runTests = true;
+  testPreRun = ''
+    export SSL_CERT_FILE=${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt;
+  '';
+
+  # enable some optional features.
+  features = [ "default" "cloud" ];
+}).overrideAttrs (_: {
+  meta.ci.targets = [ "integration-tests" ];
+  passthru.integration-tests = depot.tvix.crates.workspaceMembers.tvix-castore.build.override {
+    runTests = true;
+    testPreRun = ''
+      export SSL_CERT_FILE=${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt;
+      export PATH="$PATH:${pkgs.lib.makeBinPath [pkgs.cbtemulator pkgs.google-cloud-bigtable-tool]}"
+    '';
+
+    # enable some optional features.
+    features = [ "default" "cloud" "integration" ];
+  };
+})
diff --git a/tvix/castore/docs/blobstore-chunking.md b/tvix/castore/docs/blobstore-chunking.md
new file mode 100644
index 0000000000..49bbe69275
--- /dev/null
+++ b/tvix/castore/docs/blobstore-chunking.md
@@ -0,0 +1,147 @@
+# BlobStore: Chunking & Verified Streaming
+
+`tvix-castore`'s BlobStore is a content-addressed storage system, using [blake3]
+as hash function.
+
+Returned data is fetched by using the digest as lookup key, and can be verified
+to be correct by feeding the received data through the hash function and
+ensuring it matches the digest initially used for the lookup.
+
+This means, data can be downloaded by any untrusted third-party as well, as the
+received data is validated to match the digest it was originally requested with.
+
+However, for larger blobs of data, having to download the entire blob at once is
+wasteful, if we only care about a part of the blob. Think about mounting a
+seekable data structure, like loop-mounting an .iso file, or doing partial reads
+in a large Parquet file, a column-oriented data format.
+
+> We want to have the possibility to *seek* into a larger file.
+
+This however shouldn't compromise on data integrity properties - we should not
+need to trust a peer we're downloading from to be "honest" about the partial
+data we're reading. We should be able to verify smaller reads.
+
+Especially when substituting from an untrusted third-party, we want to be able
+to detect quickly if that third-party is sending us wrong data, and terminate
+the connection early.
+
+## Chunking
+In content-addressed systems, this problem has historically been solved by
+breaking larger blobs into smaller chunks, which can be fetched individually,
+and making a hash of *this listing* the blob digest/identifier.
+
+ - BitTorrent for example breaks files up into smaller chunks, and maintains
+   a list of sha1 digests for each of these chunks. Magnet links contain a
+   digest over this listing as an identifier. (See [bittorrent-v2][here for
+   more details]).
+   With the identifier, a client can fetch the entire list, and then recursively
+   "unpack the graph" of nodes, until it ends up with a list of individual small
+   chunks, which can be fetched individually.
+ - Similarly, IPFS with its IPLD model builds up a Merkle DAG, and uses the
+   digest of the root node as an identitier.
+
+These approaches solve the problem of being able to fetch smaller chunks in a
+trusted fashion. They can also do some deduplication, in case there's the same
+leaf nodes same leaf nodes in multiple places.
+
+However, they also have a big disadvantage. The chunking parameters, and the
+"topology" of the graph structure itself "bleed" into the root hash of the
+entire data structure itself.
+
+Depending on the chunking parameters used, there's different representations for
+the same data, causing less data sharing/reuse in the overall system, in terms of how
+many chunks need to be downloaded vs. are already available locally, as well as
+how compact data is stored on-disk.
+
+This can be workarounded by agreeing on only a single way of chunking, but it's
+not pretty and misses a lot of deduplication potential.
+
+### Chunking in Tvix' Blobstore
+tvix-castore's BlobStore uses a hybrid approach to eliminate some of the
+disadvantages, while still being content-addressed internally, with the
+highlighted benefits.
+
+It uses [blake3] as hash function, and the blake3 digest of **the raw data
+itself** as an identifier (rather than some application-specific Merkle DAG that
+also embeds some chunking information).
+
+BLAKE3 is a tree hash where all left nodes fully populated, contrary to
+conventional serial hash functions. To be able to validate the hash of a node,
+one only needs the hash of the (2) children [^1], if any.
+
+This means one only needs to the root digest to validate a constructions, and these
+constructions can be sent [separately][bao-spec].
+
+This relieves us from the need of having to encode more granular chunking into
+our data model / identifier upfront, but can make this a mostly a transport/
+storage concern.
+
+For the some more description on the (remote) protocol, check
+`./blobstore-protocol.md`.
+
+#### Logical vs. physical chunking
+
+Due to the properties of the BLAKE3 hash function, we have logical blocks of
+1KiB, but this doesn't necessarily imply we need to restrict ourselves to these
+chunk sizes w.r.t. what "physical chunks" are sent over the wire between peers,
+or are stored on-disk.
+
+The only thing we need to be able to read and verify an arbitrary byte range is
+having the covering range of aligned 1K blocks, and a construction from the root
+digest to the 1K block.
+
+Note the intermediate hash tree can be further trimmed, [omitting][bao-tree]
+lower parts of the tree while still providing verified streaming - at the cost
+of having to fetch larger covering ranges of aligned blocks.
+
+Let's pick an example. We identify each KiB by a number here for illustrational
+purposes.
+
+Assuming we omit the last two layers of the hash tree, we end up with logical
+4KiB leaf chunks (`bao_shift` of `2`).
+
+For a blob of 14 KiB total size, we could fetch logical blocks `[0..=3]`,
+`[4..=7]`, `[8..=11]` and `[12..=13]` in an authenticated fashion:
+
+`[ 0 1 2 3 ] [ 4 5 6 7 ] [ 8 9 10 11 ] [ 12 13 ]`
+
+Assuming the server now informs us about the following physical chunking:
+
+```
+[ 0 1 ] [ 2 3 4 5 ] [ 6 ] [ 7 8 ] [ 9 10 11 12 13 14 15 ]`
+```
+
+If our application now wants to arbitrarily read from 0 until 4 (inclusive):
+
+```
+[ 0 1 ] [ 2 3 4 5 ] [ 6 ] [ 7 8 ] [ 9 10 11 12 13 14 15 ]
+ |-------------|
+
+```
+
+…we need to fetch physical chunks `[ 0 1 ]`, `[ 2 3 4 5 ]` and `[ 6 ] [ 7 8 ]`.
+
+
+`[ 0 1 ]` and `[ 2 3 4 5 ]` are obvious, they contain the data we're
+interested in.
+
+We however also need to fetch the physical chunks `[ 6 ]` and `[ 7 8 ]`, so we
+can assemble `[ 4 5 6 7 ]` to verify both logical chunks:
+
+```
+[ 0 1 ] [ 2 3 4 5 ] [ 6 ] [ 7 8 ] [ 9 10 11 12 13 14 15 ]
+^       ^           ^     ^
+|----4KiB----|------4KiB-----|
+```
+
+Each physical chunk fetched can be validated to have the blake3 digest that was
+communicated upfront, and can be stored in a client-side cache/storage, so
+subsequent / other requests for the same data will be fast(er).
+
+---
+
+[^1]: and the surrounding context, aka position inside the whole blob, which is available while verifying the tree
+[bittorrent-v2]: https://blog.libtorrent.org/2020/09/bittorrent-v2/
+[blake3]: https://github.com/BLAKE3-team/BLAKE3
+[bao-spec]: https://github.com/oconnor663/bao/blob/master/docs/spec.md
+[bao-tree]: https://github.com/n0-computer/bao-tree
diff --git a/tvix/castore/docs/blobstore-protocol.md b/tvix/castore/docs/blobstore-protocol.md
new file mode 100644
index 0000000000..048cafc3d8
--- /dev/null
+++ b/tvix/castore/docs/blobstore-protocol.md
@@ -0,0 +1,104 @@
+# BlobStore: Protocol / Composition
+
+This documents describes the protocol that BlobStore uses to substitute blobs
+other ("remote") BlobStores.
+
+How to come up with the blake3 digest of the blob to fetch is left to another
+layer in the stack.
+
+To put this into the context of Tvix as a Nix alternative, a blob represents an
+individual file inside a StorePath.
+In the Tvix Data Model, this is accomplished by having a `FileNode` (either the
+`root_node` in a `PathInfo` message, or a individual file inside a `Directory`
+message) encode a BLAKE3 digest.
+
+However, the whole infrastructure can be applied for other usecases requiring
+exchange/storage or access into data of which the blake3 digest is known.
+
+## Protocol and Interfaces
+As an RPC protocol, BlobStore currently uses gRPC.
+
+On the Rust side of things, every blob service implements the
+[`BlobService`](../src/blobservice/mod.rs) async trait, which isn't
+gRPC-specific.
+
+This `BlobService` trait provides functionality to check for existence of Blobs,
+read from blobs, and write new blobs.
+It also provides a method to ask for more granular chunks if they are available.
+
+In addition to some in-memory, on-disk and (soon) object-storage-based
+implementations, we also have a `BlobService` implementation that talks to a
+gRPC server, as well as a gRPC server wrapper component, which provides a gRPC
+service for anything implementing the `BlobService` trait.
+
+This makes it very easy to talk to a remote `BlobService`, which does not even
+need to be written in the same language, as long it speaks the same gRPC
+protocol.
+
+It also puts very little requirements on someone implementing a new
+`BlobService`, and how its internal storage or chunking algorithm looks like.
+
+The gRPC protocol is documented in `../protos/rpc_blobstore.proto`.
+Contrary to the `BlobService` trait, it does not have any options for seeking/
+ranging, as it's more desirable to provide this through chunking (see also
+`./blobstore-chunking.md`).
+
+## Composition
+Different `BlobStore` are supposed to be "composed"/"layered" to express
+caching, multiple local and remote sources.
+
+The fronting interface can be the same, it'd just be multiple "tiers" that can
+respond to requests, depending on where the data resides. [^1]
+
+This makes it very simple for consumers, as they don't need to be aware of the
+entire substitutor config.
+
+The flexibility of this doesn't need to be exposed to the user in the default
+case; in most cases we should be fine with some form of on-disk storage and a
+bunch of substituters with different priorities.
+
+### gRPC Clients
+Clients are encouraged to always read blobs in a chunked fashion (asking for a
+list of chunks for a blob via `BlobService.Stat()`, then fetching chunks via
+`BlobService.Read()` as needed), instead of directly reading the entire blob via
+`BlobService.Read()`.
+
+In a composition setting, this provides opportunity for caching, and avoids
+downloading some chunks if they're already present locally (for example, because
+they were already downloaded by reading from a similar blob earlier).
+
+It also removes the need for seeking to be a part of the gRPC protocol
+alltogether, as chunks are supposed to be "reasonably small" [^2].
+
+There's some further optimization potential, a `BlobService.Stat()` request
+could tell the server it's happy with very small blobs just being inlined in
+an additional additional field in the response, which would allow clients to
+populate their local chunk store in a single roundtrip.
+
+## Verified Streaming
+As already described in `./docs/blobstore-chunking.md`, the physical chunk
+information sent in a `BlobService.Stat()` response is still sufficient to fetch
+in an authenticated fashion.
+
+The exact protocol and formats are still a bit in flux, but here's some notes:
+
+ - `BlobService.Stat()` request gets a `send_bao` field (bool), signalling a
+   [BAO][bao-spec] should be sent. Could also be `bao_shift` integer, signalling
+   how detailed (down to the leaf chunks) it should go.
+   The exact format (and request fields) still need to be defined, edef has some
+   ideas around omitting some intermediate hash nodes over the wire and
+   recomputing them, reducing size by another ~50% over [bao-tree].
+ - `BlobService.Stat()` response gets some bao-related fields (`bao_shift`
+   field, signalling the actual format/shift level the server replies with, the
+   actual bao, and maybe some format specifier).
+   It would be nice to also be compatible with the baos used by [iroh], so we
+   can provide an implementation using it too.
+
+---
+
+[^1]: We might want to have some backchannel, so it becomes possible to provide
+      feedback to the user that something is downloaded.
+[^2]: Something between 512K-4M, TBD.
+[bao-spec]: https://github.com/oconnor663/bao/blob/master/docs/spec.md
+[bao-tree]: https://github.com/n0-computer/bao-tree
+[iroh]: https://github.com/n0-computer/iroh
diff --git a/tvix/castore/docs/data-model.md b/tvix/castore/docs/data-model.md
new file mode 100644
index 0000000000..2df6761aae
--- /dev/null
+++ b/tvix/castore/docs/data-model.md
@@ -0,0 +1,50 @@
+# Data model
+
+This provides some more notes on the fields used in castore.proto.
+
+See `//tvix/store/docs/api.md` for the full context.
+
+## Directory message
+`Directory` messages use the blake3 hash of their canonical protobuf
+serialization as its identifier.
+
+A `Directory` message contains three lists, `directories`, `files` and
+`symlinks`, holding `DirectoryNode`, `FileNode` and `SymlinkNode` messages
+respectively. They describe all the direct child elements that are contained in
+a directory.
+
+All three message types have a `name` field, specifying the (base)name of the
+element (which MUST not contain slashes or null bytes, and MUST not be '.' or '..').
+For reproducibility reasons, the lists MUST be sorted by that name and also
+MUST be unique across all three lists.
+
+In addition to the `name` field, the various *Node messages have the following
+fields:
+
+## DirectoryNode
+A `DirectoryNode` message represents a child directory.
+
+It has a `digest` field, which points to the identifier of another `Directory`
+message, making a `Directory` a merkle tree (or strictly speaking, a graph, as
+two elements pointing to a child directory with the same contents would point
+to the same `Directory` message.
+
+There's also a `size` field, containing the (total) number of all child
+elements in the referenced `Directory`, which helps for inode calculation.
+
+## FileNode
+A `FileNode` message represents a child (regular) file.
+
+Its `digest` field contains the blake3 hash of the file contents. It can be
+looked up in the `BlobService`.
+
+The `size` field contains the size of the blob the `digest` field refers to.
+
+The `executable` field specifies whether the file should be marked as
+executable or not.
+
+## SymlinkNode
+A `SymlinkNode` message represents a child symlink.
+
+In addition to the `name` field, the only additional field is the `target`,
+which is a string containing the target of the symlink.
diff --git a/tvix/castore/docs/why-not-git-trees.md b/tvix/castore/docs/why-not-git-trees.md
new file mode 100644
index 0000000000..fd46252cf5
--- /dev/null
+++ b/tvix/castore/docs/why-not-git-trees.md
@@ -0,0 +1,57 @@
+## Why not git tree objects?
+
+We've been experimenting with (some variations of) the git tree and object
+format, and ultimately decided against using it as an internal format, and
+instead adapted the one documented in the other documents here.
+
+While the tvix-store API protocol shares some similarities with the format used
+in git for trees and objects, the git one has shown some significant
+disadvantages:
+
+### The binary encoding itself
+
+#### trees
+The git tree object format is a very binary, error-prone and
+"made-to-be-read-and-written-from-C" format.
+
+Tree objects are a combination of null-terminated strings, and fields of known
+length. References to other tree objects use the literal sha1 hash of another
+tree object in this encoding.
+Extensions of the format/changes are very hard to do right, because parsers are
+not aware they might be parsing something different.
+
+The tvix-store protocol uses a canonical protobuf serialization, and uses
+the [blake3][blake3] hash of that serialization to point to other `Directory`
+messages.
+It's both compact and with a wide range of libraries for encoders and decoders
+in many programming languages.
+The choice of protobuf makes it easy to add new fields, and make old clients
+aware of some unknown fields being detected [^adding-fields].
+
+#### blob
+On disk, git blob objects start with a "blob" prefix, then the size of the
+payload, and then the data itself. The hash of a blob is the literal sha1sum
+over all of this - which makes it something very git specific to request for.
+
+tvix-store simply uses the [blake3][blake3] hash of the literal contents
+when referring to a file/blob, which makes it very easy to ask other data
+sources for the same data, as no git-specific payload is included in the hash.
+This also plays very well together with things like [iroh][iroh-discussion],
+which plans to provide a way to substitute (large)blobs by their blake3 hash
+over the IPFS network.
+
+In addition to that, [blake3][blake3] makes it possible to do
+[verified streaming][bao], as already described in other parts of the
+documentation.
+
+The git tree object format uses sha1 both for references to other trees and
+hashes of blobs, which isn't really a hash function to fundamentally base
+everything on in 2023.
+The [migration to sha256][git-sha256] also has been dead for some years now,
+and it's unclear how a "blake3" version of this would even look like.
+
+[bao]: https://github.com/oconnor663/bao
+[blake3]: https://github.com/BLAKE3-team/BLAKE3
+[git-sha256]: https://git-scm.com/docs/hash-function-transition/
+[iroh-discussion]: https://github.com/n0-computer/iroh/discussions/707#discussioncomment-5070197
+[^adding-fields]: Obviously, adding new fields will change hashes, but it's something that's easy to detect.
\ No newline at end of file
diff --git a/tvix/castore/protos/LICENSE b/tvix/castore/protos/LICENSE
new file mode 100644
index 0000000000..2034ada6fd
--- /dev/null
+++ b/tvix/castore/protos/LICENSE
@@ -0,0 +1,21 @@
+Copyright © The Tvix Authors
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+“Software”), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
diff --git a/tvix/castore/protos/castore.proto b/tvix/castore/protos/castore.proto
new file mode 100644
index 0000000000..1ef4044045
--- /dev/null
+++ b/tvix/castore/protos/castore.proto
@@ -0,0 +1,71 @@
+// SPDX-FileCopyrightText: edef <edef@unfathomable.blue>
+// SPDX-License-Identifier: OSL-3.0 OR MIT OR Apache-2.0
+
+syntax = "proto3";
+
+package tvix.castore.v1;
+
+option go_package = "code.tvl.fyi/tvix/castore-go;castorev1";
+
+// A Directory can contain Directory, File or Symlink nodes.
+// Each of these nodes have a name attribute, which is the basename in that
+// directory and node type specific attributes.
+// The name attribute:
+//  - MUST not contain slashes or null bytes
+//  - MUST not be '.' or '..'
+//  - MUST be unique across all three lists
+// Elements in each list need to be lexicographically ordered by the name
+// attribute.
+message Directory {
+  repeated DirectoryNode directories = 1;
+  repeated FileNode files = 2;
+  repeated SymlinkNode symlinks = 3;
+}
+
+// A DirectoryNode represents a directory in a Directory.
+message DirectoryNode {
+  // The (base)name of the directory
+  bytes name = 1;
+  // The blake3 hash of a Directory message, serialized in protobuf canonical form.
+  bytes digest = 2;
+  // Number of child elements in the Directory referred to by `digest`.
+  // Calculated by summing up the numbers of `directories`, `files` and
+  // `symlinks`, and for each directory, its size field. Used for inode number
+  // calculation.
+  // This field is precisely as verifiable as any other Merkle tree edge.
+  // Resolve `digest`, and you can compute it incrementally. Resolve the entire
+  // tree, and you can fully compute it from scratch.
+  // A credulous implementation won't reject an excessive size, but this is
+  // harmless: you'll have some ordinals without nodes. Undersizing is obvious
+  // and easy to reject: you won't have an ordinal for some nodes.
+  uint64 size = 3;
+}
+
+// A FileNode represents a regular or executable file in a Directory.
+message FileNode {
+  // The (base)name of the file
+  bytes name = 1;
+  // The blake3 digest of the file contents
+  bytes digest = 2;
+  // The file content size
+  uint64 size = 3;
+  // Whether the file is executable
+  bool executable = 4;
+}
+
+// A SymlinkNode represents a symbolic link in a Directory.
+message SymlinkNode {
+  // The (base)name of the symlink
+  bytes name = 1;
+  // The target of the symlink.
+  bytes target = 2;
+}
+
+// A Node is either a DirectoryNode, FileNode or SymlinkNode.
+message Node {
+  oneof node {
+    DirectoryNode directory = 1;
+    FileNode file = 2;
+    SymlinkNode symlink = 3;
+  }
+}
diff --git a/tvix/castore/protos/default.nix b/tvix/castore/protos/default.nix
new file mode 100644
index 0000000000..feef55690f
--- /dev/null
+++ b/tvix/castore/protos/default.nix
@@ -0,0 +1,54 @@
+{ depot, pkgs, ... }:
+let
+  protos = depot.nix.sparseTree {
+    name = "castore-protos";
+    root = depot.path.origSrc;
+    paths = [
+      ./castore.proto
+      ./rpc_blobstore.proto
+      ./rpc_directory.proto
+      ../../../buf.yaml
+      ../../../buf.gen.yaml
+    ];
+  };
+in
+depot.nix.readTree.drvTargets {
+  inherit protos;
+
+  # Lints and ensures formatting of the proto files.
+  check = pkgs.stdenv.mkDerivation {
+    name = "proto-check";
+    src = protos;
+
+    nativeBuildInputs = [
+      pkgs.buf
+    ];
+
+    buildPhase = ''
+      export HOME=$TMPDIR
+      buf lint
+      buf format -d --exit-code
+      touch $out
+    '';
+  };
+
+  # Produces the golang bindings.
+  go-bindings = pkgs.stdenv.mkDerivation {
+    name = "go-bindings";
+    src = protos;
+
+    nativeBuildInputs = [
+      pkgs.buf
+      pkgs.protoc-gen-go
+      pkgs.protoc-gen-go-grpc
+    ];
+
+    buildPhase = ''
+      export HOME=$TMPDIR
+      buf generate
+
+      mkdir -p $out
+      cp tvix/castore/protos/*.pb.go $out/
+    '';
+  };
+}
diff --git a/tvix/castore/protos/rpc_blobstore.proto b/tvix/castore/protos/rpc_blobstore.proto
new file mode 100644
index 0000000000..eebe39ace7
--- /dev/null
+++ b/tvix/castore/protos/rpc_blobstore.proto
@@ -0,0 +1,85 @@
+// SPDX-License-Identifier: MIT
+// Copyright © 2022 The Tvix Authors
+syntax = "proto3";
+
+package tvix.castore.v1;
+
+option go_package = "code.tvl.fyi/tvix/castore-go;castorev1";
+
+// BlobService allows reading (or uploading) content-addressed blobs of data.
+// BLAKE3 is used as a hashing function for the data. Uploading a blob will
+// return the BLAKE3 digest of it, and that's the identifier used to Read/Stat
+// them too.
+service BlobService {
+  // Stat can be used to check for the existence of a blob, as well as
+  // gathering more data about it, like more granular chunking information
+  // or baos.
+  // Server implementations are not required to provide more granular chunking
+  // information, especially if the digest specified in `StatBlobRequest` is
+  // already a chunk of a blob.
+  rpc Stat(StatBlobRequest) returns (StatBlobResponse);
+
+  // Read allows reading (all) data of a blob/chunk by the BLAKE3 digest of
+  // its contents.
+  // If the backend communicated more granular chunks in the `Stat` request,
+  // this can also be used to read chunks.
+  // This request returns a stream of BlobChunk, which is just a container for
+  // a stream of bytes.
+  // The server may decide on whatever chunking it may seem fit as a size for
+  // the individual BlobChunk sent in the response stream, this is mostly to
+  // keep individual messages at a manageable size.
+  rpc Read(ReadBlobRequest) returns (stream BlobChunk);
+
+  // Put uploads a Blob, by reading a stream of bytes.
+  //
+  // The way the data is chunked up in individual BlobChunk messages sent in
+  // the stream has no effect on how the server ends up chunking blobs up, if
+  // it does at all.
+  rpc Put(stream BlobChunk) returns (PutBlobResponse);
+}
+
+message StatBlobRequest {
+  // The blake3 digest of the blob requested
+  bytes digest = 1;
+
+  // Whether the server should reply with a list of more granular chunks.
+  bool send_chunks = 2;
+
+  // Whether the server should reply with a bao.
+  bool send_bao = 3;
+}
+
+message StatBlobResponse {
+  // If `send_chunks` was set to true, this MAY contain a list of more
+  // granular chunks, which then may be read individually via the `Read`
+  // method.
+  repeated ChunkMeta chunks = 2;
+
+  message ChunkMeta {
+    // Digest of that specific chunk
+    bytes digest = 1;
+
+    // Length of that chunk, in bytes.
+    uint64 size = 2;
+  }
+
+  // If `send_bao` was set to true, this MAY contain a outboard bao.
+  // The exact format and message types here will still be fleshed out.
+  bytes bao = 3;
+}
+
+message ReadBlobRequest {
+  // The blake3 digest of the blob or chunk requested
+  bytes digest = 1;
+}
+
+// This represents some bytes of a blob.
+// Blobs are sent in smaller chunks to keep message sizes manageable.
+message BlobChunk {
+  bytes data = 1;
+}
+
+message PutBlobResponse {
+  // The blake3 digest of the data that was sent.
+  bytes digest = 1;
+}
diff --git a/tvix/castore/protos/rpc_directory.proto b/tvix/castore/protos/rpc_directory.proto
new file mode 100644
index 0000000000..f4f41c433a
--- /dev/null
+++ b/tvix/castore/protos/rpc_directory.proto
@@ -0,0 +1,53 @@
+// SPDX-License-Identifier: MIT
+// Copyright © 2022 The Tvix Authors
+syntax = "proto3";
+
+package tvix.castore.v1;
+
+import "tvix/castore/protos/castore.proto";
+
+option go_package = "code.tvl.fyi/tvix/castore-go;castorev1";
+
+service DirectoryService {
+  // Get retrieves a stream of Directory messages, by using the lookup
+  // parameters in GetDirectoryRequest.
+  // Keep in mind multiple DirectoryNodes in different parts of the graph might
+  // have the same digest if they have the same underlying contents,
+  // so sending subsequent ones can be omitted.
+  //
+  // It is okay for certain implementations to only allow retrieval of
+  // Directory digests that are at the "root", aka the last element that's
+  // sent in a Put. This makes sense for implementations bundling closures of
+  // directories together in batches.
+  rpc Get(GetDirectoryRequest) returns (stream Directory);
+
+  // Put uploads a graph of Directory messages.
+  // Individual Directory messages need to be send in an order walking up
+  // from the leaves to the root - a Directory message can only refer to
+  // Directory messages previously sent in the same stream.
+  // Keep in mind multiple DirectoryNodes in different parts of the graph might
+  // have the same digest if they have the same underlying contents,
+  // so sending subsequent ones can be omitted.
+  // We might add a separate method, allowing to send partial graphs at a later
+  // time, if requiring to send the full graph turns out to be a problem.
+  rpc Put(stream Directory) returns (PutDirectoryResponse);
+}
+
+message GetDirectoryRequest {
+  oneof by_what {
+    // The blake3 hash of the (root) Directory message, serialized in
+    // protobuf canonical form.
+    // Keep in mind this can be a subtree of another root.
+    bytes digest = 1;
+  }
+
+  // If set to true, recursively resolve all child Directory messages.
+  // Directory messages SHOULD be streamed in a recursive breadth-first walk,
+  // but other orders are also fine, as long as Directory messages are only
+  // sent after they are referred to from previously sent Directory messages.
+  bool recursive = 2;
+}
+
+message PutDirectoryResponse {
+  bytes root_digest = 1;
+}
diff --git a/tvix/castore/src/blobservice/chunked_reader.rs b/tvix/castore/src/blobservice/chunked_reader.rs
new file mode 100644
index 0000000000..6e8355874b
--- /dev/null
+++ b/tvix/castore/src/blobservice/chunked_reader.rs
@@ -0,0 +1,496 @@
+use futures::{ready, TryStreamExt};
+use pin_project_lite::pin_project;
+use tokio::io::{AsyncRead, AsyncSeekExt};
+use tokio_stream::StreamExt;
+use tokio_util::io::{ReaderStream, StreamReader};
+use tracing::{instrument, trace, warn};
+
+use crate::B3Digest;
+use std::{cmp::Ordering, pin::Pin};
+
+use super::{BlobReader, BlobService};
+
+pin_project! {
+    /// ChunkedReader provides a chunk-aware [BlobReader], so allows reading and
+    /// seeking into a blob.
+    /// It internally holds a [ChunkedBlob], which is storing chunk information
+    /// able to emit a reader seeked to a specific position whenever we need to seek.
+    pub struct ChunkedReader<BS> {
+        chunked_blob: ChunkedBlob<BS>,
+
+        #[pin]
+        r: Box<dyn AsyncRead + Unpin + Send>,
+
+        pos: u64,
+    }
+}
+
+impl<BS> ChunkedReader<BS>
+where
+    BS: AsRef<dyn BlobService> + Clone + 'static + Send,
+{
+    /// Construct a new [ChunkedReader], by retrieving a list of chunks (their
+    /// blake3 digests and chunk sizes)
+    pub fn from_chunks(chunks_it: impl Iterator<Item = (B3Digest, u64)>, blob_service: BS) -> Self {
+        let chunked_blob = ChunkedBlob::from_iter(chunks_it, blob_service);
+        let r = chunked_blob.reader_skipped_offset(0);
+
+        Self {
+            chunked_blob,
+            r,
+            pos: 0,
+        }
+    }
+}
+
+/// ChunkedReader implements BlobReader.
+impl<BS> BlobReader for ChunkedReader<BS> where BS: Send + Clone + 'static + AsRef<dyn BlobService> {}
+
+impl<BS> tokio::io::AsyncRead for ChunkedReader<BS>
+where
+    BS: AsRef<dyn BlobService> + Clone + 'static,
+{
+    fn poll_read(
+        self: std::pin::Pin<&mut Self>,
+        cx: &mut std::task::Context<'_>,
+        buf: &mut tokio::io::ReadBuf<'_>,
+    ) -> std::task::Poll<std::io::Result<()>> {
+        // The amount of data read can be determined by the increase
+        // in the length of the slice returned by `ReadBuf::filled`.
+        let filled_before = buf.filled().len();
+
+        let this = self.project();
+
+        ready!(this.r.poll_read(cx, buf))?;
+        let bytes_read = buf.filled().len() - filled_before;
+        *this.pos += bytes_read as u64;
+
+        Ok(()).into()
+    }
+}
+
+impl<BS> tokio::io::AsyncSeek for ChunkedReader<BS>
+where
+    BS: AsRef<dyn BlobService> + Clone + Send + 'static,
+{
+    #[instrument(skip(self), err(Debug))]
+    fn start_seek(self: Pin<&mut Self>, position: std::io::SeekFrom) -> std::io::Result<()> {
+        let total_len = self.chunked_blob.blob_length();
+        let mut this = self.project();
+
+        let absolute_offset: u64 = match position {
+            std::io::SeekFrom::Start(from_start) => from_start,
+            std::io::SeekFrom::End(from_end) => {
+                // note from_end is i64, not u64, so this is usually negative.
+                total_len.checked_add_signed(from_end).ok_or_else(|| {
+                    std::io::Error::new(
+                        std::io::ErrorKind::InvalidInput,
+                        "over/underflow while seeking",
+                    )
+                })?
+            }
+            std::io::SeekFrom::Current(from_current) => {
+                // note from_end is i64, not u64, so this can be positive or negative.
+                (*this.pos)
+                    .checked_add_signed(from_current)
+                    .ok_or_else(|| {
+                        std::io::Error::new(
+                            std::io::ErrorKind::InvalidInput,
+                            "over/underflow while seeking",
+                        )
+                    })?
+            }
+        };
+
+        // check if the position actually did change.
+        if absolute_offset != *this.pos {
+            // ensure the new position still is inside the file.
+            if absolute_offset > total_len {
+                Err(std::io::Error::new(
+                    std::io::ErrorKind::InvalidInput,
+                    "seeked beyond EOF",
+                ))?
+            }
+
+            // Update the position and the internal reader.
+            *this.pos = absolute_offset;
+
+            // FUTUREWORK: if we can seek forward, avoid re-assembling.
+            // At least if it's still in the same chunk?
+            *this.r = this.chunked_blob.reader_skipped_offset(absolute_offset);
+        }
+
+        Ok(())
+    }
+
+    fn poll_complete(
+        self: Pin<&mut Self>,
+        _cx: &mut std::task::Context<'_>,
+    ) -> std::task::Poll<std::io::Result<u64>> {
+        std::task::Poll::Ready(Ok(self.pos))
+    }
+}
+
+/// Holds a list of blake3 digest for individual chunks (and their sizes).
+/// Is able to construct a Reader that seeked to a certain offset, which
+/// is useful to construct a BlobReader (that implements AsyncSeek).
+/// - the current chunk index, and a Custor<Vec<u8>> holding the data of that chunk.
+struct ChunkedBlob<BS> {
+    blob_service: BS,
+    chunks: Vec<(u64, u64, B3Digest)>,
+}
+
+impl<BS> ChunkedBlob<BS>
+where
+    BS: AsRef<dyn BlobService> + Clone + 'static + Send,
+{
+    /// Constructs [Self] from a list of blake3 digests of chunks and their
+    /// sizes, and a reference to a blob service.
+    /// Initializing it with an empty list is disallowed.
+    fn from_iter(chunks_it: impl Iterator<Item = (B3Digest, u64)>, blob_service: BS) -> Self {
+        let mut chunks = Vec::new();
+        let mut offset: u64 = 0;
+
+        for (chunk_digest, chunk_size) in chunks_it {
+            chunks.push((offset, chunk_size, chunk_digest));
+            offset += chunk_size;
+        }
+
+        assert!(
+            !chunks.is_empty(),
+            "Chunks must be provided, don't use this for blobs without chunks"
+        );
+
+        Self {
+            blob_service,
+            chunks,
+        }
+    }
+
+    /// Returns the length of the blob.
+    fn blob_length(&self) -> u64 {
+        self.chunks
+            .last()
+            .map(|(chunk_offset, chunk_size, _)| chunk_offset + chunk_size)
+            .unwrap_or(0)
+    }
+
+    /// For a given position pos, return the chunk containing the data.
+    /// In case this would range outside the blob, None is returned.
+    #[instrument(level = "trace", skip(self), ret)]
+    fn get_chunk_idx_for_position(&self, pos: u64) -> Option<usize> {
+        // FUTUREWORK: benchmark when to use linear search, binary_search and BTreeSet
+        self.chunks
+            .binary_search_by(|(chunk_start_pos, chunk_size, _)| {
+                if chunk_start_pos + chunk_size <= pos {
+                    Ordering::Less
+                } else if *chunk_start_pos > pos {
+                    Ordering::Greater
+                } else {
+                    Ordering::Equal
+                }
+            })
+            .ok()
+    }
+
+    /// Returns a stream of bytes of the data in that blob.
+    /// It internally assembles a stream reading from each chunk (skipping over
+    /// chunks containing irrelevant data).
+    /// From the first relevant chunk, the irrelevant bytes are skipped too.
+    /// The returned boxed thing does not implement AsyncSeek on its own, but
+    /// ChunkedReader does.
+    #[instrument(level = "trace", skip(self))]
+    fn reader_skipped_offset(&self, offset: u64) -> Box<dyn tokio::io::AsyncRead + Send + Unpin> {
+        if offset == self.blob_length() {
+            return Box::new(std::io::Cursor::new(vec![]));
+        }
+        // construct a stream of all chunks starting with the given offset
+        let start_chunk_idx = self
+            .get_chunk_idx_for_position(offset)
+            .expect("outside of blob");
+        // It's ok to panic here, we can only reach this by seeking, and seeking should already reject out-of-file seeking.
+
+        let skip_first_chunk_bytes = (offset - self.chunks[start_chunk_idx].0) as usize;
+
+        let blob_service = self.blob_service.clone();
+        let chunks: Vec<_> = self.chunks[start_chunk_idx..].to_vec();
+        let readers_stream = tokio_stream::iter(chunks.into_iter().enumerate()).map(
+            move |(nth_chunk, (_chunk_start_offset, chunk_size, chunk_digest))| {
+                let chunk_digest = chunk_digest.to_owned();
+                let blob_service = blob_service.clone();
+                async move {
+                    trace!(chunk_size=%chunk_size, chunk_digest=%chunk_digest, "open_read on chunk in stream");
+                    let mut blob_reader = blob_service
+                        .as_ref()
+                        .open_read(&chunk_digest.to_owned())
+                        .await?
+                        .ok_or_else(|| {
+                            warn!(chunk.digest = %chunk_digest, "chunk not found");
+                            std::io::Error::new(std::io::ErrorKind::NotFound, "chunk not found")
+                        })?;
+
+                    // iff this is the first chunk in the stream, skip by skip_first_chunk_bytes
+                    if nth_chunk == 0 && skip_first_chunk_bytes > 0 {
+                        blob_reader
+                            .seek(std::io::SeekFrom::Start(skip_first_chunk_bytes as u64))
+                            .await?;
+                    }
+                    Ok::<_, std::io::Error>(blob_reader)
+                }
+            },
+        );
+
+        // convert the stream of readers to a stream of streams of byte chunks
+        let bytes_streams = readers_stream.then(|elem| async { elem.await.map(ReaderStream::new) });
+
+        // flatten into one stream of byte chunks
+        let bytes_stream = bytes_streams.try_flatten();
+
+        // convert into AsyncRead
+        Box::new(StreamReader::new(Box::pin(bytes_stream)))
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use std::{io::SeekFrom, sync::Arc};
+
+    use crate::{
+        blobservice::{chunked_reader::ChunkedReader, BlobService, MemoryBlobService},
+        B3Digest,
+    };
+    use hex_literal::hex;
+    use lazy_static::lazy_static;
+    use tokio::io::{AsyncReadExt, AsyncSeekExt};
+
+    const CHUNK_1: [u8; 2] = hex!("0001");
+    const CHUNK_2: [u8; 4] = hex!("02030405");
+    const CHUNK_3: [u8; 1] = hex!("06");
+    const CHUNK_4: [u8; 2] = hex!("0708");
+    const CHUNK_5: [u8; 7] = hex!("090a0b0c0d0e0f");
+
+    lazy_static! {
+        // `[ 0 1 ] [ 2 3 4 5 ] [ 6 ] [ 7 8 ] [ 9 10 11 12 13 14 15 ]`
+        pub static ref CHUNK_1_DIGEST: B3Digest = blake3::hash(&CHUNK_1).as_bytes().into();
+        pub static ref CHUNK_2_DIGEST: B3Digest = blake3::hash(&CHUNK_2).as_bytes().into();
+        pub static ref CHUNK_3_DIGEST: B3Digest = blake3::hash(&CHUNK_3).as_bytes().into();
+        pub static ref CHUNK_4_DIGEST: B3Digest = blake3::hash(&CHUNK_4).as_bytes().into();
+        pub static ref CHUNK_5_DIGEST: B3Digest = blake3::hash(&CHUNK_5).as_bytes().into();
+        pub static ref BLOB_1_LIST: [(B3Digest, u64); 5] = [
+            (CHUNK_1_DIGEST.clone(), 2),
+            (CHUNK_2_DIGEST.clone(), 4),
+            (CHUNK_3_DIGEST.clone(), 1),
+            (CHUNK_4_DIGEST.clone(), 2),
+            (CHUNK_5_DIGEST.clone(), 7),
+        ];
+    }
+
+    use super::ChunkedBlob;
+
+    /// ensure the start offsets are properly calculated.
+    #[test]
+    fn from_iter() {
+        let cb = ChunkedBlob::from_iter(
+            BLOB_1_LIST.clone().into_iter(),
+            Arc::new(MemoryBlobService::default()) as Arc<dyn BlobService>,
+        );
+
+        assert_eq!(
+            cb.chunks,
+            Vec::from_iter([
+                (0, 2, CHUNK_1_DIGEST.clone()),
+                (2, 4, CHUNK_2_DIGEST.clone()),
+                (6, 1, CHUNK_3_DIGEST.clone()),
+                (7, 2, CHUNK_4_DIGEST.clone()),
+                (9, 7, CHUNK_5_DIGEST.clone()),
+            ])
+        );
+    }
+
+    /// ensure ChunkedBlob can't be used with an empty list of chunks
+    #[test]
+    #[should_panic]
+    fn from_iter_empty() {
+        ChunkedBlob::from_iter(
+            [].into_iter(),
+            Arc::new(MemoryBlobService::default()) as Arc<dyn BlobService>,
+        );
+    }
+
+    /// ensure the right chunk is selected
+    #[test]
+    fn chunk_idx_for_position() {
+        let cb = ChunkedBlob::from_iter(
+            BLOB_1_LIST.clone().into_iter(),
+            Arc::new(MemoryBlobService::default()) as Arc<dyn BlobService>,
+        );
+
+        assert_eq!(Some(0), cb.get_chunk_idx_for_position(0), "start of blob");
+
+        assert_eq!(
+            Some(0),
+            cb.get_chunk_idx_for_position(1),
+            "middle of first chunk"
+        );
+        assert_eq!(
+            Some(1),
+            cb.get_chunk_idx_for_position(2),
+            "beginning of second chunk"
+        );
+
+        assert_eq!(
+            Some(4),
+            cb.get_chunk_idx_for_position(15),
+            "right before the end of the blob"
+        );
+        assert_eq!(
+            None,
+            cb.get_chunk_idx_for_position(16),
+            "right outside the blob"
+        );
+        assert_eq!(
+            None,
+            cb.get_chunk_idx_for_position(100),
+            "way outside the blob"
+        );
+    }
+
+    /// returns a blobservice with all chunks in BLOB_1 present.
+    async fn gen_blobservice_blob1() -> Arc<dyn BlobService> {
+        let blob_service = Arc::new(MemoryBlobService::default()) as Arc<dyn BlobService>;
+
+        // seed blob service with all chunks
+        for blob_contents in [
+            CHUNK_1.to_vec(),
+            CHUNK_2.to_vec(),
+            CHUNK_3.to_vec(),
+            CHUNK_4.to_vec(),
+            CHUNK_5.to_vec(),
+        ] {
+            let mut bw = blob_service.open_write().await;
+            tokio::io::copy(&mut std::io::Cursor::new(blob_contents), &mut bw)
+                .await
+                .expect("writing blob");
+            bw.close().await.expect("close blobwriter");
+        }
+
+        blob_service
+    }
+
+    #[tokio::test]
+    async fn test_read() {
+        let blob_service = gen_blobservice_blob1().await;
+        let mut chunked_reader =
+            ChunkedReader::from_chunks(BLOB_1_LIST.clone().into_iter(), blob_service);
+
+        // read all data
+        let mut buf = Vec::new();
+        tokio::io::copy(&mut chunked_reader, &mut buf)
+            .await
+            .expect("copy");
+
+        assert_eq!(
+            hex!("000102030405060708090a0b0c0d0e0f").to_vec(),
+            buf,
+            "read data must match"
+        );
+    }
+
+    #[tokio::test]
+    async fn test_seek() {
+        let blob_service = gen_blobservice_blob1().await;
+        let mut chunked_reader =
+            ChunkedReader::from_chunks(BLOB_1_LIST.clone().into_iter(), blob_service);
+
+        // seek to the end
+        // expect to read 0 bytes
+        {
+            chunked_reader
+                .seek(SeekFrom::End(0))
+                .await
+                .expect("seek to end");
+
+            let mut buf = Vec::new();
+            chunked_reader
+                .read_to_end(&mut buf)
+                .await
+                .expect("read to end");
+
+            assert_eq!(hex!("").to_vec(), buf);
+        }
+
+        // seek one bytes before the end
+        {
+            chunked_reader.seek(SeekFrom::End(-1)).await.expect("seek");
+
+            let mut buf = Vec::new();
+            chunked_reader
+                .read_to_end(&mut buf)
+                .await
+                .expect("read to end");
+
+            assert_eq!(hex!("0f").to_vec(), buf);
+        }
+
+        // seek back three bytes, but using relative positioning
+        // read two bytes
+        {
+            chunked_reader
+                .seek(SeekFrom::Current(-3))
+                .await
+                .expect("seek");
+
+            let mut buf = [0b0; 2];
+            chunked_reader
+                .read_exact(&mut buf)
+                .await
+                .expect("read exact");
+
+            assert_eq!(hex!("0d0e"), buf);
+        }
+    }
+
+    // seeds a blob service with only the first two chunks, reads a bit in the
+    // front (which succeeds), but then tries to seek past and read more (which
+    // should fail).
+    #[tokio::test]
+    async fn test_read_missing_chunks() {
+        let blob_service = Arc::new(MemoryBlobService::default()) as Arc<dyn BlobService>;
+
+        for blob_contents in [CHUNK_1.to_vec(), CHUNK_2.to_vec()] {
+            let mut bw = blob_service.open_write().await;
+            tokio::io::copy(&mut std::io::Cursor::new(blob_contents), &mut bw)
+                .await
+                .expect("writing blob");
+
+            bw.close().await.expect("close blobwriter");
+        }
+
+        let mut chunked_reader =
+            ChunkedReader::from_chunks(BLOB_1_LIST.clone().into_iter(), blob_service);
+
+        // read a bit from the front (5 bytes out of 6 available)
+        let mut buf = [0b0; 5];
+        chunked_reader
+            .read_exact(&mut buf)
+            .await
+            .expect("read exact");
+
+        assert_eq!(hex!("0001020304"), buf);
+
+        // seek 2 bytes forward, into an area where we don't have chunks
+        chunked_reader
+            .seek(SeekFrom::Current(2))
+            .await
+            .expect("seek");
+
+        let mut buf = Vec::new();
+        chunked_reader
+            .read_to_end(&mut buf)
+            .await
+            .expect_err("must fail");
+
+        // FUTUREWORK: check semantics on errorkinds. Should this be InvalidData
+        // or NotFound?
+    }
+}
diff --git a/tvix/castore/src/blobservice/combinator.rs b/tvix/castore/src/blobservice/combinator.rs
new file mode 100644
index 0000000000..067eff96f4
--- /dev/null
+++ b/tvix/castore/src/blobservice/combinator.rs
@@ -0,0 +1,132 @@
+use futures::{StreamExt, TryStreamExt};
+use tokio_util::io::{ReaderStream, StreamReader};
+use tonic::async_trait;
+use tracing::{instrument, warn};
+
+use crate::B3Digest;
+
+use super::{naive_seeker::NaiveSeeker, BlobReader, BlobService, BlobWriter};
+
+/// Combinator for a BlobService, using a "local" and "remote" blobservice.
+/// Requests are tried in (and returned from) the local store first, only if
+/// things are not present there, the remote BlobService is queried.
+/// In case the local blobservice doesn't have the blob, we ask the remote
+/// blobservice for chunks, and try to read each of these chunks from the local
+/// blobservice again, before falling back to the remote one.
+/// The remote BlobService is never written to.
+pub struct CombinedBlobService<BL, BR> {
+    local: BL,
+    remote: BR,
+}
+
+impl<BL, BR> Clone for CombinedBlobService<BL, BR>
+where
+    BL: Clone,
+    BR: Clone,
+{
+    fn clone(&self) -> Self {
+        Self {
+            local: self.local.clone(),
+            remote: self.remote.clone(),
+        }
+    }
+}
+
+#[async_trait]
+impl<BL, BR> BlobService for CombinedBlobService<BL, BR>
+where
+    BL: AsRef<dyn BlobService> + Clone + Send + Sync + 'static,
+    BR: AsRef<dyn BlobService> + Clone + Send + Sync + 'static,
+{
+    #[instrument(skip(self, digest), fields(blob.digest=%digest))]
+    async fn has(&self, digest: &B3Digest) -> std::io::Result<bool> {
+        Ok(self.local.as_ref().has(digest).await? || self.remote.as_ref().has(digest).await?)
+    }
+
+    #[instrument(skip(self, digest), fields(blob.digest=%digest), err)]
+    async fn open_read(&self, digest: &B3Digest) -> std::io::Result<Option<Box<dyn BlobReader>>> {
+        if self.local.as_ref().has(digest).await? {
+            // local store has the blob, so we can assume it also has all chunks.
+            self.local.as_ref().open_read(digest).await
+        } else {
+            // Local store doesn't have the blob.
+            // Ask the remote one for the list of chunks,
+            // and create a chunked reader that uses self.open_read() for
+            // individual chunks. There's a chance we already have some chunks
+            // locally, meaning we don't need to fetch them all from the remote
+            // BlobService.
+            match self.remote.as_ref().chunks(digest).await? {
+                // blob doesn't exist on the remote side either, nothing we can do.
+                None => Ok(None),
+                Some(remote_chunks) => {
+                    // if there's no more granular chunks, or the remote
+                    // blobservice doesn't support chunks, read the blob from
+                    // the remote blobservice directly.
+                    if remote_chunks.is_empty() {
+                        return self.remote.as_ref().open_read(digest).await;
+                    }
+                    // otherwise, a chunked reader, which will always try the
+                    // local backend first.
+
+                    // map Vec<ChunkMeta> to Vec<(B3Digest, u64)>
+                    let chunks: Vec<(B3Digest, u64)> = remote_chunks
+                        .into_iter()
+                        .map(|chunk_meta| {
+                            (
+                                B3Digest::try_from(chunk_meta.digest)
+                                    .expect("invalid chunk digest"),
+                                chunk_meta.size,
+                            )
+                        })
+                        .collect();
+
+                    Ok(Some(make_chunked_reader(self.clone(), chunks)))
+                }
+            }
+        }
+    }
+
+    #[instrument(skip_all)]
+    async fn open_write(&self) -> Box<dyn BlobWriter> {
+        // direct writes to the local one.
+        self.local.as_ref().open_write().await
+    }
+}
+
+fn make_chunked_reader<BS>(
+    // This must consume, as we can't retain references to blob_service,
+    // as it'd add a lifetime to BlobReader in general, which will get
+    // problematic in TvixStoreFs, which is using async move closures and cloning.
+    blob_service: BS,
+    // A list of b3 digests for individual chunks, and their sizes.
+    chunks: Vec<(B3Digest, u64)>,
+) -> Box<dyn BlobReader>
+where
+    BS: BlobService + Clone + 'static,
+{
+    // TODO: offset, verified streaming
+
+    // construct readers for each chunk
+    let blob_service = blob_service.clone();
+    let readers_stream = tokio_stream::iter(chunks).map(move |(digest, _)| {
+        let d = digest.to_owned();
+        let blob_service = blob_service.clone();
+        async move {
+            blob_service.open_read(&d.to_owned()).await?.ok_or_else(|| {
+                warn!(chunk.digest = %digest, "chunk not found");
+                std::io::Error::new(std::io::ErrorKind::NotFound, "chunk not found")
+            })
+        }
+    });
+
+    // convert the stream of readers to a stream of streams of byte chunks
+    let bytes_streams = readers_stream.then(|elem| async { elem.await.map(ReaderStream::new) });
+
+    // flatten into one stream of byte chunks
+    let bytes_stream = bytes_streams.try_flatten();
+
+    // convert into AsyncRead
+    let blob_reader = StreamReader::new(bytes_stream);
+
+    Box::new(NaiveSeeker::new(Box::pin(blob_reader)))
+}
diff --git a/tvix/castore/src/blobservice/from_addr.rs b/tvix/castore/src/blobservice/from_addr.rs
new file mode 100644
index 0000000000..3e3f943e59
--- /dev/null
+++ b/tvix/castore/src/blobservice/from_addr.rs
@@ -0,0 +1,152 @@
+use url::Url;
+
+use crate::{proto::blob_service_client::BlobServiceClient, Error};
+
+use super::{
+    BlobService, GRPCBlobService, MemoryBlobService, ObjectStoreBlobService, SledBlobService,
+};
+
+/// Constructs a new instance of a [BlobService] from an URI.
+///
+/// The following schemes are supported by the following services:
+/// - `memory://` ([MemoryBlobService])
+/// - `sled://` ([SledBlobService])
+/// - `grpc+*://` ([GRPCBlobService])
+/// - `objectstore+*://` ([ObjectStoreBlobService])
+///
+/// See their `from_url` methods for more details about their syntax.
+pub async fn from_addr(uri: &str) -> Result<Box<dyn BlobService>, crate::Error> {
+    let url = Url::parse(uri)
+        .map_err(|e| crate::Error::StorageError(format!("unable to parse url: {}", e)))?;
+
+    let blob_service: Box<dyn BlobService> = match url.scheme() {
+        "memory" => {
+            // memory doesn't support host or path in the URL.
+            if url.has_host() || !url.path().is_empty() {
+                return Err(Error::StorageError("invalid url".to_string()));
+            }
+            Box::<MemoryBlobService>::default()
+        }
+        "sled" => {
+            // sled doesn't support host, and a path can be provided (otherwise
+            // it'll live in memory only).
+            if url.has_host() {
+                return Err(Error::StorageError("no host allowed".to_string()));
+            }
+
+            if url.path() == "/" {
+                return Err(Error::StorageError(
+                    "cowardly refusing to open / with sled".to_string(),
+                ));
+            }
+
+            // TODO: expose other parameters as URL parameters?
+
+            Box::new(if url.path().is_empty() {
+                SledBlobService::new_temporary().map_err(|e| Error::StorageError(e.to_string()))?
+            } else {
+                SledBlobService::new(url.path()).map_err(|e| Error::StorageError(e.to_string()))?
+            })
+        }
+        scheme if scheme.starts_with("grpc+") => {
+            // schemes starting with grpc+ go to the GRPCPathInfoService.
+            //   That's normally grpc+unix for unix sockets, and grpc+http(s) for the HTTP counterparts.
+            // - In the case of unix sockets, there must be a path, but may not be a host.
+            // - In the case of non-unix sockets, there must be a host, but no path.
+            // Constructing the channel is handled by tvix_castore::channel::from_url.
+            let client = BlobServiceClient::new(crate::tonic::channel_from_url(&url).await?);
+            Box::new(GRPCBlobService::from_client(client))
+        }
+        scheme if scheme.starts_with("objectstore+") => {
+            // We need to convert the URL to string, strip the prefix there, and then
+            // parse it back as url, as Url::set_scheme() rejects some of the transitions we want to do.
+            let trimmed_url = {
+                let s = url.to_string();
+                Url::parse(s.strip_prefix("objectstore+").unwrap()).unwrap()
+            };
+            Box::new(
+                ObjectStoreBlobService::parse_url(&trimmed_url)
+                    .map_err(|e| Error::StorageError(e.to_string()))?,
+            )
+        }
+        scheme => {
+            return Err(crate::Error::StorageError(format!(
+                "unknown scheme: {}",
+                scheme
+            )))
+        }
+    };
+
+    Ok(blob_service)
+}
+
+#[cfg(test)]
+mod tests {
+    use super::from_addr;
+    use lazy_static::lazy_static;
+    use rstest::rstest;
+    use tempfile::TempDir;
+
+    lazy_static! {
+        static ref TMPDIR_SLED_1: TempDir = TempDir::new().unwrap();
+        static ref TMPDIR_SLED_2: TempDir = TempDir::new().unwrap();
+    }
+
+    #[rstest]
+    /// This uses an unsupported scheme.
+    #[case::unsupported_scheme("http://foo.example/test", false)]
+    /// This configures sled in temporary mode.
+    #[case::sled_temporary("sled://", true)]
+    /// This configures sled with /, which should fail.
+    #[case::sled_invalid_root("sled:///", false)]
+    /// This configures sled with a host, not path, which should fail.
+    #[case::sled_invalid_host("sled://foo.example", false)]
+    /// This configures sled with a valid path path, which should succeed.
+    #[case::sled_valid_path(&format!("sled://{}", &TMPDIR_SLED_1.path().to_str().unwrap()), true)]
+    /// This configures sled with a host, and a valid path path, which should fail.
+    #[case::sled_invalid_host_with_valid_path(&format!("sled://foo.example{}", &TMPDIR_SLED_2.path().to_str().unwrap()), false)]
+    /// This correctly sets the scheme, and doesn't set a path.
+    #[case::memory_valid("memory://", true)]
+    /// This sets a memory url host to `foo`
+    #[case::memory_invalid_host("memory://foo", false)]
+    /// This sets a memory url path to "/", which is invalid.
+    #[case::memory_invalid_root_path("memory:///", false)]
+    /// This sets a memory url path to "/foo", which is invalid.
+    #[case::memory_invalid_root_path_foo("memory:///foo", false)]
+    /// Correct scheme to connect to a unix socket.
+    #[case::grpc_valid_unix_socket("grpc+unix:///path/to/somewhere", true)]
+    /// Correct scheme for unix socket, but setting a host too, which is invalid.
+    #[case::grpc_invalid_unix_socket_and_host("grpc+unix://host.example/path/to/somewhere", false)]
+    /// Correct scheme to connect to localhost, with port 12345
+    #[case::grpc_valid_ipv6_localhost_port_12345("grpc+http://[::1]:12345", true)]
+    /// Correct scheme to connect to localhost over http, without specifying a port.
+    #[case::grpc_valid_http_host_without_port("grpc+http://localhost", true)]
+    /// Correct scheme to connect to localhost over http, without specifying a port.
+    #[case::grpc_valid_https_host_without_port("grpc+https://localhost", true)]
+    /// Correct scheme to connect to localhost over http, but with additional path, which is invalid.
+    #[case::grpc_invalid_has_path("grpc+http://localhost/some-path", false)]
+    /// An example for object store (InMemory)
+    #[case::objectstore_valid_memory("objectstore+memory:///", true)]
+    /// An example for object store (LocalFileSystem)
+    #[case::objectstore_valid_file("objectstore+file:///foo/bar", true)]
+    // An example for object store (HTTP / WebDAV)
+    #[case::objectstore_valid_http_url("objectstore+https://localhost:8080/some-path", true)]
+    /// An example for object store (S3)
+    #[cfg_attr(
+        feature = "cloud",
+        case::objectstore_valid_s3_url("objectstore+s3://bucket/path", true)
+    )]
+    /// An example for object store (GCS)
+    #[cfg_attr(
+        feature = "cloud",
+        case::objectstore_valid_gcs_url("objectstore+gs://bucket/path", true)
+    )]
+    #[tokio::test]
+    async fn test_from_addr_tokio(#[case] uri_str: &str, #[case] exp_succeed: bool) {
+        if exp_succeed {
+            from_addr(uri_str).await.expect("should succeed");
+        } else {
+            assert!(from_addr(uri_str).await.is_err(), "should fail");
+        }
+    }
+}
diff --git a/tvix/castore/src/blobservice/grpc.rs b/tvix/castore/src/blobservice/grpc.rs
new file mode 100644
index 0000000000..5663cd3838
--- /dev/null
+++ b/tvix/castore/src/blobservice/grpc.rs
@@ -0,0 +1,349 @@
+use super::{BlobReader, BlobService, BlobWriter, ChunkedReader};
+use crate::{
+    proto::{self, stat_blob_response::ChunkMeta},
+    B3Digest,
+};
+use futures::sink::SinkExt;
+use std::{
+    io::{self, Cursor},
+    pin::pin,
+    sync::Arc,
+    task::Poll,
+};
+use tokio::io::AsyncWriteExt;
+use tokio::task::JoinHandle;
+use tokio_stream::{wrappers::ReceiverStream, StreamExt};
+use tokio_util::{
+    io::{CopyToBytes, SinkWriter},
+    sync::PollSender,
+};
+use tonic::{async_trait, transport::Channel, Code, Status};
+use tracing::instrument;
+
+/// Connects to a (remote) tvix-store BlobService over gRPC.
+#[derive(Clone)]
+pub struct GRPCBlobService {
+    /// The internal reference to a gRPC client.
+    /// Cloning it is cheap, and it internally handles concurrent requests.
+    grpc_client: proto::blob_service_client::BlobServiceClient<Channel>,
+}
+
+impl GRPCBlobService {
+    /// construct a [GRPCBlobService] from a [proto::blob_service_client::BlobServiceClient].
+    /// panics if called outside the context of a tokio runtime.
+    pub fn from_client(
+        grpc_client: proto::blob_service_client::BlobServiceClient<Channel>,
+    ) -> Self {
+        Self { grpc_client }
+    }
+}
+
+#[async_trait]
+impl BlobService for GRPCBlobService {
+    #[instrument(skip(self, digest), fields(blob.digest=%digest))]
+    async fn has(&self, digest: &B3Digest) -> io::Result<bool> {
+        let mut grpc_client = self.grpc_client.clone();
+        let resp = grpc_client
+            .stat(proto::StatBlobRequest {
+                digest: digest.clone().into(),
+                ..Default::default()
+            })
+            .await;
+
+        match resp {
+            Ok(_blob_meta) => Ok(true),
+            Err(e) if e.code() == Code::NotFound => Ok(false),
+            Err(e) => Err(io::Error::new(io::ErrorKind::Other, e)),
+        }
+    }
+
+    #[instrument(skip(self, digest), fields(blob.digest=%digest), err)]
+    async fn open_read(&self, digest: &B3Digest) -> io::Result<Option<Box<dyn BlobReader>>> {
+        // First try to get a list of chunks. In case there's only one chunk returned,
+        // buffer its data into a Vec, otherwise use a ChunkedReader.
+        // We previously used NaiveSeeker here, but userland likes to seek backwards too often,
+        // and without store composition this will get very noisy.
+        // FUTUREWORK: use CombinedBlobService and store composition.
+        match self.chunks(digest).await {
+            Ok(None) => Ok(None),
+            Ok(Some(chunks)) => {
+                if chunks.is_empty() || chunks.len() == 1 {
+                    // No more granular chunking info, treat this as an individual chunk.
+                    // Get a stream of [proto::BlobChunk], or return an error if the blob
+                    // doesn't exist.
+                    return match self
+                        .grpc_client
+                        .clone()
+                        .read(proto::ReadBlobRequest {
+                            digest: digest.clone().into(),
+                        })
+                        .await
+                    {
+                        Ok(stream) => {
+                            let data_stream = stream.into_inner().map(|e| {
+                                e.map(|c| c.data)
+                                    .map_err(|s| std::io::Error::new(io::ErrorKind::InvalidData, s))
+                            });
+
+                            // Use StreamReader::new to convert to an AsyncRead.
+                            let mut data_reader = tokio_util::io::StreamReader::new(data_stream);
+
+                            let mut buf = Vec::new();
+                            // TODO: only do this up to a certain limit.
+                            tokio::io::copy(&mut data_reader, &mut buf).await?;
+
+                            Ok(Some(Box::new(Cursor::new(buf))))
+                        }
+                        Err(e) if e.code() == Code::NotFound => Ok(None),
+                        Err(e) => Err(io::Error::new(io::ErrorKind::Other, e)),
+                    };
+                }
+
+                // The chunked case. Let ChunkedReader do individual reads.
+                // TODO: we should store the chunking data in some local cache,
+                // so `ChunkedReader` doesn't call `self.chunks` *again* for every chunk.
+                // Think about how store composition will fix this.
+                let chunked_reader = ChunkedReader::from_chunks(
+                    chunks.into_iter().map(|chunk| {
+                        (
+                            chunk.digest.try_into().expect("invalid b3 digest"),
+                            chunk.size,
+                        )
+                    }),
+                    Arc::new(self.clone()) as Arc<dyn BlobService>,
+                );
+                Ok(Some(Box::new(chunked_reader)))
+            }
+            Err(e) => Err(e)?,
+        }
+    }
+
+    /// Returns a BlobWriter, that'll internally wrap each write in a
+    /// [proto::BlobChunk], which is send to the gRPC server.
+    #[instrument(skip_all)]
+    async fn open_write(&self) -> Box<dyn BlobWriter> {
+        // set up an mpsc channel passing around Bytes.
+        let (tx, rx) = tokio::sync::mpsc::channel::<bytes::Bytes>(10);
+
+        // bytes arriving on the RX side are wrapped inside a
+        // [proto::BlobChunk], and a [ReceiverStream] is constructed.
+        let blobchunk_stream = ReceiverStream::new(rx).map(|x| proto::BlobChunk { data: x });
+
+        // spawn the gRPC put request, which will read from blobchunk_stream.
+        let task = tokio::spawn({
+            let mut grpc_client = self.grpc_client.clone();
+            async move { Ok::<_, Status>(grpc_client.put(blobchunk_stream).await?.into_inner()) }
+        });
+
+        // The tx part of the channel is converted to a sink of byte chunks.
+        let sink = PollSender::new(tx)
+            .sink_map_err(|e| std::io::Error::new(std::io::ErrorKind::BrokenPipe, e));
+
+        // … which is turned into an [tokio::io::AsyncWrite].
+        let writer = SinkWriter::new(CopyToBytes::new(sink));
+
+        Box::new(GRPCBlobWriter {
+            task_and_writer: Some((task, writer)),
+            digest: None,
+        })
+    }
+
+    #[instrument(skip(self, digest), fields(blob.digest=%digest), err)]
+    async fn chunks(&self, digest: &B3Digest) -> io::Result<Option<Vec<ChunkMeta>>> {
+        let resp = self
+            .grpc_client
+            .clone()
+            .stat(proto::StatBlobRequest {
+                digest: digest.clone().into(),
+                send_chunks: true,
+                ..Default::default()
+            })
+            .await;
+
+        match resp {
+            Err(e) if e.code() == Code::NotFound => Ok(None),
+            Err(e) => Err(io::Error::new(io::ErrorKind::Other, e)),
+            Ok(resp) => {
+                let resp = resp.into_inner();
+
+                resp.validate()
+                    .map_err(|e| std::io::Error::new(io::ErrorKind::InvalidData, e))?;
+
+                Ok(Some(resp.chunks))
+            }
+        }
+    }
+}
+
+pub struct GRPCBlobWriter<W: tokio::io::AsyncWrite> {
+    /// The task containing the put request, and the inner writer, if we're still writing.
+    task_and_writer: Option<(JoinHandle<Result<proto::PutBlobResponse, Status>>, W)>,
+
+    /// The digest that has been returned, if we successfully closed.
+    digest: Option<B3Digest>,
+}
+
+#[async_trait]
+impl<W: tokio::io::AsyncWrite + Send + Sync + Unpin + 'static> BlobWriter for GRPCBlobWriter<W> {
+    async fn close(&mut self) -> io::Result<B3Digest> {
+        if self.task_and_writer.is_none() {
+            // if we're already closed, return the b3 digest, which must exist.
+            // If it doesn't, we already closed and failed once, and didn't handle the error.
+            match &self.digest {
+                Some(digest) => Ok(digest.clone()),
+                None => Err(io::Error::new(io::ErrorKind::BrokenPipe, "already closed")),
+            }
+        } else {
+            let (task, mut writer) = self.task_and_writer.take().unwrap();
+
+            // invoke shutdown, so the inner writer closes its internal tx side of
+            // the channel.
+            writer.shutdown().await?;
+
+            // block on the RPC call to return.
+            // This ensures all chunks are sent out, and have been received by the
+            // backend.
+
+            match task.await? {
+                Ok(resp) => {
+                    // return the digest from the response, and store it in self.digest for subsequent closes.
+                    let digest_len = resp.digest.len();
+                    let digest: B3Digest = resp.digest.try_into().map_err(|_| {
+                        io::Error::new(
+                            io::ErrorKind::Other,
+                            format!("invalid root digest length {} in response", digest_len),
+                        )
+                    })?;
+                    self.digest = Some(digest.clone());
+                    Ok(digest)
+                }
+                Err(e) => Err(io::Error::new(io::ErrorKind::Other, e.to_string())),
+            }
+        }
+    }
+}
+
+impl<W: tokio::io::AsyncWrite + Unpin> tokio::io::AsyncWrite for GRPCBlobWriter<W> {
+    fn poll_write(
+        mut self: std::pin::Pin<&mut Self>,
+        cx: &mut std::task::Context<'_>,
+        buf: &[u8],
+    ) -> std::task::Poll<Result<usize, io::Error>> {
+        match &mut self.task_and_writer {
+            None => Poll::Ready(Err(io::Error::new(
+                io::ErrorKind::NotConnected,
+                "already closed",
+            ))),
+            Some((_, ref mut writer)) => {
+                let pinned_writer = pin!(writer);
+                pinned_writer.poll_write(cx, buf)
+            }
+        }
+    }
+
+    fn poll_flush(
+        mut self: std::pin::Pin<&mut Self>,
+        cx: &mut std::task::Context<'_>,
+    ) -> std::task::Poll<Result<(), io::Error>> {
+        match &mut self.task_and_writer {
+            None => Poll::Ready(Err(io::Error::new(
+                io::ErrorKind::NotConnected,
+                "already closed",
+            ))),
+            Some((_, ref mut writer)) => {
+                let pinned_writer = pin!(writer);
+                pinned_writer.poll_flush(cx)
+            }
+        }
+    }
+
+    fn poll_shutdown(
+        self: std::pin::Pin<&mut Self>,
+        _cx: &mut std::task::Context<'_>,
+    ) -> std::task::Poll<Result<(), io::Error>> {
+        // TODO(raitobezarius): this might not be a graceful shutdown of the
+        // channel inside the gRPC connection.
+        Poll::Ready(Ok(()))
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use std::time::Duration;
+
+    use tempfile::TempDir;
+    use tokio::net::UnixListener;
+    use tokio_retry::strategy::ExponentialBackoff;
+    use tokio_retry::Retry;
+    use tokio_stream::wrappers::UnixListenerStream;
+
+    use crate::blobservice::MemoryBlobService;
+    use crate::fixtures;
+    use crate::proto::blob_service_client::BlobServiceClient;
+    use crate::proto::GRPCBlobServiceWrapper;
+
+    use super::BlobService;
+    use super::GRPCBlobService;
+
+    /// This ensures connecting via gRPC works as expected.
+    #[tokio::test]
+    async fn test_valid_unix_path_ping_pong() {
+        let tmpdir = TempDir::new().unwrap();
+        let socket_path = tmpdir.path().join("daemon");
+
+        let path_clone = socket_path.clone();
+
+        // Spin up a server
+        tokio::spawn(async {
+            let uds = UnixListener::bind(path_clone).unwrap();
+            let uds_stream = UnixListenerStream::new(uds);
+
+            // spin up a new server
+            let mut server = tonic::transport::Server::builder();
+            let router =
+                server.add_service(crate::proto::blob_service_server::BlobServiceServer::new(
+                    GRPCBlobServiceWrapper::new(
+                        Box::<MemoryBlobService>::default() as Box<dyn BlobService>
+                    ),
+                ));
+            router.serve_with_incoming(uds_stream).await
+        });
+
+        // wait for the socket to be created
+        Retry::spawn(
+            ExponentialBackoff::from_millis(20).max_delay(Duration::from_secs(10)),
+            || async {
+                if socket_path.exists() {
+                    Ok(())
+                } else {
+                    Err(())
+                }
+            },
+        )
+        .await
+        .expect("failed to wait for socket");
+
+        // prepare a client
+        let grpc_client = {
+            let url = url::Url::parse(&format!(
+                "grpc+unix://{}?wait-connect=1",
+                socket_path.display()
+            ))
+            .expect("must parse");
+            let client = BlobServiceClient::new(
+                crate::tonic::channel_from_url(&url)
+                    .await
+                    .expect("must succeed"),
+            );
+
+            GRPCBlobService::from_client(client)
+        };
+
+        let has = grpc_client
+            .has(&fixtures::BLOB_A_DIGEST)
+            .await
+            .expect("must not be err");
+
+        assert!(!has);
+    }
+}
diff --git a/tvix/castore/src/blobservice/memory.rs b/tvix/castore/src/blobservice/memory.rs
new file mode 100644
index 0000000000..25eec334de
--- /dev/null
+++ b/tvix/castore/src/blobservice/memory.rs
@@ -0,0 +1,137 @@
+use std::io::{self, Cursor, Write};
+use std::task::Poll;
+use std::{
+    collections::HashMap,
+    sync::{Arc, RwLock},
+};
+use tonic::async_trait;
+use tracing::instrument;
+
+use super::{BlobReader, BlobService, BlobWriter};
+use crate::B3Digest;
+
+#[derive(Clone, Default)]
+pub struct MemoryBlobService {
+    db: Arc<RwLock<HashMap<B3Digest, Vec<u8>>>>,
+}
+
+#[async_trait]
+impl BlobService for MemoryBlobService {
+    #[instrument(skip_all, ret, err, fields(blob.digest=%digest))]
+    async fn has(&self, digest: &B3Digest) -> io::Result<bool> {
+        let db = self.db.read().unwrap();
+        Ok(db.contains_key(digest))
+    }
+
+    #[instrument(skip_all, err, fields(blob.digest=%digest))]
+    async fn open_read(&self, digest: &B3Digest) -> io::Result<Option<Box<dyn BlobReader>>> {
+        let db = self.db.read().unwrap();
+
+        match db.get(digest).map(|x| Cursor::new(x.clone())) {
+            Some(result) => Ok(Some(Box::new(result))),
+            None => Ok(None),
+        }
+    }
+
+    #[instrument(skip_all)]
+    async fn open_write(&self) -> Box<dyn BlobWriter> {
+        Box::new(MemoryBlobWriter::new(self.db.clone()))
+    }
+}
+
+pub struct MemoryBlobWriter {
+    db: Arc<RwLock<HashMap<B3Digest, Vec<u8>>>>,
+
+    /// Contains the buffer Vec and hasher, or None if already closed
+    writers: Option<(Vec<u8>, blake3::Hasher)>,
+
+    /// The digest that has been returned, if we successfully closed.
+    digest: Option<B3Digest>,
+}
+
+impl MemoryBlobWriter {
+    fn new(db: Arc<RwLock<HashMap<B3Digest, Vec<u8>>>>) -> Self {
+        Self {
+            db,
+            writers: Some((Vec::new(), blake3::Hasher::new())),
+            digest: None,
+        }
+    }
+}
+impl tokio::io::AsyncWrite for MemoryBlobWriter {
+    fn poll_write(
+        mut self: std::pin::Pin<&mut Self>,
+        _cx: &mut std::task::Context<'_>,
+        b: &[u8],
+    ) -> std::task::Poll<Result<usize, io::Error>> {
+        Poll::Ready(match &mut self.writers {
+            None => Err(io::Error::new(
+                io::ErrorKind::NotConnected,
+                "already closed",
+            )),
+            Some((ref mut buf, ref mut hasher)) => {
+                let bytes_written = buf.write(b)?;
+                hasher.write(&b[..bytes_written])
+            }
+        })
+    }
+
+    fn poll_flush(
+        self: std::pin::Pin<&mut Self>,
+        _cx: &mut std::task::Context<'_>,
+    ) -> std::task::Poll<Result<(), io::Error>> {
+        Poll::Ready(match self.writers {
+            None => Err(io::Error::new(
+                io::ErrorKind::NotConnected,
+                "already closed",
+            )),
+            Some(_) => Ok(()),
+        })
+    }
+
+    fn poll_shutdown(
+        self: std::pin::Pin<&mut Self>,
+        _cx: &mut std::task::Context<'_>,
+    ) -> std::task::Poll<Result<(), io::Error>> {
+        // shutdown is "instantaneous", we only write to memory.
+        Poll::Ready(Ok(()))
+    }
+}
+
+#[async_trait]
+impl BlobWriter for MemoryBlobWriter {
+    async fn close(&mut self) -> io::Result<B3Digest> {
+        if self.writers.is_none() {
+            match &self.digest {
+                Some(digest) => Ok(digest.clone()),
+                None => Err(io::Error::new(io::ErrorKind::BrokenPipe, "already closed")),
+            }
+        } else {
+            let (buf, hasher) = self.writers.take().unwrap();
+
+            // We know self.hasher is doing blake3 hashing, so this won't fail.
+            let digest: B3Digest = hasher.finalize().as_bytes().into();
+
+            // Only insert if the blob doesn't already exist.
+            let db = self.db.read().map_err(|e| {
+                io::Error::new(io::ErrorKind::BrokenPipe, format!("RwLock poisoned: {}", e))
+            })?;
+            if !db.contains_key(&digest) {
+                // drop the read lock, so we can open for writing.
+                drop(db);
+
+                // open the database for writing.
+                let mut db = self.db.write().map_err(|e| {
+                    io::Error::new(io::ErrorKind::BrokenPipe, format!("RwLock poisoned: {}", e))
+                })?;
+
+                // and put buf in there. This will move buf out.
+                db.insert(digest.clone(), buf);
+            }
+
+            self.digest = Some(digest.clone());
+
+            Ok(digest)
+        }
+    }
+}
diff --git a/tvix/castore/src/blobservice/mod.rs b/tvix/castore/src/blobservice/mod.rs
new file mode 100644
index 0000000000..4ba56a4af7
--- /dev/null
+++ b/tvix/castore/src/blobservice/mod.rs
@@ -0,0 +1,105 @@
+use std::io;
+use tonic::async_trait;
+
+use crate::proto::stat_blob_response::ChunkMeta;
+use crate::B3Digest;
+
+mod chunked_reader;
+mod combinator;
+mod from_addr;
+mod grpc;
+mod memory;
+mod naive_seeker;
+mod object_store;
+mod sled;
+
+#[cfg(test)]
+pub mod tests;
+
+pub use self::chunked_reader::ChunkedReader;
+pub use self::combinator::CombinedBlobService;
+pub use self::from_addr::from_addr;
+pub use self::grpc::GRPCBlobService;
+pub use self::memory::MemoryBlobService;
+pub use self::object_store::ObjectStoreBlobService;
+pub use self::sled::SledBlobService;
+
+/// The base trait all BlobService services need to implement.
+/// It provides functions to check whether a given blob exists,
+/// a way to read (and seek) a blob, and a method to create a blobwriter handle,
+/// which will implement a writer interface, and also provides a close funtion,
+/// to finalize a blob and get its digest.
+#[async_trait]
+pub trait BlobService: Send + Sync {
+    /// Check if the service has the blob, by its content hash.
+    /// On implementations returning chunks, this must also work for chunks.
+    async fn has(&self, digest: &B3Digest) -> io::Result<bool>;
+
+    /// Request a blob from the store, by its content hash.
+    /// On implementations returning chunks, this must also work for chunks.
+    async fn open_read(&self, digest: &B3Digest) -> io::Result<Option<Box<dyn BlobReader>>>;
+
+    /// Insert a new blob into the store. Returns a [BlobWriter], which
+    /// implements [tokio::io::AsyncWrite] and a [BlobWriter::close] to finalize
+    /// the blob and get its digest.
+    async fn open_write(&self) -> Box<dyn BlobWriter>;
+
+    /// Return a list of chunks for a given blob.
+    /// There's a distinction between returning Ok(None) and Ok(Some(vec![])).
+    /// The former return value is sent in case the blob is not present at all,
+    /// while the second one is sent in case there's no more granular chunks (or
+    /// the backend does not support chunking).
+    /// A default implementation checking for existence and then returning it
+    /// does not have more granular chunks available is provided.
+    async fn chunks(&self, digest: &B3Digest) -> io::Result<Option<Vec<ChunkMeta>>> {
+        if !self.has(digest).await? {
+            return Ok(None);
+        }
+        // default implementation, signalling the backend does not have more
+        // granular chunks available.
+        Ok(Some(vec![]))
+    }
+}
+
+#[async_trait]
+impl<A> BlobService for A
+where
+    A: AsRef<dyn BlobService> + Send + Sync,
+{
+    async fn has(&self, digest: &B3Digest) -> io::Result<bool> {
+        self.as_ref().has(digest).await
+    }
+
+    async fn open_read(&self, digest: &B3Digest) -> io::Result<Option<Box<dyn BlobReader>>> {
+        self.as_ref().open_read(digest).await
+    }
+
+    async fn open_write(&self) -> Box<dyn BlobWriter> {
+        self.as_ref().open_write().await
+    }
+
+    async fn chunks(&self, digest: &B3Digest) -> io::Result<Option<Vec<ChunkMeta>>> {
+        self.as_ref().chunks(digest).await
+    }
+}
+
+/// A [tokio::io::AsyncWrite] that the user needs to close() afterwards for persist.
+/// On success, it returns the digest of the written blob.
+#[async_trait]
+pub trait BlobWriter: tokio::io::AsyncWrite + Send + Unpin {
+    /// Signal there's no more data to be written, and return the digest of the
+    /// contents written.
+    ///
+    /// Closing a already-closed BlobWriter is a no-op.
+    async fn close(&mut self) -> io::Result<B3Digest>;
+}
+
+/// BlobReader is a [tokio::io::AsyncRead] that also allows seeking.
+pub trait BlobReader: tokio::io::AsyncRead + tokio::io::AsyncSeek + Send + Unpin + 'static {}
+
+/// A [`io::Cursor<Vec<u8>>`] can be used as a BlobReader.
+impl BlobReader for io::Cursor<&'static [u8]> {}
+impl BlobReader for io::Cursor<&'static [u8; 0]> {}
+impl BlobReader for io::Cursor<Vec<u8>> {}
+impl BlobReader for io::Cursor<bytes::Bytes> {}
+impl BlobReader for tokio::fs::File {}
diff --git a/tvix/castore/src/blobservice/naive_seeker.rs b/tvix/castore/src/blobservice/naive_seeker.rs
new file mode 100644
index 0000000000..f5a5307150
--- /dev/null
+++ b/tvix/castore/src/blobservice/naive_seeker.rs
@@ -0,0 +1,265 @@
+use super::BlobReader;
+use futures::ready;
+use pin_project_lite::pin_project;
+use std::io;
+use std::task::Poll;
+use tokio::io::AsyncRead;
+use tracing::{debug, instrument, trace, warn};
+
+pin_project! {
+    /// This implements [tokio::io::AsyncSeek] for and [tokio::io::AsyncRead] by
+    /// simply skipping over some bytes, keeping track of the position.
+    /// It fails whenever you try to seek backwards.
+    ///
+    /// ## Pinning concerns:
+    ///
+    /// [NaiveSeeker] is itself pinned by callers, and we do not need to concern
+    /// ourselves regarding that.
+    ///
+    /// Though, its fields as per
+    /// <https://doc.rust-lang.org/std/pin/#pinning-is-not-structural-for-field>
+    /// can be pinned or unpinned.
+    ///
+    /// So we need to go over each field and choose our policy carefully.
+    ///
+    /// The obvious cases are the bookkeeping integers we keep in the structure,
+    /// those are private and not shared to anyone, we never build a
+    /// `Pin<&mut X>` out of them at any point, therefore, we can safely never
+    /// mark them as pinned. Of course, it is expected that no developer here
+    /// attempt to `pin!(self.pos)` to pin them because it makes no sense. If
+    /// they have to become pinned, they should be marked `#[pin]` and we need
+    /// to discuss it.
+    ///
+    /// So the bookkeeping integers are in the right state with respect to their
+    /// pinning status. The projection should offer direct access.
+    ///
+    /// On the `r` field, i.e. a `BufReader<R>`, given that
+    /// <https://docs.rs/tokio/latest/tokio/io/struct.BufReader.html#impl-Unpin-for-BufReader%3CR%3E>
+    /// is available, even a `Pin<&mut BufReader<R>>` can be safely moved.
+    ///
+    /// The only care we should have regards the internal reader itself, i.e.
+    /// the `R` instance, see that Tokio decided to `#[pin]` it too:
+    /// <https://docs.rs/tokio/latest/src/tokio/io/util/buf_reader.rs.html#29>
+    ///
+    /// In general, there's no `Unpin` instance for `R: tokio::io::AsyncRead`
+    /// (see <https://docs.rs/tokio/latest/tokio/io/trait.AsyncRead.html>).
+    ///
+    /// Therefore, we could keep it unpinned and pin it in every call site
+    /// whenever we need to call `poll_*` which can be confusing to the non-
+    /// expert developer and we have a fair share amount of situations where the
+    /// [BufReader] instance is naked, i.e. in its `&mut BufReader<R>`
+    /// form, this is annoying because it could lead to expose the naked `R`
+    /// internal instance somehow and would produce a risk of making it move
+    /// unexpectedly.
+    ///
+    /// We choose the path of the least resistance as we have no reason to have
+    /// access to the raw `BufReader<R>` instance, we just `#[pin]` it too and
+    /// enjoy its `poll_*` safe APIs and push the unpinning concerns to the
+    /// internal implementations themselves, which studied the question longer
+    /// than us.
+    pub struct NaiveSeeker<R: tokio::io::AsyncRead> {
+        #[pin]
+        r: tokio::io::BufReader<R>,
+        pos: u64,
+        bytes_to_skip: u64,
+    }
+}
+
+/// The buffer size used to discard data.
+const DISCARD_BUF_SIZE: usize = 4096;
+
+impl<R: tokio::io::AsyncRead> NaiveSeeker<R> {
+    pub fn new(r: R) -> Self {
+        NaiveSeeker {
+            r: tokio::io::BufReader::new(r),
+            pos: 0,
+            bytes_to_skip: 0,
+        }
+    }
+}
+
+impl<R: tokio::io::AsyncRead> tokio::io::AsyncRead for NaiveSeeker<R> {
+    #[instrument(level = "trace", skip_all)]
+    fn poll_read(
+        self: std::pin::Pin<&mut Self>,
+        cx: &mut std::task::Context<'_>,
+        buf: &mut tokio::io::ReadBuf<'_>,
+    ) -> Poll<std::io::Result<()>> {
+        // The amount of data read can be determined by the increase
+        // in the length of the slice returned by `ReadBuf::filled`.
+        let filled_before = buf.filled().len();
+
+        let this = self.project();
+        ready!(this.r.poll_read(cx, buf))?;
+
+        let bytes_read = buf.filled().len() - filled_before;
+        *this.pos += bytes_read as u64;
+
+        trace!(bytes_read = bytes_read, new_pos = this.pos, "poll_read");
+
+        Ok(()).into()
+    }
+}
+
+impl<R: tokio::io::AsyncRead> tokio::io::AsyncBufRead for NaiveSeeker<R> {
+    fn poll_fill_buf(
+        self: std::pin::Pin<&mut Self>,
+        cx: &mut std::task::Context<'_>,
+    ) -> Poll<io::Result<&[u8]>> {
+        self.project().r.poll_fill_buf(cx)
+    }
+
+    #[instrument(level = "trace", skip(self))]
+    fn consume(self: std::pin::Pin<&mut Self>, amt: usize) {
+        let this = self.project();
+        this.r.consume(amt);
+        *this.pos += amt as u64;
+
+        trace!(new_pos = this.pos, "consume");
+    }
+}
+
+impl<R: tokio::io::AsyncRead> tokio::io::AsyncSeek for NaiveSeeker<R> {
+    #[instrument(level="trace", skip(self), fields(inner_pos=%self.pos), err(Debug))]
+    fn start_seek(
+        self: std::pin::Pin<&mut Self>,
+        position: std::io::SeekFrom,
+    ) -> std::io::Result<()> {
+        let absolute_offset: u64 = match position {
+            io::SeekFrom::Start(start_offset) => {
+                if start_offset < self.pos {
+                    return Err(io::Error::new(
+                        io::ErrorKind::Unsupported,
+                        format!("can't seek backwards ({} -> {})", self.pos, start_offset),
+                    ));
+                } else {
+                    start_offset
+                }
+            }
+            // we don't know the total size, can't support this.
+            io::SeekFrom::End(_end_offset) => {
+                return Err(io::Error::new(
+                    io::ErrorKind::Unsupported,
+                    "can't seek from end",
+                ));
+            }
+            io::SeekFrom::Current(relative_offset) => {
+                if relative_offset < 0 {
+                    return Err(io::Error::new(
+                        io::ErrorKind::Unsupported,
+                        "can't seek backwards relative to current position",
+                    ));
+                } else {
+                    self.pos + relative_offset as u64
+                }
+            }
+        };
+
+        // we already know absolute_offset is >= self.pos
+        debug_assert!(
+            absolute_offset >= self.pos,
+            "absolute_offset {} must be >= self.pos {}",
+            absolute_offset,
+            self.pos
+        );
+
+        // calculate bytes to skip
+        let this = self.project();
+        *this.bytes_to_skip = absolute_offset - *this.pos;
+
+        debug!(bytes_to_skip = *this.bytes_to_skip, "seek");
+
+        Ok(())
+    }
+
+    #[instrument(skip_all)]
+    fn poll_complete(
+        mut self: std::pin::Pin<&mut Self>,
+        cx: &mut std::task::Context<'_>,
+    ) -> Poll<std::io::Result<u64>> {
+        if self.bytes_to_skip == 0 {
+            // return the new position (from the start of the stream)
+            return Poll::Ready(Ok(self.pos));
+        }
+
+        // discard some bytes, until pos is where we want it to be.
+        // We create a buffer that we'll discard later on.
+        let mut discard_buf = [0; DISCARD_BUF_SIZE];
+
+        // Loop until we've reached the desired seek position. This is done by issuing repeated
+        // `poll_read` calls.
+        // If the data is not available yet, we will yield back to the executor
+        // and wait to be polled again.
+        loop {
+            if self.bytes_to_skip == 0 {
+                return Poll::Ready(Ok(self.pos));
+            }
+
+            // calculate the length we want to skip at most, which is either a max
+            // buffer size, or the number of remaining bytes to read, whatever is
+            // smaller.
+            let bytes_to_skip_now = std::cmp::min(self.bytes_to_skip as usize, discard_buf.len());
+            let mut discard_buf = tokio::io::ReadBuf::new(&mut discard_buf[..bytes_to_skip_now]);
+
+            ready!(self.as_mut().poll_read(cx, &mut discard_buf))?;
+            let bytes_skipped = discard_buf.filled().len();
+
+            if bytes_skipped == 0 {
+                return Poll::Ready(Err(io::Error::new(
+                    io::ErrorKind::UnexpectedEof,
+                    "got EOF while trying to skip bytes",
+                )));
+            }
+            // decrement bytes to skip. The poll_read call already updated self.pos.
+            *self.as_mut().project().bytes_to_skip -= bytes_skipped as u64;
+        }
+    }
+}
+
+impl<R: tokio::io::AsyncRead + Send + Unpin + 'static> BlobReader for NaiveSeeker<R> {}
+
+#[cfg(test)]
+mod tests {
+    use super::{NaiveSeeker, DISCARD_BUF_SIZE};
+    use std::io::{Cursor, SeekFrom};
+    use tokio::io::{AsyncReadExt, AsyncSeekExt};
+
+    /// This seek requires multiple `poll_read` as we use a multiples of
+    /// DISCARD_BUF_SIZE when doing the seek.
+    /// This ensures we don't hang indefinitely.
+    #[tokio::test]
+    async fn seek() {
+        let buf = vec![0u8; DISCARD_BUF_SIZE * 4];
+        let reader = Cursor::new(&buf);
+        let mut seeker = NaiveSeeker::new(reader);
+        seeker.seek(SeekFrom::Start(4000)).await.unwrap();
+    }
+
+    #[tokio::test]
+    async fn seek_read() {
+        let mut buf = vec![0u8; DISCARD_BUF_SIZE * 2];
+        buf.extend_from_slice(&[1u8; DISCARD_BUF_SIZE * 2]);
+        buf.extend_from_slice(&[2u8; DISCARD_BUF_SIZE * 2]);
+
+        let reader = Cursor::new(&buf);
+        let mut seeker = NaiveSeeker::new(reader);
+
+        let mut read_buf = vec![0u8; DISCARD_BUF_SIZE];
+        seeker.read_exact(&mut read_buf).await.expect("must read");
+        assert_eq!(read_buf.as_slice(), &[0u8; DISCARD_BUF_SIZE]);
+
+        seeker
+            .seek(SeekFrom::Current(DISCARD_BUF_SIZE as i64))
+            .await
+            .expect("must seek");
+        seeker.read_exact(&mut read_buf).await.expect("must read");
+        assert_eq!(read_buf.as_slice(), &[1u8; DISCARD_BUF_SIZE]);
+
+        seeker
+            .seek(SeekFrom::Start(2 * 2 * DISCARD_BUF_SIZE as u64))
+            .await
+            .expect("must seek");
+        seeker.read_exact(&mut read_buf).await.expect("must read");
+        assert_eq!(read_buf.as_slice(), &[2u8; DISCARD_BUF_SIZE]);
+    }
+}
diff --git a/tvix/castore/src/blobservice/object_store.rs b/tvix/castore/src/blobservice/object_store.rs
new file mode 100644
index 0000000000..d2d0a288a5
--- /dev/null
+++ b/tvix/castore/src/blobservice/object_store.rs
@@ -0,0 +1,546 @@
+use std::{
+    io::{self, Cursor},
+    pin::pin,
+    sync::Arc,
+    task::Poll,
+};
+
+use data_encoding::HEXLOWER;
+use fastcdc::v2020::AsyncStreamCDC;
+use futures::Future;
+use object_store::{path::Path, ObjectStore};
+use pin_project_lite::pin_project;
+use prost::Message;
+use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt};
+use tokio_stream::StreamExt;
+use tonic::async_trait;
+use tracing::{debug, instrument, trace, Level};
+use url::Url;
+
+use crate::{
+    proto::{stat_blob_response::ChunkMeta, StatBlobResponse},
+    B3Digest, B3HashingReader,
+};
+
+use super::{BlobReader, BlobService, BlobWriter, ChunkedReader};
+
+#[derive(Clone)]
+pub struct ObjectStoreBlobService {
+    object_store: Arc<dyn ObjectStore>,
+    base_path: Path,
+
+    /// Average chunk size for FastCDC, in bytes.
+    /// min value is half, max value double of that number.
+    avg_chunk_size: u32,
+}
+
+/// Uses any object storage supported by the [object_store] crate to provide a
+/// tvix-castore [BlobService].
+///
+/// # Data format
+/// Data is organized in "blobs" and "chunks".
+/// Blobs don't hold the actual data, but instead contain a list of more
+/// granular chunks that assemble to the contents requested.
+/// This allows clients to seek, and not download chunks they already have
+/// locally, as it's referred to from other files.
+/// Check `rpc_blobstore` and more general BlobStore docs on that.
+///
+/// ## Blobs
+/// Stored at `${base_path}/blobs/b3/$digest_key`. They contains the serialized
+/// StatBlobResponse for the blob with the digest.
+///
+/// ## Chunks
+/// Chunks are stored at `${base_path}/chunks/b3/$digest_key`. They contain
+/// the literal contents of the chunk, but are zstd-compressed.
+///
+/// ## Digest key sharding
+/// The blake3 digest encoded in lower hex, and sharded after the second
+/// character.
+/// The blob for "Hello World" is stored at
+/// `${base_path}/blobs/b3/41/41f8394111eb713a22165c46c90ab8f0fd9399c92028fd6d288944b23ff5bf76`.
+///
+/// This reduces the number of files in the same directory, which would be a
+/// problem at least when using [object_store::local::LocalFileSystem].
+///
+/// # Future changes
+/// There's no guarantees about this being a final format yet.
+/// Once object_store gets support for additional metadata / content-types,
+/// we can eliminate some requests (small blobs only consisting of a single
+/// chunk can be stored as-is, without the blob index file).
+/// It also allows signalling any compression of chunks in the content-type.
+/// Migration *should* be possible by simply adding the right content-types to
+/// all keys stored so far, but no promises ;-)
+impl ObjectStoreBlobService {
+    /// Constructs a new [ObjectStoreBlobService] from a [Url] supported by
+    /// [object_store].
+    /// Any path suffix becomes the base path of the object store.
+    /// additional options, the same as in [object_store::parse_url_opts] can
+    /// be passed.
+    pub fn parse_url_opts<I, K, V>(url: &Url, options: I) -> Result<Self, object_store::Error>
+    where
+        I: IntoIterator<Item = (K, V)>,
+        K: AsRef<str>,
+        V: Into<String>,
+    {
+        let (object_store, path) = object_store::parse_url_opts(url, options)?;
+
+        Ok(Self {
+            object_store: Arc::new(object_store),
+            base_path: path,
+            avg_chunk_size: 256 * 1024,
+        })
+    }
+
+    /// Like [Self::parse_url_opts], except without the options.
+    pub fn parse_url(url: &Url) -> Result<Self, object_store::Error> {
+        Self::parse_url_opts(url, Vec::<(String, String)>::new())
+    }
+}
+
+#[instrument(level=Level::TRACE, skip_all,fields(base_path=%base_path,blob.digest=%digest),ret(Display))]
+fn derive_blob_path(base_path: &Path, digest: &B3Digest) -> Path {
+    base_path
+        .child("blobs")
+        .child("b3")
+        .child(HEXLOWER.encode(&digest.as_slice()[..2]))
+        .child(HEXLOWER.encode(digest.as_slice()))
+}
+
+#[instrument(level=Level::TRACE, skip_all,fields(base_path=%base_path,chunk.digest=%digest),ret(Display))]
+fn derive_chunk_path(base_path: &Path, digest: &B3Digest) -> Path {
+    base_path
+        .child("chunks")
+        .child("b3")
+        .child(HEXLOWER.encode(&digest.as_slice()[..2]))
+        .child(HEXLOWER.encode(digest.as_slice()))
+}
+
+#[async_trait]
+impl BlobService for ObjectStoreBlobService {
+    #[instrument(skip_all, ret, err, fields(blob.digest=%digest))]
+    async fn has(&self, digest: &B3Digest) -> io::Result<bool> {
+        // TODO: clarify if this should work for chunks or not, and explicitly
+        // document in the proto docs.
+        let p = derive_blob_path(&self.base_path, digest);
+
+        match self.object_store.head(&p).await {
+            Ok(_) => Ok(true),
+            Err(object_store::Error::NotFound { .. }) => {
+                let p = derive_chunk_path(&self.base_path, digest);
+                match self.object_store.head(&p).await {
+                    Ok(_) => Ok(true),
+                    Err(object_store::Error::NotFound { .. }) => Ok(false),
+                    Err(e) => Err(e)?,
+                }
+            }
+            Err(e) => Err(e)?,
+        }
+    }
+
+    #[instrument(skip_all, err, fields(blob.digest=%digest))]
+    async fn open_read(&self, digest: &B3Digest) -> io::Result<Option<Box<dyn BlobReader>>> {
+        // handle reading the empty blob.
+        if digest.as_slice() == blake3::hash(b"").as_bytes() {
+            return Ok(Some(Box::new(Cursor::new(b"")) as Box<dyn BlobReader>));
+        }
+        match self
+            .object_store
+            .get(&derive_chunk_path(&self.base_path, digest))
+            .await
+        {
+            Ok(res) => {
+                // handle reading blobs that are small enough to fit inside a single chunk:
+                // fetch the entire chunk into memory, decompress, ensure the b3 digest matches,
+                // and return a io::Cursor over that data.
+                // FUTUREWORK: use zstd::bulk to prevent decompression bombs
+
+                let chunk_raw_bytes = res.bytes().await?;
+                let chunk_contents = zstd::stream::decode_all(Cursor::new(chunk_raw_bytes))?;
+
+                if *digest != blake3::hash(&chunk_contents).as_bytes().into() {
+                    Err(io::Error::other("chunk contents invalid"))?;
+                }
+
+                Ok(Some(Box::new(Cursor::new(chunk_contents))))
+            }
+            Err(object_store::Error::NotFound { .. }) => {
+                // NOTE: For public-facing things, we would want to stop here.
+                // Clients should fetch granularly, so they can make use of
+                // chunks they have locally.
+                // However, if this is used directly, without any caches, do the
+                // assembly here.
+                // This is subject to change, once we have store composition.
+                // TODO: make this configurable, and/or clarify behaviour for
+                // the gRPC server surface (explicitly document behaviour in the
+                // proto docs)
+                if let Some(chunks) = self.chunks(digest).await? {
+                    let chunked_reader = ChunkedReader::from_chunks(
+                        chunks.into_iter().map(|chunk| {
+                            (
+                                chunk.digest.try_into().expect("invalid b3 digest"),
+                                chunk.size,
+                            )
+                        }),
+                        Arc::new(self.clone()) as Arc<dyn BlobService>,
+                    );
+
+                    Ok(Some(Box::new(chunked_reader)))
+                } else {
+                    // This is neither a chunk nor a blob, return None.
+                    Ok(None)
+                }
+            }
+            Err(e) => Err(e.into()),
+        }
+    }
+
+    #[instrument(skip_all)]
+    async fn open_write(&self) -> Box<dyn BlobWriter> {
+        // ObjectStoreBlobWriter implements AsyncWrite, but all the chunking
+        // needs an AsyncRead, so we create a pipe here.
+        // In its `AsyncWrite` implementation, `ObjectStoreBlobWriter` delegates
+        // writes to w. It periodically polls the future that's reading from the
+        // other side.
+        let (w, r) = tokio::io::duplex(self.avg_chunk_size as usize * 10);
+
+        Box::new(ObjectStoreBlobWriter {
+            writer: Some(w),
+            fut: Some(Box::pin(chunk_and_upload(
+                r,
+                self.object_store.clone(),
+                self.base_path.clone(),
+                self.avg_chunk_size / 2,
+                self.avg_chunk_size,
+                self.avg_chunk_size * 2,
+            ))),
+            fut_output: None,
+        })
+    }
+
+    #[instrument(skip_all, err, fields(blob.digest=%digest))]
+    async fn chunks(&self, digest: &B3Digest) -> io::Result<Option<Vec<ChunkMeta>>> {
+        match self
+            .object_store
+            .get(&derive_blob_path(&self.base_path, digest))
+            .await
+        {
+            Ok(get_result) => {
+                // fetch the data at the blob path
+                let blob_data = get_result.bytes().await?;
+                // parse into StatBlobResponse
+                let stat_blob_response: StatBlobResponse = StatBlobResponse::decode(blob_data)?;
+
+                debug!(
+                    chunk.count = stat_blob_response.chunks.len(),
+                    blob.size = stat_blob_response
+                        .chunks
+                        .iter()
+                        .map(|x| x.size)
+                        .sum::<u64>(),
+                    "found more granular chunks"
+                );
+
+                Ok(Some(stat_blob_response.chunks))
+            }
+            Err(object_store::Error::NotFound { .. }) => {
+                // If there's only a chunk, we must return the empty vec here, rather than None.
+                match self
+                    .object_store
+                    .head(&derive_chunk_path(&self.base_path, digest))
+                    .await
+                {
+                    Ok(_) => {
+                        // present, but no more chunks available
+                        debug!("found a single chunk");
+                        Ok(Some(vec![]))
+                    }
+                    Err(object_store::Error::NotFound { .. }) => {
+                        // Neither blob nor single chunk found
+                        debug!("not found");
+                        Ok(None)
+                    }
+                    // error checking for chunk
+                    Err(e) => Err(e.into()),
+                }
+            }
+            // error checking for blob
+            Err(err) => Err(err.into()),
+        }
+    }
+}
+
+/// Reads blob contents from a AsyncRead, chunks and uploads them.
+/// On success, returns a [StatBlobResponse] pointing to the individual chunks.
+#[instrument(skip_all, fields(base_path=%base_path, min_chunk_size, avg_chunk_size, max_chunk_size), err)]
+async fn chunk_and_upload<R: AsyncRead + Unpin>(
+    r: R,
+    object_store: Arc<dyn ObjectStore>,
+    base_path: Path,
+    min_chunk_size: u32,
+    avg_chunk_size: u32,
+    max_chunk_size: u32,
+) -> io::Result<B3Digest> {
+    // wrap reader with something calculating the blake3 hash of all data read.
+    let mut b3_r = B3HashingReader::from(r);
+    // set up a fastcdc chunker
+    let mut chunker =
+        AsyncStreamCDC::new(&mut b3_r, min_chunk_size, avg_chunk_size, max_chunk_size);
+
+    /// This really should just belong into the closure at
+    /// `chunker.as_stream().then(|_| { … })``, but if we try to, rustc spits
+    /// higher-ranked lifetime errors at us.
+    async fn fastcdc_chunk_uploader(
+        resp: Result<fastcdc::v2020::ChunkData, fastcdc::v2020::Error>,
+        base_path: Path,
+        object_store: Arc<dyn ObjectStore>,
+    ) -> std::io::Result<ChunkMeta> {
+        let chunk_data = resp?;
+        let chunk_digest: B3Digest = blake3::hash(&chunk_data.data).as_bytes().into();
+        let chunk_path = derive_chunk_path(&base_path, &chunk_digest);
+
+        upload_chunk(object_store, chunk_digest, chunk_path, chunk_data.data).await
+    }
+
+    // Use the fastcdc chunker to produce a stream of chunks, and upload these
+    // that don't exist to the backend.
+    let chunks = chunker
+        .as_stream()
+        .then(|resp| fastcdc_chunk_uploader(resp, base_path.clone(), object_store.clone()))
+        .collect::<io::Result<Vec<ChunkMeta>>>()
+        .await?;
+
+    let stat_blob_response = StatBlobResponse {
+        chunks,
+        bao: "".into(), // still todo
+    };
+
+    // check for Blob, if it doesn't exist, persist.
+    let blob_digest: B3Digest = b3_r.digest().into();
+    let blob_path = derive_blob_path(&base_path, &blob_digest);
+
+    match object_store.head(&blob_path).await {
+        // blob already exists, nothing to do
+        Ok(_) => {
+            trace!(
+                blob.digest = %blob_digest,
+                blob.path = %blob_path,
+                "blob already exists on backend"
+            );
+        }
+        // chunk does not yet exist, upload first
+        Err(object_store::Error::NotFound { .. }) => {
+            debug!(
+                blob.digest = %blob_digest,
+                blob.path = %blob_path,
+                "uploading blob"
+            );
+            object_store
+                .put(&blob_path, stat_blob_response.encode_to_vec().into())
+                .await?;
+        }
+        Err(err) => {
+            // other error
+            Err(err)?
+        }
+    }
+
+    Ok(blob_digest)
+}
+
+/// upload chunk if it doesn't exist yet.
+#[instrument(skip_all, fields(chunk.digest = %chunk_digest, chunk.size = chunk_data.len(), chunk.path = %chunk_path), err)]
+async fn upload_chunk(
+    object_store: Arc<dyn ObjectStore>,
+    chunk_digest: B3Digest,
+    chunk_path: Path,
+    chunk_data: Vec<u8>,
+) -> std::io::Result<ChunkMeta> {
+    let chunk_size = chunk_data.len();
+    match object_store.head(&chunk_path).await {
+        // chunk already exists, nothing to do
+        Ok(_) => {
+            debug!("chunk already exists");
+        }
+
+        // chunk does not yet exist, compress and upload.
+        Err(object_store::Error::NotFound { .. }) => {
+            let chunk_data_compressed =
+                zstd::encode_all(Cursor::new(chunk_data), zstd::DEFAULT_COMPRESSION_LEVEL)?;
+
+            debug!(chunk.compressed_size=%chunk_data_compressed.len(), "uploading chunk");
+
+            object_store
+                .as_ref()
+                .put(&chunk_path, chunk_data_compressed.into())
+                .await?;
+        }
+        // other error
+        Err(err) => Err(err)?,
+    }
+
+    Ok(ChunkMeta {
+        digest: chunk_digest.into(),
+        size: chunk_size as u64,
+    })
+}
+
+pin_project! {
+    /// Takes care of blob uploads.
+    /// All writes are relayed to self.writer, and we continuously poll the
+    /// future (which will internally read from the other side of the pipe and
+    /// upload chunks).
+    /// Our BlobWriter::close() needs to drop self.writer, so the other side
+    /// will read EOF and can finalize the blob.
+    /// The future should then resolve and return the blob digest.
+    pub struct ObjectStoreBlobWriter<W, Fut>
+    where
+        W: AsyncWrite,
+        Fut: Future,
+    {
+        #[pin]
+        writer: Option<W>,
+
+        #[pin]
+        fut: Option<Fut>,
+
+        fut_output: Option<io::Result<B3Digest>>
+    }
+}
+
+impl<W, Fut> tokio::io::AsyncWrite for ObjectStoreBlobWriter<W, Fut>
+where
+    W: AsyncWrite + Send + Unpin,
+    Fut: Future,
+{
+    fn poll_write(
+        self: std::pin::Pin<&mut Self>,
+        cx: &mut std::task::Context<'_>,
+        buf: &[u8],
+    ) -> std::task::Poll<Result<usize, io::Error>> {
+        let this = self.project();
+        // poll the future.
+        let fut = this.fut.as_pin_mut().expect("not future");
+        let fut_p = fut.poll(cx);
+        // if it's ready, the only way this could have happened is that the
+        // upload failed, because we're only closing `self.writer` after all
+        // writes happened.
+        if fut_p.is_ready() {
+            return Poll::Ready(Err(io::Error::other("upload failed")));
+        }
+
+        // write to the underlying writer
+        this.writer
+            .as_pin_mut()
+            .expect("writer must be some")
+            .poll_write(cx, buf)
+    }
+
+    fn poll_flush(
+        self: std::pin::Pin<&mut Self>,
+        cx: &mut std::task::Context<'_>,
+    ) -> std::task::Poll<Result<(), io::Error>> {
+        let this = self.project();
+        // poll the future.
+        let fut = this.fut.as_pin_mut().expect("not future");
+        let fut_p = fut.poll(cx);
+        // if it's ready, the only way this could have happened is that the
+        // upload failed, because we're only closing `self.writer` after all
+        // writes happened.
+        if fut_p.is_ready() {
+            return Poll::Ready(Err(io::Error::other("upload failed")));
+        }
+
+        // Call poll_flush on the writer
+        this.writer
+            .as_pin_mut()
+            .expect("writer must be some")
+            .poll_flush(cx)
+    }
+
+    fn poll_shutdown(
+        self: std::pin::Pin<&mut Self>,
+        _cx: &mut std::task::Context<'_>,
+    ) -> std::task::Poll<Result<(), io::Error>> {
+        // There's nothing to do on shutdown. We might have written some chunks
+        // that are nowhere else referenced, but cleaning them up here would be racy.
+        std::task::Poll::Ready(Ok(()))
+    }
+}
+
+#[async_trait]
+impl<W, Fut> BlobWriter for ObjectStoreBlobWriter<W, Fut>
+where
+    W: AsyncWrite + Send + Unpin,
+    Fut: Future<Output = io::Result<B3Digest>> + Send + Unpin,
+{
+    async fn close(&mut self) -> io::Result<B3Digest> {
+        match self.writer.take() {
+            Some(mut writer) => {
+                // shut down the writer, so the other side will read EOF.
+                writer.shutdown().await?;
+
+                // take out the future.
+                let fut = self.fut.take().expect("fut must be some");
+                // await it.
+                let resp = pin!(fut).await;
+
+                match resp.as_ref() {
+                    // In the case of an Ok value, we store it in self.fut_output,
+                    // so future calls to close can return that.
+                    Ok(b3_digest) => {
+                        self.fut_output = Some(Ok(b3_digest.clone()));
+                    }
+                    Err(e) => {
+                        // for the error type, we need to cheat a bit, as
+                        // they're not clone-able.
+                        // Simply store a sloppy clone, with the same ErrorKind and message there.
+                        self.fut_output = Some(Err(std::io::Error::new(e.kind(), e.to_string())))
+                    }
+                }
+                resp
+            }
+            None => {
+                // called a second time, return self.fut_output.
+                match self.fut_output.as_ref().unwrap() {
+                    Ok(ref b3_digest) => Ok(b3_digest.clone()),
+                    Err(e) => Err(std::io::Error::new(e.kind(), e.to_string())),
+                }
+            }
+        }
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::chunk_and_upload;
+    use crate::{
+        blobservice::{BlobService, ObjectStoreBlobService},
+        fixtures::{BLOB_A, BLOB_A_DIGEST},
+    };
+    use std::{io::Cursor, sync::Arc};
+    use url::Url;
+
+    /// Tests chunk_and_upload directly, bypassing the BlobWriter at open_write().
+    #[tokio::test]
+    async fn test_chunk_and_upload() {
+        let blobsvc = Arc::new(
+            ObjectStoreBlobService::parse_url(&Url::parse("memory:///").unwrap()).unwrap(),
+        );
+
+        let blob_digest = chunk_and_upload(
+            &mut Cursor::new(BLOB_A.to_vec()),
+            blobsvc.object_store.clone(),
+            object_store::path::Path::from("/"),
+            1024 / 2,
+            1024,
+            1024 * 2,
+        )
+        .await
+        .expect("chunk_and_upload succeeds");
+
+        assert_eq!(BLOB_A_DIGEST.clone(), blob_digest);
+
+        // Now we should have the blob
+        assert!(blobsvc.has(&BLOB_A_DIGEST).await.unwrap());
+    }
+}
diff --git a/tvix/castore/src/blobservice/sled.rs b/tvix/castore/src/blobservice/sled.rs
new file mode 100644
index 0000000000..3dd4bff7bc
--- /dev/null
+++ b/tvix/castore/src/blobservice/sled.rs
@@ -0,0 +1,150 @@
+use super::{BlobReader, BlobService, BlobWriter};
+use crate::{B3Digest, Error};
+use std::{
+    io::{self, Cursor, Write},
+    path::Path,
+    task::Poll,
+};
+use tonic::async_trait;
+use tracing::instrument;
+
+#[derive(Clone)]
+pub struct SledBlobService {
+    db: sled::Db,
+}
+
+impl SledBlobService {
+    pub fn new<P: AsRef<Path>>(p: P) -> Result<Self, sled::Error> {
+        let config = sled::Config::default()
+            .use_compression(false) // is a required parameter
+            .path(p);
+        let db = config.open()?;
+
+        Ok(Self { db })
+    }
+
+    pub fn new_temporary() -> Result<Self, sled::Error> {
+        let config = sled::Config::default().temporary(true);
+        let db = config.open()?;
+
+        Ok(Self { db })
+    }
+}
+
+#[async_trait]
+impl BlobService for SledBlobService {
+    #[instrument(skip(self), fields(blob.digest=%digest))]
+    async fn has(&self, digest: &B3Digest) -> io::Result<bool> {
+        match self.db.contains_key(digest.as_slice()) {
+            Ok(has) => Ok(has),
+            Err(e) => Err(io::Error::new(io::ErrorKind::Other, e.to_string())),
+        }
+    }
+
+    #[instrument(skip(self), fields(blob.digest=%digest))]
+    async fn open_read(&self, digest: &B3Digest) -> io::Result<Option<Box<dyn BlobReader>>> {
+        match self.db.get(digest.as_slice()) {
+            Ok(None) => Ok(None),
+            Ok(Some(data)) => Ok(Some(Box::new(Cursor::new(data[..].to_vec())))),
+            Err(e) => Err(io::Error::new(io::ErrorKind::Other, e.to_string())),
+        }
+    }
+
+    #[instrument(skip(self))]
+    async fn open_write(&self) -> Box<dyn BlobWriter> {
+        Box::new(SledBlobWriter::new(self.db.clone()))
+    }
+}
+
+pub struct SledBlobWriter {
+    db: sled::Db,
+
+    /// Contains the buffer Vec and hasher, or None if already closed
+    writers: Option<(Vec<u8>, blake3::Hasher)>,
+
+    /// The digest that has been returned, if we successfully closed.
+    digest: Option<B3Digest>,
+}
+
+impl SledBlobWriter {
+    pub fn new(db: sled::Db) -> Self {
+        Self {
+            db,
+            writers: Some((Vec::new(), blake3::Hasher::new())),
+            digest: None,
+        }
+    }
+}
+
+impl tokio::io::AsyncWrite for SledBlobWriter {
+    fn poll_write(
+        mut self: std::pin::Pin<&mut Self>,
+        _cx: &mut std::task::Context<'_>,
+        b: &[u8],
+    ) -> std::task::Poll<Result<usize, io::Error>> {
+        Poll::Ready(match &mut self.writers {
+            None => Err(io::Error::new(
+                io::ErrorKind::NotConnected,
+                "already closed",
+            )),
+            Some((ref mut buf, ref mut hasher)) => {
+                let bytes_written = buf.write(b)?;
+                hasher.write(&b[..bytes_written])
+            }
+        })
+    }
+
+    fn poll_flush(
+        mut self: std::pin::Pin<&mut Self>,
+        _cx: &mut std::task::Context<'_>,
+    ) -> std::task::Poll<Result<(), io::Error>> {
+        Poll::Ready(match &mut self.writers {
+            None => Err(io::Error::new(
+                io::ErrorKind::NotConnected,
+                "already closed",
+            )),
+            Some(_) => Ok(()),
+        })
+    }
+
+    fn poll_shutdown(
+        self: std::pin::Pin<&mut Self>,
+        _cx: &mut std::task::Context<'_>,
+    ) -> std::task::Poll<Result<(), io::Error>> {
+        // shutdown is "instantaneous", we only write to a Vec<u8> as buffer.
+        Poll::Ready(Ok(()))
+    }
+}
+
+#[async_trait]
+impl BlobWriter for SledBlobWriter {
+    async fn close(&mut self) -> io::Result<B3Digest> {
+        if self.writers.is_none() {
+            match &self.digest {
+                Some(digest) => Ok(digest.clone()),
+                None => Err(io::Error::new(
+                    io::ErrorKind::NotConnected,
+                    "already closed",
+                )),
+            }
+        } else {
+            let (buf, hasher) = self.writers.take().unwrap();
+
+            let digest: B3Digest = hasher.finalize().as_bytes().into();
+
+            // Only insert if the blob doesn't already exist.
+            if !self.db.contains_key(digest.as_slice()).map_err(|e| {
+                Error::StorageError(format!("Unable to check if we have blob {}: {}", digest, e))
+            })? {
+                // put buf in there. This will move buf out.
+                self.db
+                    .insert(digest.as_slice(), buf)
+                    .map_err(|e| Error::StorageError(format!("unable to insert blob: {}", e)))?;
+            }
+
+            self.digest = Some(digest.clone());
+
+            Ok(digest)
+        }
+    }
+}
diff --git a/tvix/castore/src/blobservice/tests/mod.rs b/tvix/castore/src/blobservice/tests/mod.rs
new file mode 100644
index 0000000000..30c4e97634
--- /dev/null
+++ b/tvix/castore/src/blobservice/tests/mod.rs
@@ -0,0 +1,254 @@
+//! This contains test scenarios that a given [BlobService] needs to pass.
+//! We use [rstest] and [rstest_reuse] to provide all services we want to test
+//! against, and then apply this template to all test functions.
+
+use rstest::*;
+use rstest_reuse::{self, *};
+use std::io;
+use tokio::io::AsyncReadExt;
+use tokio::io::AsyncSeekExt;
+
+use super::BlobService;
+use crate::blobservice;
+use crate::fixtures::BLOB_A;
+use crate::fixtures::BLOB_A_DIGEST;
+use crate::fixtures::BLOB_B;
+use crate::fixtures::BLOB_B_DIGEST;
+
+mod utils;
+use self::utils::make_grpc_blob_service_client;
+
+/// This produces a template, which will be applied to all individual test functions.
+/// See https://github.com/la10736/rstest/issues/130#issuecomment-968864832
+#[template]
+#[rstest]
+#[case::grpc(make_grpc_blob_service_client().await)]
+#[case::memory(blobservice::from_addr("memory://").await.unwrap())]
+#[case::objectstore_memory(blobservice::from_addr("objectstore+memory://").await.unwrap())]
+#[case::sled(blobservice::from_addr("sled://").await.unwrap())]
+pub fn blob_services(#[case] blob_service: impl BlobService) {}
+
+/// Using [BlobService::has] on a non-existing blob should return false.
+#[apply(blob_services)]
+#[tokio::test]
+async fn has_nonexistent_false(blob_service: impl BlobService) {
+    assert!(!blob_service
+        .has(&BLOB_A_DIGEST)
+        .await
+        .expect("must not fail"));
+}
+
+/// Using [BlobService::chunks] on a non-existing blob should return Ok(None)
+#[apply(blob_services)]
+#[tokio::test]
+async fn chunks_nonexistent_false(blob_service: impl BlobService) {
+    assert!(blob_service
+        .chunks(&BLOB_A_DIGEST)
+        .await
+        .expect("must be ok")
+        .is_none());
+}
+
+// TODO: do tests with `chunks`
+
+/// Trying to read a non-existing blob should return a None instead of a reader.
+#[apply(blob_services)]
+#[tokio::test]
+async fn not_found_read(blob_service: impl BlobService) {
+    assert!(blob_service
+        .open_read(&BLOB_A_DIGEST)
+        .await
+        .expect("must not fail")
+        .is_none())
+}
+
+/// Put a blob in the store, check has, get it back.
+#[apply(blob_services)]
+// #[case::small(&fixtures::BLOB_A, &fixtures::BLOB_A_DIGEST)]
+// #[case::big(&fixtures::BLOB_B, &fixtures::BLOB_B_DIGEST)]
+#[tokio::test]
+async fn put_has_get(blob_service: impl BlobService) {
+    // TODO: figure out how to instantiate this with BLOB_A and BLOB_B, as two separate cases
+    for (blob_contents, blob_digest) in &[
+        (&*BLOB_A, BLOB_A_DIGEST.clone()),
+        (&*BLOB_B, BLOB_B_DIGEST.clone()),
+    ] {
+        let mut w = blob_service.open_write().await;
+
+        let l = tokio::io::copy(&mut io::Cursor::new(blob_contents), &mut w)
+            .await
+            .expect("copy must succeed");
+        assert_eq!(
+            blob_contents.len(),
+            l as usize,
+            "written bytes must match blob length"
+        );
+
+        let digest = w.close().await.expect("close must succeed");
+
+        assert_eq!(*blob_digest, digest, "returned digest must be correct");
+
+        assert!(
+            blob_service.has(blob_digest).await.expect("must not fail"),
+            "blob service should now have the blob"
+        );
+
+        let mut r = blob_service
+            .open_read(blob_digest)
+            .await
+            .expect("open_read must succeed")
+            .expect("must be some");
+
+        let mut buf: Vec<u8> = Vec::new();
+        let mut pinned_reader = std::pin::pin!(r);
+        let l = tokio::io::copy(&mut pinned_reader, &mut buf)
+            .await
+            .expect("copy must succeed");
+
+        assert_eq!(
+            blob_contents.len(),
+            l as usize,
+            "read bytes must match blob length"
+        );
+
+        assert_eq!(&blob_contents[..], &buf, "read blob contents must match");
+    }
+}
+
+/// Put a blob in the store, and seek inside it a bit.
+#[apply(blob_services)]
+#[tokio::test]
+async fn put_seek(blob_service: impl BlobService) {
+    let mut w = blob_service.open_write().await;
+
+    tokio::io::copy(&mut io::Cursor::new(&BLOB_B.to_vec()), &mut w)
+        .await
+        .expect("copy must succeed");
+    w.close().await.expect("close must succeed");
+
+    // open a blob for reading
+    let mut r = blob_service
+        .open_read(&BLOB_B_DIGEST)
+        .await
+        .expect("open_read must succeed")
+        .expect("must be some");
+
+    let mut pos: u64 = 0;
+
+    // read the first 10 bytes, they must match the data in the fixture.
+    {
+        let mut buf = [0; 10];
+        r.read_exact(&mut buf).await.expect("must succeed");
+
+        assert_eq!(
+            &BLOB_B[pos as usize..pos as usize + buf.len()],
+            buf,
+            "expected first 10 bytes to match"
+        );
+
+        pos += buf.len() as u64;
+    }
+    // seek by 0 bytes, using SeekFrom::Start.
+    let p = r
+        .seek(io::SeekFrom::Start(pos))
+        .await
+        .expect("must not fail");
+    assert_eq!(pos, p);
+
+    // read the next 10 bytes, they must match the data in the fixture.
+    {
+        let mut buf = [0; 10];
+        r.read_exact(&mut buf).await.expect("must succeed");
+
+        assert_eq!(
+            &BLOB_B[pos as usize..pos as usize + buf.len()],
+            buf,
+            "expected data to match"
+        );
+
+        pos += buf.len() as u64;
+    }
+
+    // seek by 5 bytes, using SeekFrom::Start.
+    let p = r
+        .seek(io::SeekFrom::Start(pos + 5))
+        .await
+        .expect("must not fail");
+    pos += 5;
+    assert_eq!(pos, p);
+
+    // read the next 10 bytes, they must match the data in the fixture.
+    {
+        let mut buf = [0; 10];
+        r.read_exact(&mut buf).await.expect("must succeed");
+
+        assert_eq!(
+            &BLOB_B[pos as usize..pos as usize + buf.len()],
+            buf,
+            "expected data to match"
+        );
+
+        pos += buf.len() as u64;
+    }
+
+    // seek by 12345 bytes, using SeekFrom::
+    let p = r
+        .seek(io::SeekFrom::Current(12345))
+        .await
+        .expect("must not fail");
+    pos += 12345;
+    assert_eq!(pos, p);
+
+    // read the next 10 bytes, they must match the data in the fixture.
+    {
+        let mut buf = [0; 10];
+        r.read_exact(&mut buf).await.expect("must succeed");
+
+        assert_eq!(
+            &BLOB_B[pos as usize..pos as usize + buf.len()],
+            buf,
+            "expected data to match"
+        );
+
+        #[allow(unused_assignments)]
+        {
+            pos += buf.len() as u64;
+        }
+    }
+
+    // seeking to the end is okay…
+    let p = r
+        .seek(io::SeekFrom::Start(BLOB_B.len() as u64))
+        .await
+        .expect("must not fail");
+    pos = BLOB_B.len() as u64;
+    assert_eq!(pos, p);
+
+    {
+        // but it returns no more data.
+        let mut buf: Vec<u8> = Vec::new();
+        r.read_to_end(&mut buf).await.expect("must not fail");
+        assert!(buf.is_empty(), "expected no more data to be read");
+    }
+
+    // seeking past the end…
+    // should either be ok, but then return 0 bytes.
+    // this matches the behaviour or a Cursor<Vec<u8>>.
+    if let Ok(_pos) = r.seek(io::SeekFrom::Start(BLOB_B.len() as u64 + 1)).await {
+        let mut buf: Vec<u8> = Vec::new();
+        r.read_to_end(&mut buf).await.expect("must not fail");
+        assert!(buf.is_empty(), "expected no more data to be read");
+    }
+    // or not be okay.
+
+    // TODO: this is only broken for the gRPC version
+    // We expect seeking backwards or relative to the end to fail.
+    // r.seek(io::SeekFrom::Current(-1))
+    //     .expect_err("SeekFrom::Current(-1) expected to fail");
+
+    // r.seek(io::SeekFrom::Start(pos - 1))
+    //     .expect_err("SeekFrom::Start(pos-1) expected to fail");
+
+    // r.seek(io::SeekFrom::End(0))
+    //     .expect_err("SeekFrom::End(_) expected to fail");
+}
diff --git a/tvix/castore/src/blobservice/tests/utils.rs b/tvix/castore/src/blobservice/tests/utils.rs
new file mode 100644
index 0000000000..706c4b5e43
--- /dev/null
+++ b/tvix/castore/src/blobservice/tests/utils.rs
@@ -0,0 +1,41 @@
+use crate::blobservice::{BlobService, MemoryBlobService};
+use crate::proto::blob_service_client::BlobServiceClient;
+use crate::proto::GRPCBlobServiceWrapper;
+use crate::{blobservice::GRPCBlobService, proto::blob_service_server::BlobServiceServer};
+use tonic::transport::{Endpoint, Server, Uri};
+
+/// Constructs and returns a gRPC BlobService.
+/// The server part is a [MemoryBlobService], exposed via the
+/// [GRPCBlobServiceWrapper], and connected through a DuplexStream
+pub async fn make_grpc_blob_service_client() -> Box<dyn BlobService> {
+    let (left, right) = tokio::io::duplex(64);
+
+    // spin up a server, which will only connect once, to the left side.
+    tokio::spawn(async {
+        let blob_service = Box::<MemoryBlobService>::default() as Box<dyn BlobService>;
+
+        // spin up a new DirectoryService
+        let mut server = Server::builder();
+        let router = server.add_service(BlobServiceServer::new(GRPCBlobServiceWrapper::new(
+            blob_service,
+        )));
+
+        router
+            .serve_with_incoming(tokio_stream::once(Ok::<_, std::io::Error>(left)))
+            .await
+    });
+
+    // Create a client, connecting to the right side. The URI is unused.
+    let mut maybe_right = Some(right);
+
+    Box::new(GRPCBlobService::from_client(BlobServiceClient::new(
+        Endpoint::try_from("http://[::]:50051")
+            .unwrap()
+            .connect_with_connector(tower::service_fn(move |_: Uri| {
+                let right = maybe_right.take().unwrap();
+                async move { Ok::<_, std::io::Error>(right) }
+            }))
+            .await
+            .unwrap(),
+    )))
+}
diff --git a/tvix/castore/src/digests.rs b/tvix/castore/src/digests.rs
new file mode 100644
index 0000000000..2311c95c4d
--- /dev/null
+++ b/tvix/castore/src/digests.rs
@@ -0,0 +1,86 @@
+use bytes::Bytes;
+use data_encoding::BASE64;
+use thiserror::Error;
+
+#[derive(PartialEq, Eq, Hash)]
+pub struct B3Digest(Bytes);
+
+// TODO: allow converting these errors to crate::Error
+#[derive(Error, Debug)]
+pub enum Error {
+    #[error("invalid digest length: {0}")]
+    InvalidDigestLen(usize),
+}
+
+pub const B3_LEN: usize = 32;
+
+impl B3Digest {
+    pub fn as_slice(&self) -> &[u8] {
+        &self.0[..]
+    }
+}
+
+impl From<B3Digest> for bytes::Bytes {
+    fn from(val: B3Digest) -> Self {
+        val.0
+    }
+}
+
+impl From<digest::Output<blake3::Hasher>> for B3Digest {
+    fn from(value: digest::Output<blake3::Hasher>) -> Self {
+        let v = Into::<[u8; B3_LEN]>::into(value);
+        Self(Bytes::copy_from_slice(&v))
+    }
+}
+
+impl TryFrom<Vec<u8>> for B3Digest {
+    type Error = Error;
+
+    // constructs a [B3Digest] from a [Vec<u8>].
+    // Returns an error if the digest has the wrong length.
+    fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
+        if value.len() != B3_LEN {
+            Err(Error::InvalidDigestLen(value.len()))
+        } else {
+            Ok(Self(value.into()))
+        }
+    }
+}
+
+impl TryFrom<bytes::Bytes> for B3Digest {
+    type Error = Error;
+
+    // constructs a [B3Digest] from a [bytes::Bytes].
+    // Returns an error if the digest has the wrong length.
+    fn try_from(value: bytes::Bytes) -> Result<Self, Self::Error> {
+        if value.len() != B3_LEN {
+            Err(Error::InvalidDigestLen(value.len()))
+        } else {
+            Ok(Self(value))
+        }
+    }
+}
+
+impl From<&[u8; B3_LEN]> for B3Digest {
+    fn from(value: &[u8; B3_LEN]) -> Self {
+        Self(value.to_vec().into())
+    }
+}
+
+impl Clone for B3Digest {
+    fn clone(&self) -> Self {
+        Self(self.0.to_owned())
+    }
+}
+
+impl std::fmt::Display for B3Digest {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "b3:{}", BASE64.encode(&self.0))
+    }
+}
+
+impl std::fmt::Debug for B3Digest {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "b3:{}", BASE64.encode(&self.0))
+    }
+}
diff --git a/tvix/castore/src/directoryservice/bigtable.rs b/tvix/castore/src/directoryservice/bigtable.rs
new file mode 100644
index 0000000000..0fdb24628f
--- /dev/null
+++ b/tvix/castore/src/directoryservice/bigtable.rs
@@ -0,0 +1,357 @@
+use bigtable_rs::{bigtable, google::bigtable::v2 as bigtable_v2};
+use bytes::Bytes;
+use data_encoding::HEXLOWER;
+use futures::stream::BoxStream;
+use prost::Message;
+use serde::{Deserialize, Serialize};
+use serde_with::{serde_as, DurationSeconds};
+use tonic::async_trait;
+use tracing::{instrument, trace, warn};
+
+use super::{utils::traverse_directory, DirectoryPutter, DirectoryService, SimplePutter};
+use crate::{proto, B3Digest, Error};
+
+/// There should not be more than 10 MiB in a single cell.
+/// https://cloud.google.com/bigtable/docs/schema-design#cells
+const CELL_SIZE_LIMIT: u64 = 10 * 1024 * 1024;
+
+/// Provides a [DirectoryService] implementation using
+/// [Bigtable](https://cloud.google.com/bigtable/docs/)
+/// as an underlying K/V store.
+///
+/// # Data format
+/// We use Bigtable as a plain K/V store.
+/// The row key is the digest of the directory, in hexlower.
+/// Inside the row, we currently have a single column/cell, again using the
+/// hexlower directory digest.
+/// Its value is the Directory message, serialized in canonical protobuf.
+/// We currently only populate this column.
+///
+/// In the future, we might want to introduce "bucketing", essentially storing
+/// all directories inserted via `put_multiple_start` in a batched form.
+/// This will prevent looking up intermediate Directories, which are not
+/// directly at the root, so rely on store composition.
+#[derive(Clone)]
+pub struct BigtableDirectoryService {
+    client: bigtable::BigTable,
+    params: BigtableParameters,
+
+    #[cfg(test)]
+    #[allow(dead_code)]
+    /// Holds the temporary directory containing the unix socket, and the
+    /// spawned emulator process.
+    emulator: std::sync::Arc<(tempfile::TempDir, async_process::Child)>,
+}
+
+/// Represents configuration of [BigtableDirectoryService].
+/// This currently conflates both connect parameters and data model/client
+/// behaviour parameters.
+#[serde_as]
+#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
+pub struct BigtableParameters {
+    project_id: String,
+    instance_name: String,
+    #[serde(default)]
+    is_read_only: bool,
+    #[serde(default = "default_channel_size")]
+    channel_size: usize,
+
+    #[serde_as(as = "Option<DurationSeconds<String>>")]
+    #[serde(default = "default_timeout")]
+    timeout: Option<std::time::Duration>,
+    table_name: String,
+    family_name: String,
+
+    #[serde(default = "default_app_profile_id")]
+    app_profile_id: String,
+}
+
+fn default_app_profile_id() -> String {
+    "default".to_owned()
+}
+
+fn default_channel_size() -> usize {
+    4
+}
+
+fn default_timeout() -> Option<std::time::Duration> {
+    Some(std::time::Duration::from_secs(4))
+}
+
+impl BigtableDirectoryService {
+    #[cfg(not(test))]
+    pub async fn connect(params: BigtableParameters) -> Result<Self, bigtable::Error> {
+        let connection = bigtable::BigTableConnection::new(
+            &params.project_id,
+            &params.instance_name,
+            params.is_read_only,
+            params.channel_size,
+            params.timeout,
+        )
+        .await?;
+
+        Ok(Self {
+            client: connection.client(),
+            params,
+        })
+    }
+
+    #[cfg(test)]
+    pub async fn connect(params: BigtableParameters) -> Result<Self, bigtable::Error> {
+        use std::time::Duration;
+
+        use async_process::{Command, Stdio};
+        use tempfile::TempDir;
+        use tokio_retry::{strategy::ExponentialBackoff, Retry};
+
+        let tmpdir = TempDir::new().unwrap();
+
+        let socket_path = tmpdir.path().join("cbtemulator.sock");
+
+        let emulator_process = Command::new("cbtemulator")
+            .arg("-address")
+            .arg(socket_path.clone())
+            .stderr(Stdio::piped())
+            .stdout(Stdio::piped())
+            .kill_on_drop(true)
+            .spawn()
+            .expect("failed to spawn emulator");
+
+        Retry::spawn(
+            ExponentialBackoff::from_millis(20)
+                .max_delay(Duration::from_secs(1))
+                .take(3),
+            || async {
+                if socket_path.exists() {
+                    Ok(())
+                } else {
+                    Err(())
+                }
+            },
+        )
+        .await
+        .expect("failed to wait for socket");
+
+        // populate the emulator
+        for cmd in &[
+            vec!["createtable", &params.table_name],
+            vec!["createfamily", &params.table_name, &params.family_name],
+        ] {
+            Command::new("cbt")
+                .args({
+                    let mut args = vec![
+                        "-instance",
+                        &params.instance_name,
+                        "-project",
+                        &params.project_id,
+                    ];
+                    args.extend_from_slice(cmd);
+                    args
+                })
+                .env(
+                    "BIGTABLE_EMULATOR_HOST",
+                    format!("unix://{}", socket_path.to_string_lossy()),
+                )
+                .output()
+                .await
+                .expect("failed to run cbt setup command");
+        }
+
+        let connection = bigtable_rs::bigtable::BigTableConnection::new_with_emulator(
+            &format!("unix://{}", socket_path.to_string_lossy()),
+            &params.project_id,
+            &params.instance_name,
+            params.is_read_only,
+            params.timeout,
+        )?;
+
+        Ok(Self {
+            client: connection.client(),
+            params,
+            emulator: (tmpdir, emulator_process).into(),
+        })
+    }
+}
+
+/// Derives the row/column key for a given blake3 digest.
+/// We use hexlower encoding, also because it can't be misinterpreted as RE2.
+fn derive_directory_key(digest: &B3Digest) -> String {
+    HEXLOWER.encode(digest.as_slice())
+}
+
+#[async_trait]
+impl DirectoryService for BigtableDirectoryService {
+    #[instrument(skip(self, digest), err, fields(directory.digest = %digest))]
+    async fn get(&self, digest: &B3Digest) -> Result<Option<proto::Directory>, Error> {
+        let mut client = self.client.clone();
+        let directory_key = derive_directory_key(digest);
+
+        let request = bigtable_v2::ReadRowsRequest {
+            app_profile_id: self.params.app_profile_id.to_string(),
+            table_name: client.get_full_table_name(&self.params.table_name),
+            rows_limit: 1,
+            rows: Some(bigtable_v2::RowSet {
+                row_keys: vec![directory_key.clone().into()],
+                row_ranges: vec![],
+            }),
+            // Filter selected family name, and column qualifier matching our digest.
+            // This is to ensure we don't fail once we start bucketing.
+            filter: Some(bigtable_v2::RowFilter {
+                filter: Some(bigtable_v2::row_filter::Filter::Chain(
+                    bigtable_v2::row_filter::Chain {
+                        filters: vec![
+                            bigtable_v2::RowFilter {
+                                filter: Some(
+                                    bigtable_v2::row_filter::Filter::FamilyNameRegexFilter(
+                                        self.params.family_name.to_string(),
+                                    ),
+                                ),
+                            },
+                            bigtable_v2::RowFilter {
+                                filter: Some(
+                                    bigtable_v2::row_filter::Filter::ColumnQualifierRegexFilter(
+                                        directory_key.clone().into(),
+                                    ),
+                                ),
+                            },
+                        ],
+                    },
+                )),
+            }),
+            ..Default::default()
+        };
+
+        let mut response = client
+            .read_rows(request)
+            .await
+            .map_err(|e| Error::StorageError(format!("unable to read rows: {}", e)))?;
+
+        if response.len() != 1 {
+            if response.len() > 1 {
+                // This shouldn't happen, we limit number of rows to 1
+                return Err(Error::StorageError(
+                    "got more than one row from bigtable".into(),
+                ));
+            }
+            // else, this is simply a "not found".
+            return Ok(None);
+        }
+
+        let (row_key, mut row_cells) = response.pop().unwrap();
+        if row_key != directory_key.as_bytes() {
+            // This shouldn't happen, we requested this row key.
+            return Err(Error::StorageError(
+                "got wrong row key from bigtable".into(),
+            ));
+        }
+
+        let row_cell = row_cells
+            .pop()
+            .ok_or_else(|| Error::StorageError("found no cells".into()))?;
+
+        // Ensure there's only one cell (so no more left after the pop())
+        // This shouldn't happen, We filter out other cells in our query.
+        if !row_cells.is_empty() {
+            return Err(Error::StorageError(
+                "more than one cell returned from bigtable".into(),
+            ));
+        }
+
+        // We also require the qualifier to be correct in the filter above,
+        // so this shouldn't happen.
+        if directory_key.as_bytes() != row_cell.qualifier {
+            return Err(Error::StorageError("unexpected cell qualifier".into()));
+        }
+
+        // For the data in that cell, ensure the digest matches what's requested, before parsing.
+        let got_digest = B3Digest::from(blake3::hash(&row_cell.value).as_bytes());
+        if got_digest != *digest {
+            return Err(Error::StorageError(format!(
+                "invalid digest: {}",
+                got_digest
+            )));
+        }
+
+        // Try to parse the value into a Directory message.
+        let directory = proto::Directory::decode(Bytes::from(row_cell.value))
+            .map_err(|e| Error::StorageError(format!("unable to decode directory proto: {}", e)))?;
+
+        // validate the Directory.
+        directory
+            .validate()
+            .map_err(|e| Error::StorageError(format!("invalid Directory message: {}", e)))?;
+
+        Ok(Some(directory))
+    }
+
+    #[instrument(skip(self, directory), err, fields(directory.digest = %directory.digest()))]
+    async fn put(&self, directory: proto::Directory) -> Result<B3Digest, Error> {
+        let directory_digest = directory.digest();
+        let mut client = self.client.clone();
+        let directory_key = derive_directory_key(&directory_digest);
+
+        // Ensure the directory we're trying to upload passes validation
+        directory
+            .validate()
+            .map_err(|e| Error::InvalidRequest(format!("directory is invalid: {}", e)))?;
+
+        let data = directory.encode_to_vec();
+        if data.len() as u64 > CELL_SIZE_LIMIT {
+            return Err(Error::StorageError(
+                "Directory exceeds cell limit on Bigtable".into(),
+            ));
+        }
+
+        let resp = client
+            .check_and_mutate_row(bigtable_v2::CheckAndMutateRowRequest {
+                table_name: client.get_full_table_name(&self.params.table_name),
+                app_profile_id: self.params.app_profile_id.to_string(),
+                row_key: directory_key.clone().into(),
+                predicate_filter: Some(bigtable_v2::RowFilter {
+                    filter: Some(bigtable_v2::row_filter::Filter::ColumnQualifierRegexFilter(
+                        directory_key.clone().into(),
+                    )),
+                }),
+                // If the column was already found, do nothing.
+                true_mutations: vec![],
+                // Else, do the insert.
+                false_mutations: vec![
+                    // https://cloud.google.com/bigtable/docs/writes
+                    bigtable_v2::Mutation {
+                        mutation: Some(bigtable_v2::mutation::Mutation::SetCell(
+                            bigtable_v2::mutation::SetCell {
+                                family_name: self.params.family_name.to_string(),
+                                column_qualifier: directory_key.clone().into(),
+                                timestamp_micros: -1, // use server time to fill timestamp
+                                value: data,
+                            },
+                        )),
+                    },
+                ],
+            })
+            .await
+            .map_err(|e| Error::StorageError(format!("unable to mutate rows: {}", e)))?;
+
+        if resp.predicate_matched {
+            trace!("already existed")
+        }
+
+        Ok(directory_digest)
+    }
+
+    #[instrument(skip_all, fields(directory.digest = %root_directory_digest))]
+    fn get_recursive(
+        &self,
+        root_directory_digest: &B3Digest,
+    ) -> BoxStream<Result<proto::Directory, Error>> {
+        traverse_directory(self.clone(), root_directory_digest)
+    }
+
+    #[instrument(skip_all)]
+    fn put_multiple_start(&self) -> Box<(dyn DirectoryPutter + 'static)>
+    where
+        Self: Clone,
+    {
+        Box::new(SimplePutter::new(self.clone()))
+    }
+}
diff --git a/tvix/castore/src/directoryservice/closure_validator.rs b/tvix/castore/src/directoryservice/closure_validator.rs
new file mode 100644
index 0000000000..183928a86f
--- /dev/null
+++ b/tvix/castore/src/directoryservice/closure_validator.rs
@@ -0,0 +1,268 @@
+use std::collections::{HashMap, HashSet};
+
+use bstr::ByteSlice;
+
+use petgraph::{
+    graph::{DiGraph, NodeIndex},
+    visit::Bfs,
+};
+use tracing::instrument;
+
+use crate::{
+    proto::{self, Directory},
+    B3Digest, Error,
+};
+
+/// This can be used to validate a Directory closure (DAG of connected
+/// Directories), and their insertion order.
+///
+/// Directories need to be inserted (via `add`), in an order from the leaves to
+/// the root (DFS Post-Order).
+/// During insertion, We validate as much as we can at that time:
+///
+///  - individual validation of Directory messages
+///  - validation of insertion order (no upload of not-yet-known Directories)
+///  - validation of size fields of referred Directories
+///
+/// Internally it keeps all received Directories in a directed graph,
+/// with node weights being the Directories and edges pointing to child
+/// directories.
+///
+/// Once all Directories have been inserted, a finalize function can be
+/// called to get a (deduplicated and) validated list of directories, in
+/// insertion order.
+/// During finalize, a check for graph connectivity is performed too, to ensure
+/// there's no disconnected components, and only one root.
+#[derive(Default)]
+pub struct ClosureValidator {
+    // A directed graph, using Directory as node weight, without edge weights.
+    // Edges point from parents to children.
+    graph: DiGraph<Directory, ()>,
+
+    // A lookup table from directory digest to node index.
+    digest_to_node_ix: HashMap<B3Digest, NodeIndex>,
+
+    /// Keeps track of the last-inserted directory graph node index.
+    /// On a correct insert, this will be the root node, from which the DFS post
+    /// order traversal will start from.
+    last_directory_ix: Option<NodeIndex>,
+}
+
+impl ClosureValidator {
+    /// Insert a new Directory into the closure.
+    /// Perform individual Directory validation, validation of insertion order
+    /// and size fields.
+    #[instrument(level = "trace", skip_all, fields(directory.digest=%directory.digest(), directory.size=%directory.size()), err)]
+    pub fn add(&mut self, directory: proto::Directory) -> Result<(), Error> {
+        let digest = directory.digest();
+
+        // If we already saw this node previously, it's already validated and in the graph.
+        if self.digest_to_node_ix.contains_key(&digest) {
+            return Ok(());
+        }
+
+        // Do some general validation
+        directory
+            .validate()
+            .map_err(|e| Error::InvalidRequest(e.to_string()))?;
+
+        // Ensure the directory only refers to directories which we already accepted.
+        // We lookup their node indices and add them to a HashSet.
+        let mut child_ixs = HashSet::new();
+        for dir in &directory.directories {
+            let child_digest = B3Digest::try_from(dir.digest.to_owned()).unwrap(); // validated
+
+            // Ensure the digest has already been seen
+            let child_ix = *self.digest_to_node_ix.get(&child_digest).ok_or_else(|| {
+                Error::InvalidRequest(format!(
+                    "'{}' refers to unseen child dir: {}",
+                    dir.name.as_bstr(),
+                    &child_digest
+                ))
+            })?;
+
+            // Ensure the size specified in the child node matches the directory size itself.
+            let recorded_child_size = self
+                .graph
+                .node_weight(child_ix)
+                .expect("node not found")
+                .size();
+
+            // Ensure the size specified in the child node matches our records.
+            if dir.size != recorded_child_size {
+                return Err(Error::InvalidRequest(format!(
+                    "'{}' has wrong size, specified {}, recorded {}",
+                    dir.name.as_bstr(),
+                    dir.size,
+                    recorded_child_size
+                )));
+            }
+
+            child_ixs.insert(child_ix);
+        }
+
+        // Insert node into the graph, and add edges to all children.
+        let node_ix = self.graph.add_node(directory);
+        for child_ix in child_ixs {
+            self.graph.add_edge(node_ix, child_ix, ());
+        }
+
+        // Record the mapping from digest to node_ix in our lookup table.
+        self.digest_to_node_ix.insert(digest, node_ix);
+
+        // Update last_directory_ix.
+        self.last_directory_ix = Some(node_ix);
+
+        Ok(())
+    }
+
+    /// Ensure that all inserted Directories are connected, then return a
+    /// (deduplicated) and validated list of directories, in from-leaves-to-root
+    /// order.
+    /// In case no elements have been inserted, returns an empty list.
+    #[instrument(level = "trace", skip_all, err)]
+    pub(crate) fn finalize(self) -> Result<Vec<Directory>, Error> {
+        // If no nodes were inserted, an empty list is returned.
+        let last_directory_ix = if let Some(x) = self.last_directory_ix {
+            x
+        } else {
+            return Ok(vec![]);
+        };
+
+        // do a BFS traversal of the graph, starting with the root node to get
+        // (the count of) all nodes reachable from there.
+        let mut traversal = Bfs::new(&self.graph, last_directory_ix);
+
+        let mut visited_directory_count = 0;
+        #[cfg(debug_assertions)]
+        let mut visited_directory_ixs = HashSet::new();
+        #[cfg_attr(not(debug_assertions), allow(unused))]
+        while let Some(directory_ix) = traversal.next(&self.graph) {
+            #[cfg(debug_assertions)]
+            visited_directory_ixs.insert(directory_ix);
+
+            visited_directory_count += 1;
+        }
+
+        // If the number of nodes collected equals the total number of nodes in
+        // the graph, we know all nodes are connected.
+        if visited_directory_count != self.graph.node_count() {
+            // more or less exhaustive error reporting.
+            #[cfg(debug_assertions)]
+            {
+                let all_directory_ixs: HashSet<_> = self.graph.node_indices().collect();
+
+                let unvisited_directories: HashSet<_> = all_directory_ixs
+                    .difference(&visited_directory_ixs)
+                    .map(|ix| self.graph.node_weight(*ix).expect("node not found"))
+                    .collect();
+
+                return Err(Error::InvalidRequest(format!(
+                    "found {} disconnected directories: {:?}",
+                    self.graph.node_count() - visited_directory_ixs.len(),
+                    unvisited_directories
+                )));
+            }
+            #[cfg(not(debug_assertions))]
+            {
+                return Err(Error::InvalidRequest(format!(
+                    "found {} disconnected directories",
+                    self.graph.node_count() - visited_directory_count
+                )));
+            }
+        }
+
+        // Dissolve the graph, returning the nodes as a Vec.
+        // As the graph was populated in a valid DFS PostOrder, we can return
+        // nodes in that same order.
+        let (nodes, _edges) = self.graph.into_nodes_edges();
+        Ok(nodes.into_iter().map(|x| x.weight).collect())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::{
+        fixtures::{DIRECTORY_A, DIRECTORY_B, DIRECTORY_C},
+        proto::{self, Directory},
+    };
+    use lazy_static::lazy_static;
+    use rstest::rstest;
+
+    lazy_static! {
+        pub static ref BROKEN_DIRECTORY : Directory = Directory {
+            symlinks: vec![proto::SymlinkNode {
+                name: "".into(), // invalid name!
+                target: "doesntmatter".into(),
+            }],
+            ..Default::default()
+        };
+
+        pub static ref BROKEN_PARENT_DIRECTORY: Directory = Directory {
+            directories: vec![proto::DirectoryNode {
+                name: "foo".into(),
+                digest: DIRECTORY_A.digest().into(),
+                size: DIRECTORY_A.size() + 42, // wrong!
+            }],
+            ..Default::default()
+        };
+    }
+
+    use super::ClosureValidator;
+
+    #[rstest]
+    /// Uploading an empty directory should succeed.
+    #[case::empty_directory(&[&*DIRECTORY_A], false, Some(vec![&*DIRECTORY_A]))]
+    /// Uploading A, then B (referring to A) should succeed.
+    #[case::simple_closure(&[&*DIRECTORY_A, &*DIRECTORY_B], false, Some(vec![&*DIRECTORY_A, &*DIRECTORY_B]))]
+    /// Uploading A, then A, then C (referring to A twice) should succeed.
+    /// We pretend to be a dumb client not deduping directories.
+    #[case::same_child(&[&*DIRECTORY_A, &*DIRECTORY_A, &*DIRECTORY_C], false, Some(vec![&*DIRECTORY_A, &*DIRECTORY_C]))]
+    /// Uploading A, then C (referring to A twice) should succeed.
+    #[case::same_child_dedup(&[&*DIRECTORY_A, &*DIRECTORY_C], false, Some(vec![&*DIRECTORY_A, &*DIRECTORY_C]))]
+    /// Uploading A, then C (referring to A twice), then B (itself referring to A) should fail during close,
+    /// as B itself would be left unconnected.
+    #[case::unconnected_node(&[&*DIRECTORY_A, &*DIRECTORY_C, &*DIRECTORY_B], false, None)]
+    /// Uploading B (referring to A) should fail immediately, because A was never uploaded.
+    #[case::dangling_pointer(&[&*DIRECTORY_B], true, None)]
+    /// Uploading a directory failing validation should fail immediately.
+    #[case::failing_validation(&[&*BROKEN_DIRECTORY], true, None)]
+    /// Uploading a directory which refers to another Directory with a wrong size should fail.
+    #[case::wrong_size_in_parent(&[&*DIRECTORY_A, &*BROKEN_PARENT_DIRECTORY], true, None)]
+    fn test_uploads(
+        #[case] directories_to_upload: &[&Directory],
+        #[case] exp_fail_upload_last: bool,
+        #[case] exp_finalize: Option<Vec<&Directory>>, // Some(_) if finalize successful, None if not.
+    ) {
+        let mut dcv = ClosureValidator::default();
+        let len_directories_to_upload = directories_to_upload.len();
+
+        for (i, d) in directories_to_upload.iter().enumerate() {
+            let resp = dcv.add((*d).clone());
+            if i == len_directories_to_upload - 1 && exp_fail_upload_last {
+                assert!(resp.is_err(), "expect last put to fail");
+
+                // We don't really care anymore what finalize() would return, as
+                // the add() failed.
+                return;
+            } else {
+                assert!(resp.is_ok(), "expect put to succeed");
+            }
+        }
+
+        // everything was uploaded successfully. Test finalize().
+        let resp = dcv.finalize();
+
+        match exp_finalize {
+            Some(directories) => {
+                assert_eq!(
+                    Vec::from_iter(directories.iter().map(|e| (*e).to_owned())),
+                    resp.expect("drain should succeed")
+                );
+            }
+            None => {
+                resp.expect_err("drain should fail");
+            }
+        }
+    }
+}
diff --git a/tvix/castore/src/directoryservice/from_addr.rs b/tvix/castore/src/directoryservice/from_addr.rs
new file mode 100644
index 0000000000..ae51df6376
--- /dev/null
+++ b/tvix/castore/src/directoryservice/from_addr.rs
@@ -0,0 +1,174 @@
+use url::Url;
+
+use crate::{proto::directory_service_client::DirectoryServiceClient, Error};
+
+use super::{DirectoryService, GRPCDirectoryService, MemoryDirectoryService, SledDirectoryService};
+
+/// Constructs a new instance of a [DirectoryService] from an URI.
+///
+/// The following URIs are supported:
+/// - `memory:`
+///   Uses a in-memory implementation.
+/// - `sled:`
+///   Uses a in-memory sled implementation.
+/// - `sled:///absolute/path/to/somewhere`
+///   Uses sled, using a path on the disk for persistency. Can be only opened
+///   from one process at the same time.
+/// - `grpc+unix:///absolute/path/to/somewhere`
+///   Connects to a local tvix-store gRPC service via Unix socket.
+/// - `grpc+http://host:port`, `grpc+https://host:port`
+///    Connects to a (remote) tvix-store gRPC service.
+pub async fn from_addr(uri: &str) -> Result<Box<dyn DirectoryService>, crate::Error> {
+    #[allow(unused_mut)]
+    let mut url = Url::parse(uri)
+        .map_err(|e| crate::Error::StorageError(format!("unable to parse url: {}", e)))?;
+
+    let directory_service: Box<dyn DirectoryService> = match url.scheme() {
+        "memory" => {
+            // memory doesn't support host or path in the URL.
+            if url.has_host() || !url.path().is_empty() {
+                return Err(Error::StorageError("invalid url".to_string()));
+            }
+            Box::<MemoryDirectoryService>::default()
+        }
+        "sled" => {
+            // sled doesn't support host, and a path can be provided (otherwise
+            // it'll live in memory only).
+            if url.has_host() {
+                return Err(Error::StorageError("no host allowed".to_string()));
+            }
+
+            if url.path() == "/" {
+                return Err(Error::StorageError(
+                    "cowardly refusing to open / with sled".to_string(),
+                ));
+            }
+
+            // TODO: expose compression and other parameters as URL parameters?
+
+            Box::new(if url.path().is_empty() {
+                SledDirectoryService::new_temporary()
+                    .map_err(|e| Error::StorageError(e.to_string()))?
+            } else {
+                SledDirectoryService::new(url.path())
+                    .map_err(|e| Error::StorageError(e.to_string()))?
+            })
+        }
+        scheme if scheme.starts_with("grpc+") => {
+            // schemes starting with grpc+ go to the GRPCPathInfoService.
+            //   That's normally grpc+unix for unix sockets, and grpc+http(s) for the HTTP counterparts.
+            // - In the case of unix sockets, there must be a path, but may not be a host.
+            // - In the case of non-unix sockets, there must be a host, but no path.
+            // Constructing the channel is handled by tvix_castore::channel::from_url.
+            let client = DirectoryServiceClient::new(crate::tonic::channel_from_url(&url).await?);
+            Box::new(GRPCDirectoryService::from_client(client))
+        }
+        #[cfg(feature = "cloud")]
+        "bigtable" => {
+            use super::bigtable::BigtableParameters;
+            use super::BigtableDirectoryService;
+
+            // parse the instance name from the hostname.
+            let instance_name = url
+                .host_str()
+                .ok_or_else(|| Error::StorageError("instance name missing".into()))?
+                .to_string();
+
+            // … but add it to the query string now, so we just need to parse that.
+            url.query_pairs_mut()
+                .append_pair("instance_name", &instance_name);
+
+            let params: BigtableParameters = serde_qs::from_str(url.query().unwrap_or_default())
+                .map_err(|e| Error::InvalidRequest(format!("failed to parse parameters: {}", e)))?;
+
+            Box::new(
+                BigtableDirectoryService::connect(params)
+                    .await
+                    .map_err(|e| Error::StorageError(e.to_string()))?,
+            )
+        }
+        _ => {
+            return Err(crate::Error::StorageError(format!(
+                "unknown scheme: {}",
+                url.scheme()
+            )))
+        }
+    };
+    Ok(directory_service)
+}
+
+#[cfg(test)]
+mod tests {
+    use super::from_addr;
+    use lazy_static::lazy_static;
+    use rstest::rstest;
+    use tempfile::TempDir;
+
+    lazy_static! {
+        static ref TMPDIR_SLED_1: TempDir = TempDir::new().unwrap();
+        static ref TMPDIR_SLED_2: TempDir = TempDir::new().unwrap();
+    }
+
+    #[rstest]
+    /// This uses an unsupported scheme.
+    #[case::unsupported_scheme("http://foo.example/test", false)]
+    /// This configures sled in temporary mode.
+    #[case::sled_valid_temporary("sled://", true)]
+    /// This configures sled with /, which should fail.
+    #[case::sled_invalid_root("sled:///", false)]
+    /// This configures sled with a host, not path, which should fail.
+    #[case::sled_invalid_host("sled://foo.example", false)]
+    /// This configures sled with a valid path path, which should succeed.
+    #[case::sled_valid_path(&format!("sled://{}", &TMPDIR_SLED_1.path().to_str().unwrap()), true)]
+    /// This configures sled with a host, and a valid path path, which should fail.
+    #[case::sled_invalid_host_with_valid_path(&format!("sled://foo.example{}", &TMPDIR_SLED_2.path().to_str().unwrap()), false)]
+    /// This correctly sets the scheme, and doesn't set a path.
+    #[case::memory_valid("memory://", true)]
+    /// This sets a memory url host to `foo`
+    #[case::memory_invalid_host("memory://foo", false)]
+    /// This sets a memory url path to "/", which is invalid.
+    #[case::memory_invalid_root_path("memory:///", false)]
+    /// This sets a memory url path to "/foo", which is invalid.
+    #[case::memory_invalid_root_path_foo("memory:///foo", false)]
+    /// Correct scheme to connect to a unix socket.
+    #[case::grpc_valid_unix_socket("grpc+unix:///path/to/somewhere", true)]
+    /// Correct scheme for unix socket, but setting a host too, which is invalid.
+    #[case::grpc_invalid_unix_socket_and_host("grpc+unix://host.example/path/to/somewhere", false)]
+    /// Correct scheme to connect to localhost, with port 12345
+    #[case::grpc_valid_ipv6_localhost_port_12345("grpc+http://[::1]:12345", true)]
+    /// Correct scheme to connect to localhost over http, without specifying a port.
+    #[case::grpc_valid_http_host_without_port("grpc+http://localhost", true)]
+    /// Correct scheme to connect to localhost over http, without specifying a port.
+    #[case::grpc_valid_https_host_without_port("grpc+https://localhost", true)]
+    /// Correct scheme to connect to localhost over http, but with additional path, which is invalid.
+    #[case::grpc_invalid_host_and_path("grpc+http://localhost/some-path", false)]
+    /// A valid example for Bigtable
+    #[cfg_attr(
+        all(feature = "cloud", feature = "integration"),
+        case::bigtable_valid_url(
+            "bigtable://instance-1?project_id=project-1&table_name=table-1&family_name=cf1",
+            true
+        )
+    )]
+    /// A valid example for Bigtable, specifying a custom channel size and timeout
+    #[cfg_attr(
+        all(feature = "cloud", feature = "integration"),
+        case::bigtable_valid_url(
+            "bigtable://instance-1?project_id=project-1&table_name=table-1&family_name=cf1&channel_size=10&timeout=10",
+            true
+        )
+    )]
+    /// A invalid Bigtable example (missing fields)
+    #[cfg_attr(
+        all(feature = "cloud", feature = "integration"),
+        case::bigtable_invalid_url("bigtable://instance-1", false)
+    )]
+    #[tokio::test]
+    async fn test_from_addr_tokio(#[case] uri_str: &str, #[case] exp_succeed: bool) {
+        if exp_succeed {
+            from_addr(uri_str).await.expect("should succeed");
+        } else {
+            assert!(from_addr(uri_str).await.is_err(), "should fail");
+        }
+    }
+}
diff --git a/tvix/castore/src/directoryservice/grpc.rs b/tvix/castore/src/directoryservice/grpc.rs
new file mode 100644
index 0000000000..7402fe1b56
--- /dev/null
+++ b/tvix/castore/src/directoryservice/grpc.rs
@@ -0,0 +1,345 @@
+use std::collections::HashSet;
+
+use super::{DirectoryPutter, DirectoryService};
+use crate::proto::{self, get_directory_request::ByWhat};
+use crate::{B3Digest, Error};
+use async_stream::try_stream;
+use futures::stream::BoxStream;
+use tokio::spawn;
+use tokio::sync::mpsc::UnboundedSender;
+use tokio::task::JoinHandle;
+use tokio_stream::wrappers::UnboundedReceiverStream;
+use tonic::async_trait;
+use tonic::Code;
+use tonic::{transport::Channel, Status};
+use tracing::{instrument, warn};
+
+/// Connects to a (remote) tvix-store DirectoryService over gRPC.
+#[derive(Clone)]
+pub struct GRPCDirectoryService {
+    /// The internal reference to a gRPC client.
+    /// Cloning it is cheap, and it internally handles concurrent requests.
+    grpc_client: proto::directory_service_client::DirectoryServiceClient<Channel>,
+}
+
+impl GRPCDirectoryService {
+    /// construct a [GRPCDirectoryService] from a [proto::directory_service_client::DirectoryServiceClient].
+    /// panics if called outside the context of a tokio runtime.
+    pub fn from_client(
+        grpc_client: proto::directory_service_client::DirectoryServiceClient<Channel>,
+    ) -> Self {
+        Self { grpc_client }
+    }
+}
+
+#[async_trait]
+impl DirectoryService for GRPCDirectoryService {
+    #[instrument(level = "trace", skip_all, fields(directory.digest = %digest))]
+    async fn get(
+        &self,
+        digest: &B3Digest,
+    ) -> Result<Option<crate::proto::Directory>, crate::Error> {
+        // Get a new handle to the gRPC client, and copy the digest.
+        let mut grpc_client = self.grpc_client.clone();
+        let digest_cpy = digest.clone();
+        let message = async move {
+            let mut s = grpc_client
+                .get(proto::GetDirectoryRequest {
+                    recursive: false,
+                    by_what: Some(ByWhat::Digest(digest_cpy.into())),
+                })
+                .await?
+                .into_inner();
+
+            // Retrieve the first message only, then close the stream (we set recursive to false)
+            s.message().await
+        };
+
+        let digest = digest.clone();
+        match message.await {
+            Ok(Some(directory)) => {
+                // Validate the retrieved Directory indeed has the
+                // digest we expect it to have, to detect corruptions.
+                let actual_digest = directory.digest();
+                if actual_digest != digest {
+                    Err(crate::Error::StorageError(format!(
+                        "requested directory with digest {}, but got {}",
+                        digest, actual_digest
+                    )))
+                } else if let Err(e) = directory.validate() {
+                    // Validate the Directory itself is valid.
+                    warn!("directory failed validation: {}", e.to_string());
+                    Err(crate::Error::StorageError(format!(
+                        "directory {} failed validation: {}",
+                        digest, e,
+                    )))
+                } else {
+                    Ok(Some(directory))
+                }
+            }
+            Ok(None) => Ok(None),
+            Err(e) if e.code() == Code::NotFound => Ok(None),
+            Err(e) => Err(crate::Error::StorageError(e.to_string())),
+        }
+    }
+
+    #[instrument(level = "trace", skip_all, fields(directory.digest = %directory.digest()))]
+    async fn put(&self, directory: crate::proto::Directory) -> Result<B3Digest, crate::Error> {
+        let resp = self
+            .grpc_client
+            .clone()
+            .put(tokio_stream::once(directory))
+            .await;
+
+        match resp {
+            Ok(put_directory_resp) => Ok(put_directory_resp
+                .into_inner()
+                .root_digest
+                .try_into()
+                .map_err(|_| {
+                    Error::StorageError("invalid root digest length in response".to_string())
+                })?),
+            Err(e) => Err(crate::Error::StorageError(e.to_string())),
+        }
+    }
+
+    #[instrument(level = "trace", skip_all, fields(directory.digest = %root_directory_digest))]
+    fn get_recursive(
+        &self,
+        root_directory_digest: &B3Digest,
+    ) -> BoxStream<Result<proto::Directory, Error>> {
+        let mut grpc_client = self.grpc_client.clone();
+        let root_directory_digest = root_directory_digest.clone();
+
+        let stream = try_stream! {
+            let mut stream = grpc_client
+                .get(proto::GetDirectoryRequest {
+                    recursive: true,
+                    by_what: Some(ByWhat::Digest(root_directory_digest.clone().into())),
+                })
+                .await
+                .map_err(|e| crate::Error::StorageError(e.to_string()))?
+                .into_inner();
+
+            // The Directory digests we received so far
+            let mut received_directory_digests: HashSet<B3Digest> = HashSet::new();
+            // The Directory digests we're still expecting to get sent.
+            let mut expected_directory_digests: HashSet<B3Digest> = HashSet::from([root_directory_digest]);
+
+            loop {
+                match stream.message().await {
+                    Ok(Some(directory)) => {
+                        // validate the directory itself.
+                        if let Err(e) = directory.validate() {
+                            Err(crate::Error::StorageError(format!(
+                                "directory {} failed validation: {}",
+                                directory.digest(),
+                                e,
+                            )))?;
+                        }
+                        // validate we actually expected that directory, and move it from expected to received.
+                        let directory_digest = directory.digest();
+                        let was_expected = expected_directory_digests.remove(&directory_digest);
+                        if !was_expected {
+                            // FUTUREWORK: dumb clients might send the same stuff twice.
+                            // as a fallback, we might want to tolerate receiving
+                            // it if it's in received_directory_digests (as that
+                            // means it once was in expected_directory_digests)
+                            Err(crate::Error::StorageError(format!(
+                                "received unexpected directory {}",
+                                directory_digest
+                            )))?;
+                        }
+                        received_directory_digests.insert(directory_digest);
+
+                        // register all children in expected_directory_digests.
+                        for child_directory in &directory.directories {
+                            // We ran validate() above, so we know these digests must be correct.
+                            let child_directory_digest =
+                                child_directory.digest.clone().try_into().unwrap();
+
+                            expected_directory_digests
+                                .insert(child_directory_digest);
+                        }
+
+                        yield directory;
+                    },
+                    Ok(None) => {
+                        // If we were still expecting something, that's an error.
+                        if !expected_directory_digests.is_empty() {
+                            Err(crate::Error::StorageError(format!(
+                                "still expected {} directories, but got premature end of stream",
+                                expected_directory_digests.len(),
+                            )))?
+                        } else {
+                            return
+                        }
+                    },
+                    Err(e) => {
+                        Err(crate::Error::StorageError(e.to_string()))?;
+                    },
+                }
+            }
+        };
+
+        Box::pin(stream)
+    }
+
+    #[instrument(skip_all)]
+    fn put_multiple_start(&self) -> Box<(dyn DirectoryPutter + 'static)>
+    where
+        Self: Clone,
+    {
+        let mut grpc_client = self.grpc_client.clone();
+
+        let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
+
+        let task: JoinHandle<Result<proto::PutDirectoryResponse, Status>> = spawn(async move {
+            let s = grpc_client
+                .put(UnboundedReceiverStream::new(rx))
+                .await?
+                .into_inner();
+
+            Ok(s)
+        });
+
+        Box::new(GRPCPutter {
+            rq: Some((task, tx)),
+        })
+    }
+}
+
+/// Allows uploading multiple Directory messages in the same gRPC stream.
+pub struct GRPCPutter {
+    /// Data about the current request - a handle to the task, and the tx part
+    /// of the channel.
+    /// The tx part of the pipe is used to send [proto::Directory] to the ongoing request.
+    /// The task will yield a [proto::PutDirectoryResponse] once the stream is closed.
+    #[allow(clippy::type_complexity)] // lol
+    rq: Option<(
+        JoinHandle<Result<proto::PutDirectoryResponse, Status>>,
+        UnboundedSender<proto::Directory>,
+    )>,
+}
+
+#[async_trait]
+impl DirectoryPutter for GRPCPutter {
+    #[instrument(level = "trace", skip_all, fields(directory.digest=%directory.digest()), err)]
+    async fn put(&mut self, directory: proto::Directory) -> Result<(), crate::Error> {
+        match self.rq {
+            // If we're not already closed, send the directory to directory_sender.
+            Some((_, ref directory_sender)) => {
+                if directory_sender.send(directory).is_err() {
+                    // If the channel has been prematurely closed, invoke close (so we can peek at the error code)
+                    // That error code is much more helpful, because it
+                    // contains the error message from the server.
+                    self.close().await?;
+                }
+                Ok(())
+            }
+            // If self.close() was already called, we can't put again.
+            None => Err(Error::StorageError(
+                "DirectoryPutter already closed".to_string(),
+            )),
+        }
+    }
+
+    /// Closes the stream for sending, and returns the value.
+    #[instrument(level = "trace", skip_all, ret, err)]
+    async fn close(&mut self) -> Result<B3Digest, crate::Error> {
+        // get self.rq, and replace it with None.
+        // This ensures we can only close it once.
+        match std::mem::take(&mut self.rq) {
+            None => Err(Error::StorageError("already closed".to_string())),
+            Some((task, directory_sender)) => {
+                // close directory_sender, so blocking on task will finish.
+                drop(directory_sender);
+
+                let root_digest = task
+                    .await?
+                    .map_err(|e| Error::StorageError(e.to_string()))?
+                    .root_digest;
+
+                root_digest.try_into().map_err(|_| {
+                    Error::StorageError("invalid root digest length in response".to_string())
+                })
+            }
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use std::time::Duration;
+    use tempfile::TempDir;
+    use tokio::net::UnixListener;
+    use tokio_retry::{strategy::ExponentialBackoff, Retry};
+    use tokio_stream::wrappers::UnixListenerStream;
+
+    use crate::{
+        directoryservice::{DirectoryService, GRPCDirectoryService, MemoryDirectoryService},
+        fixtures,
+        proto::{directory_service_client::DirectoryServiceClient, GRPCDirectoryServiceWrapper},
+    };
+
+    /// This ensures connecting via gRPC works as expected.
+    #[tokio::test]
+    async fn test_valid_unix_path_ping_pong() {
+        let tmpdir = TempDir::new().unwrap();
+        let socket_path = tmpdir.path().join("daemon");
+
+        let path_clone = socket_path.clone();
+
+        // Spin up a server
+        tokio::spawn(async {
+            let uds = UnixListener::bind(path_clone).unwrap();
+            let uds_stream = UnixListenerStream::new(uds);
+
+            // spin up a new server
+            let mut server = tonic::transport::Server::builder();
+            let router = server.add_service(
+                crate::proto::directory_service_server::DirectoryServiceServer::new(
+                    GRPCDirectoryServiceWrapper::new(
+                        Box::<MemoryDirectoryService>::default() as Box<dyn DirectoryService>
+                    ),
+                ),
+            );
+            router.serve_with_incoming(uds_stream).await
+        });
+
+        // wait for the socket to be created
+        Retry::spawn(
+            ExponentialBackoff::from_millis(20).max_delay(Duration::from_secs(10)),
+            || async {
+                if socket_path.exists() {
+                    Ok(())
+                } else {
+                    Err(())
+                }
+            },
+        )
+        .await
+        .expect("failed to wait for socket");
+
+        // prepare a client
+        let grpc_client = {
+            let url = url::Url::parse(&format!(
+                "grpc+unix://{}?wait-connect=1",
+                socket_path.display()
+            ))
+            .expect("must parse");
+            let client = DirectoryServiceClient::new(
+                crate::tonic::channel_from_url(&url)
+                    .await
+                    .expect("must succeed"),
+            );
+            GRPCDirectoryService::from_client(client)
+        };
+
+        assert!(grpc_client
+            .get(&fixtures::DIRECTORY_A.digest())
+            .await
+            .expect("must not fail")
+            .is_none())
+    }
+}
diff --git a/tvix/castore/src/directoryservice/memory.rs b/tvix/castore/src/directoryservice/memory.rs
new file mode 100644
index 0000000000..2cbbbd1b16
--- /dev/null
+++ b/tvix/castore/src/directoryservice/memory.rs
@@ -0,0 +1,86 @@
+use crate::{proto, B3Digest, Error};
+use futures::stream::BoxStream;
+use std::collections::HashMap;
+use std::sync::{Arc, RwLock};
+use tonic::async_trait;
+use tracing::{instrument, warn};
+
+use super::utils::traverse_directory;
+use super::{DirectoryPutter, DirectoryService, SimplePutter};
+
+#[derive(Clone, Default)]
+pub struct MemoryDirectoryService {
+    db: Arc<RwLock<HashMap<B3Digest, proto::Directory>>>,
+}
+
+#[async_trait]
+impl DirectoryService for MemoryDirectoryService {
+    #[instrument(skip(self, digest), fields(directory.digest = %digest))]
+    async fn get(&self, digest: &B3Digest) -> Result<Option<proto::Directory>, Error> {
+        let db = self.db.read()?;
+
+        match db.get(digest) {
+            // The directory was not found, return
+            None => Ok(None),
+
+            // The directory was found, try to parse the data as Directory message
+            Some(directory) => {
+                // Validate the retrieved Directory indeed has the
+                // digest we expect it to have, to detect corruptions.
+                let actual_digest = directory.digest();
+                if actual_digest != *digest {
+                    return Err(Error::StorageError(format!(
+                        "requested directory with digest {}, but got {}",
+                        digest, actual_digest
+                    )));
+                }
+
+                // Validate the Directory itself is valid.
+                if let Err(e) = directory.validate() {
+                    warn!("directory failed validation: {}", e.to_string());
+                    return Err(Error::StorageError(format!(
+                        "directory {} failed validation: {}",
+                        actual_digest, e,
+                    )));
+                }
+
+                Ok(Some(directory.clone()))
+            }
+        }
+    }
+
+    #[instrument(skip(self, directory), fields(directory.digest = %directory.digest()))]
+    async fn put(&self, directory: proto::Directory) -> Result<B3Digest, Error> {
+        let digest = directory.digest();
+
+        // validate the directory itself.
+        if let Err(e) = directory.validate() {
+            return Err(Error::InvalidRequest(format!(
+                "directory {} failed validation: {}",
+                digest, e,
+            )));
+        }
+
+        // store it
+        let mut db = self.db.write()?;
+        db.insert(digest.clone(), directory);
+
+        Ok(digest)
+    }
+
+    #[instrument(skip_all, fields(directory.digest = %root_directory_digest))]
+    fn get_recursive(
+        &self,
+        root_directory_digest: &B3Digest,
+    ) -> BoxStream<Result<proto::Directory, Error>> {
+        traverse_directory(self.clone(), root_directory_digest)
+    }
+
+    #[instrument(skip_all)]
+    fn put_multiple_start(&self) -> Box<(dyn DirectoryPutter + 'static)>
+    where
+        Self: Clone,
+    {
+        Box::new(SimplePutter::new(self.clone()))
+    }
+}
diff --git a/tvix/castore/src/directoryservice/mod.rs b/tvix/castore/src/directoryservice/mod.rs
new file mode 100644
index 0000000000..cf6bea39d8
--- /dev/null
+++ b/tvix/castore/src/directoryservice/mod.rs
@@ -0,0 +1,122 @@
+use crate::{proto, B3Digest, Error};
+use futures::stream::BoxStream;
+use tonic::async_trait;
+
+mod closure_validator;
+mod from_addr;
+mod grpc;
+mod memory;
+mod simple_putter;
+mod sled;
+#[cfg(test)]
+pub mod tests;
+mod traverse;
+mod utils;
+
+pub use self::closure_validator::ClosureValidator;
+pub use self::from_addr::from_addr;
+pub use self::grpc::GRPCDirectoryService;
+pub use self::memory::MemoryDirectoryService;
+pub use self::simple_putter::SimplePutter;
+pub use self::sled::SledDirectoryService;
+pub use self::traverse::descend_to;
+pub use self::utils::traverse_directory;
+
+#[cfg(feature = "cloud")]
+mod bigtable;
+
+#[cfg(feature = "cloud")]
+pub use self::bigtable::BigtableDirectoryService;
+
+/// The base trait all Directory services need to implement.
+/// This is a simple get and put of [crate::proto::Directory], returning their
+/// digest.
+#[async_trait]
+pub trait DirectoryService: Send + Sync {
+    /// Looks up a single Directory message by its digest.
+    /// The returned Directory message *must* be valid.
+    /// In case the directory is not found, Ok(None) is returned.
+    ///
+    /// It is okay for certain implementations to only allow retrieval of
+    /// Directory digests that are at the "root", aka the last element that's
+    /// sent to a DirectoryPutter. This makes sense for implementations bundling
+    /// closures of directories together in batches.
+    async fn get(&self, digest: &B3Digest) -> Result<Option<proto::Directory>, Error>;
+    /// Uploads a single Directory message, and returns the calculated
+    /// digest, or an error. An error *must* also be returned if the message is
+    /// not valid.
+    async fn put(&self, directory: proto::Directory) -> Result<B3Digest, Error>;
+
+    /// Looks up a closure of [proto::Directory].
+    /// Ideally this would be a `impl Stream<Item = Result<proto::Directory, Error>>`,
+    /// and we'd be able to add a default implementation for it here, but
+    /// we can't have that yet.
+    ///
+    /// This returns a pinned, boxed stream. The pinning allows for it to be polled easily,
+    /// and the box allows different underlying stream implementations to be returned since
+    /// Rust doesn't support this as a generic in traits yet. This is the same thing that
+    /// [async_trait] generates, but for streams instead of futures.
+    ///
+    /// The individually returned Directory messages *must* be valid.
+    /// Directories are sent in an order from the root to the leaves, so that
+    /// the receiving side can validate each message to be a connected to the root
+    /// that has initially been requested.
+    fn get_recursive(
+        &self,
+        root_directory_digest: &B3Digest,
+    ) -> BoxStream<Result<proto::Directory, Error>>;
+
+    /// Allows persisting a closure of [proto::Directory], which is a graph of
+    /// connected Directory messages.
+    fn put_multiple_start(&self) -> Box<dyn DirectoryPutter>;
+}
+
+#[async_trait]
+impl<A> DirectoryService for A
+where
+    A: AsRef<dyn DirectoryService> + Send + Sync,
+{
+    async fn get(&self, digest: &B3Digest) -> Result<Option<proto::Directory>, Error> {
+        self.as_ref().get(digest).await
+    }
+
+    async fn put(&self, directory: proto::Directory) -> Result<B3Digest, Error> {
+        self.as_ref().put(directory).await
+    }
+
+    fn get_recursive(
+        &self,
+        root_directory_digest: &B3Digest,
+    ) -> BoxStream<Result<proto::Directory, Error>> {
+        self.as_ref().get_recursive(root_directory_digest)
+    }
+
+    fn put_multiple_start(&self) -> Box<dyn DirectoryPutter> {
+        self.as_ref().put_multiple_start()
+    }
+}
+
+/// Provides a handle to put a closure of connected [proto::Directory] elements.
+///
+/// The consumer can periodically call [DirectoryPutter::put], starting from the
+/// leaves. Once the root is reached, [DirectoryPutter::close] can be called to
+/// retrieve the root digest (or an error).
+///
+/// DirectoryPutters might be created without a single [DirectoryPutter::put],
+/// and then dropped without calling [DirectoryPutter::close],
+/// for example when ingesting a path that ends up not pointing to a directory,
+/// but a single file or symlink.
+#[async_trait]
+pub trait DirectoryPutter: Send {
+    /// Put a individual [proto::Directory] into the store.
+    /// Error semantics and behaviour is up to the specific implementation of
+    /// this trait.
+    /// Due to bursting, the returned error might refer to an object previously
+    /// sent via `put`.
+    async fn put(&mut self, directory: proto::Directory) -> Result<(), Error>;
+
+    /// Close the stream, and wait for any errors.
+    /// If there's been any invalid Directory message uploaded, and error *must*
+    /// be returned.
+    async fn close(&mut self) -> Result<B3Digest, Error>;
+}
diff --git a/tvix/castore/src/directoryservice/simple_putter.rs b/tvix/castore/src/directoryservice/simple_putter.rs
new file mode 100644
index 0000000000..25617ebcac
--- /dev/null
+++ b/tvix/castore/src/directoryservice/simple_putter.rs
@@ -0,0 +1,75 @@
+use super::ClosureValidator;
+use super::DirectoryPutter;
+use super::DirectoryService;
+use crate::proto;
+use crate::B3Digest;
+use crate::Error;
+use tonic::async_trait;
+use tracing::instrument;
+use tracing::warn;
+
+/// This is an implementation of DirectoryPutter that simply
+/// inserts individual Directory messages one by one, on close, after
+/// they successfully validated.
+pub struct SimplePutter<DS: DirectoryService> {
+    directory_service: DS,
+
+    directory_validator: Option<ClosureValidator>,
+}
+
+impl<DS: DirectoryService> SimplePutter<DS> {
+    pub fn new(directory_service: DS) -> Self {
+        Self {
+            directory_service,
+            directory_validator: Some(Default::default()),
+        }
+    }
+}
+
+#[async_trait]
+impl<DS: DirectoryService + 'static> DirectoryPutter for SimplePutter<DS> {
+    #[instrument(level = "trace", skip_all, fields(directory.digest=%directory.digest()), err)]
+    async fn put(&mut self, directory: proto::Directory) -> Result<(), Error> {
+        match self.directory_validator {
+            None => return Err(Error::StorageError("already closed".to_string())),
+            Some(ref mut validator) => {
+                validator.add(directory)?;
+            }
+        }
+
+        Ok(())
+    }
+
+    #[instrument(level = "trace", skip_all, ret, err)]
+    async fn close(&mut self) -> Result<B3Digest, Error> {
+        match self.directory_validator.take() {
+            None => Err(Error::InvalidRequest("already closed".to_string())),
+            Some(validator) => {
+                // retrieve the validated directories.
+                let directories = validator.finalize()?;
+
+                // Get the root digest, which is at the end (cf. insertion order)
+                let root_digest = directories
+                    .last()
+                    .ok_or_else(|| Error::InvalidRequest("got no directories".to_string()))?
+                    .digest();
+
+                // call an individual put for each directory and await the insertion.
+                for directory in directories {
+                    let exp_digest = directory.digest();
+                    let actual_digest = self.directory_service.put(directory).await?;
+
+                    // ensure the digest the backend told us matches our expectations.
+                    if exp_digest != actual_digest {
+                        warn!(directory.digest_expected=%exp_digest, directory.digest_actual=%actual_digest, "unexpected digest");
+                        return Err(Error::StorageError(
+                            "got unexpected digest from backend during put".into(),
+                        ));
+                    }
+                }
+
+                Ok(root_digest)
+            }
+        }
+    }
+}
diff --git a/tvix/castore/src/directoryservice/sled.rs b/tvix/castore/src/directoryservice/sled.rs
new file mode 100644
index 0000000000..e4a4c2bbed
--- /dev/null
+++ b/tvix/castore/src/directoryservice/sled.rs
@@ -0,0 +1,168 @@
+use crate::proto::Directory;
+use crate::{proto, B3Digest, Error};
+use futures::stream::BoxStream;
+use prost::Message;
+use std::ops::Deref;
+use std::path::Path;
+use tonic::async_trait;
+use tracing::{instrument, warn};
+
+use super::utils::traverse_directory;
+use super::{ClosureValidator, DirectoryPutter, DirectoryService};
+
+#[derive(Clone)]
+pub struct SledDirectoryService {
+    db: sled::Db,
+}
+
+impl SledDirectoryService {
+    pub fn new<P: AsRef<Path>>(p: P) -> Result<Self, sled::Error> {
+        let config = sled::Config::default()
+            .use_compression(false) // is a required parameter
+            .path(p);
+        let db = config.open()?;
+
+        Ok(Self { db })
+    }
+
+    pub fn new_temporary() -> Result<Self, sled::Error> {
+        let config = sled::Config::default().temporary(true);
+        let db = config.open()?;
+
+        Ok(Self { db })
+    }
+}
+
+#[async_trait]
+impl DirectoryService for SledDirectoryService {
+    #[instrument(skip(self, digest), fields(directory.digest = %digest))]
+    async fn get(&self, digest: &B3Digest) -> Result<Option<proto::Directory>, Error> {
+        match self.db.get(digest.as_slice()) {
+            // The directory was not found, return
+            Ok(None) => Ok(None),
+
+            // The directory was found, try to parse the data as Directory message
+            Ok(Some(data)) => match Directory::decode(&*data) {
+                Ok(directory) => {
+                    // Validate the retrieved Directory indeed has the
+                    // digest we expect it to have, to detect corruptions.
+                    let actual_digest = directory.digest();
+                    if actual_digest != *digest {
+                        return Err(Error::StorageError(format!(
+                            "requested directory with digest {}, but got {}",
+                            digest, actual_digest
+                        )));
+                    }
+
+                    // Validate the Directory itself is valid.
+                    if let Err(e) = directory.validate() {
+                        warn!("directory failed validation: {}", e.to_string());
+                        return Err(Error::StorageError(format!(
+                            "directory {} failed validation: {}",
+                            actual_digest, e,
+                        )));
+                    }
+
+                    Ok(Some(directory))
+                }
+                Err(e) => {
+                    warn!("unable to parse directory {}: {}", digest, e);
+                    Err(Error::StorageError(e.to_string()))
+                }
+            },
+            // some storage error?
+            Err(e) => Err(Error::StorageError(e.to_string())),
+        }
+    }
+
+    #[instrument(skip(self, directory), fields(directory.digest = %directory.digest()))]
+    async fn put(&self, directory: proto::Directory) -> Result<B3Digest, Error> {
+        let digest = directory.digest();
+
+        // validate the directory itself.
+        if let Err(e) = directory.validate() {
+            return Err(Error::InvalidRequest(format!(
+                "directory {} failed validation: {}",
+                digest, e,
+            )));
+        }
+        // store it
+        let result = self.db.insert(digest.as_slice(), directory.encode_to_vec());
+        if let Err(e) = result {
+            return Err(Error::StorageError(e.to_string()));
+        }
+        Ok(digest)
+    }
+
+    #[instrument(skip_all, fields(directory.digest = %root_directory_digest))]
+    fn get_recursive(
+        &self,
+        root_directory_digest: &B3Digest,
+    ) -> BoxStream<Result<proto::Directory, Error>> {
+        traverse_directory(self.clone(), root_directory_digest)
+    }
+
+    #[instrument(skip_all)]
+    fn put_multiple_start(&self) -> Box<(dyn DirectoryPutter + 'static)>
+    where
+        Self: Clone,
+    {
+        Box::new(SledDirectoryPutter {
+            tree: self.db.deref().clone(),
+            directory_validator: Some(Default::default()),
+        })
+    }
+}
+
+/// Buffers Directory messages to be uploaded and inserts them in a batch
+/// transaction on close.
+pub struct SledDirectoryPutter {
+    tree: sled::Tree,
+
+    /// The directories (inside the directory validator) that we insert later,
+    /// or None, if they were already inserted.
+    directory_validator: Option<ClosureValidator>,
+}
+
+#[async_trait]
+impl DirectoryPutter for SledDirectoryPutter {
+    #[instrument(level = "trace", skip_all, fields(directory.digest=%directory.digest()), err)]
+    async fn put(&mut self, directory: proto::Directory) -> Result<(), Error> {
+        match self.directory_validator {
+            None => return Err(Error::StorageError("already closed".to_string())),
+            Some(ref mut validator) => {
+                validator.add(directory)?;
+            }
+        }
+
+        Ok(())
+    }
+
+    #[instrument(level = "trace", skip_all, ret, err)]
+    async fn close(&mut self) -> Result<B3Digest, Error> {
+        match self.directory_validator.take() {
+            None => Err(Error::InvalidRequest("already closed".to_string())),
+            Some(validator) => {
+                // retrieve the validated directories.
+                let directories = validator.finalize()?;
+
+                // Get the root digest, which is at the end (cf. insertion order)
+                let root_digest = directories
+                    .last()
+                    .ok_or_else(|| Error::InvalidRequest("got no directories".to_string()))?
+                    .digest();
+
+                let mut batch = sled::Batch::default();
+                for directory in directories {
+                    batch.insert(directory.digest().as_slice(), directory.encode_to_vec());
+                }
+
+                self.tree
+                    .apply_batch(batch)
+                    .map_err(|e| Error::StorageError(format!("unable to apply batch: {}", e)))?;
+
+                Ok(root_digest)
+            }
+        }
+    }
+}
diff --git a/tvix/castore/src/directoryservice/tests/mod.rs b/tvix/castore/src/directoryservice/tests/mod.rs
new file mode 100644
index 0000000000..1b40d9feb0
--- /dev/null
+++ b/tvix/castore/src/directoryservice/tests/mod.rs
@@ -0,0 +1,226 @@
+//! This contains test scenarios that a given [DirectoryService] needs to pass.
+//! We use [rstest] and [rstest_reuse] to provide all services we want to test
+//! against, and then apply this template to all test functions.
+
+use futures::StreamExt;
+use rstest::*;
+use rstest_reuse::{self, *};
+
+use super::DirectoryService;
+use crate::directoryservice;
+use crate::{
+    fixtures::{DIRECTORY_A, DIRECTORY_B, DIRECTORY_C},
+    proto::{self, Directory},
+};
+
+mod utils;
+use self::utils::make_grpc_directory_service_client;
+
+// TODO: add tests doing individual puts of a closure, then doing a get_recursive
+// (and figure out semantics if necessary)
+
+/// This produces a template, which will be applied to all individual test functions.
+/// See https://github.com/la10736/rstest/issues/130#issuecomment-968864832
+#[template]
+#[rstest]
+#[case::grpc(make_grpc_directory_service_client().await)]
+#[case::memory(directoryservice::from_addr("memory://").await.unwrap())]
+#[case::sled(directoryservice::from_addr("sled://").await.unwrap())]
+#[cfg_attr(all(feature = "cloud", feature = "integration"), case::bigtable(directoryservice::from_addr("bigtable://instance-1?project_id=project-1&table_name=table-1&family_name=cf1").await.unwrap()))]
+pub fn directory_services(#[case] directory_service: impl DirectoryService) {}
+
+/// Ensures asking for a directory that doesn't exist returns a Ok(None).
+#[apply(directory_services)]
+#[tokio::test]
+async fn test_non_exist(directory_service: impl DirectoryService) {
+    let resp = directory_service.get(&DIRECTORY_A.digest()).await;
+    assert!(resp.unwrap().is_none())
+}
+
+/// Putting a single directory into the store, and then getting it out both via
+/// `.get[_recursive]` should work.
+#[apply(directory_services)]
+#[tokio::test]
+async fn put_get(directory_service: impl DirectoryService) {
+    // Insert a Directory.
+    let digest = directory_service.put(DIRECTORY_A.clone()).await.unwrap();
+    assert_eq!(DIRECTORY_A.digest(), digest, "returned digest must match");
+
+    // single get
+    assert_eq!(
+        Some(DIRECTORY_A.clone()),
+        directory_service.get(&DIRECTORY_A.digest()).await.unwrap()
+    );
+
+    // recursive get
+    assert_eq!(
+        vec![Ok(DIRECTORY_A.clone())],
+        directory_service
+            .get_recursive(&DIRECTORY_A.digest())
+            .collect::<Vec<_>>()
+            .await
+    );
+}
+
+/// Putting a directory closure should work, and it should be possible to get
+/// back the root node both via .get[_recursive]. We don't check `.get` for the
+/// leaf node is possible, as it's Ok for stores to not support that.
+#[apply(directory_services)]
+#[tokio::test]
+async fn put_get_multiple_success(directory_service: impl DirectoryService) {
+    // Insert a Directory closure.
+    let mut handle = directory_service.put_multiple_start();
+    handle.put(DIRECTORY_A.clone()).await.unwrap();
+    handle.put(DIRECTORY_C.clone()).await.unwrap();
+    let root_digest = handle.close().await.unwrap();
+    assert_eq!(
+        DIRECTORY_C.digest(),
+        root_digest,
+        "root digest should match"
+    );
+
+    // Get the root node.
+    assert_eq!(
+        Some(DIRECTORY_C.clone()),
+        directory_service.get(&DIRECTORY_C.digest()).await.unwrap()
+    );
+
+    // Get the closure. Ensure it's sent from the root to the leaves.
+    assert_eq!(
+        vec![Ok(DIRECTORY_C.clone()), Ok(DIRECTORY_A.clone())],
+        directory_service
+            .get_recursive(&DIRECTORY_C.digest())
+            .collect::<Vec<_>>()
+            .await
+    )
+}
+
+/// Puts a directory closure, but simulates a dumb client not deduplicating
+/// its list. Ensure we still only get back a deduplicated list.
+#[apply(directory_services)]
+#[tokio::test]
+async fn put_get_multiple_dedup(directory_service: impl DirectoryService) {
+    // Insert a Directory closure.
+    let mut handle = directory_service.put_multiple_start();
+    handle.put(DIRECTORY_A.clone()).await.unwrap();
+    handle.put(DIRECTORY_A.clone()).await.unwrap();
+    handle.put(DIRECTORY_C.clone()).await.unwrap();
+    let root_digest = handle.close().await.unwrap();
+    assert_eq!(
+        DIRECTORY_C.digest(),
+        root_digest,
+        "root digest should match"
+    );
+
+    // Ensure the returned closure only contains `DIRECTORY_A` once.
+    assert_eq!(
+        vec![Ok(DIRECTORY_C.clone()), Ok(DIRECTORY_A.clone())],
+        directory_service
+            .get_recursive(&DIRECTORY_C.digest())
+            .collect::<Vec<_>>()
+            .await
+    )
+}
+
+/// Uploading A, then C (referring to A twice), then B (itself referring to A) should fail during close,
+/// as B itself would be left unconnected.
+#[apply(directory_services)]
+#[tokio::test]
+async fn upload_reject_unconnected(directory_service: impl DirectoryService) {
+    let mut handle = directory_service.put_multiple_start();
+
+    handle.put(DIRECTORY_A.clone()).await.unwrap();
+    handle.put(DIRECTORY_C.clone()).await.unwrap();
+    handle.put(DIRECTORY_B.clone()).await.unwrap();
+
+    assert!(
+        handle.close().await.is_err(),
+        "closing handle should fail, as B would be left unconnected"
+    );
+}
+
+/// Uploading a directory that refers to another directory not yet uploaded
+/// should fail.
+#[apply(directory_services)]
+#[tokio::test]
+async fn upload_reject_dangling_pointer(directory_service: impl DirectoryService) {
+    let mut handle = directory_service.put_multiple_start();
+
+    // We insert DIRECTORY_A on its own, to ensure the check runs for the
+    // individual put_multiple session, not across the global DirectoryService
+    // contents.
+    directory_service.put(DIRECTORY_A.clone()).await.unwrap();
+
+    // DIRECTORY_B refers to DIRECTORY_A, which is not uploaded with this handle.
+    if handle.put(DIRECTORY_B.clone()).await.is_ok() {
+        assert!(
+            handle.close().await.is_err(),
+            "when succeeding put, close must fail"
+        )
+    }
+}
+
+/// Try uploading a Directory failing its internal validation, ensure it gets
+/// rejected.
+#[apply(directory_services)]
+#[tokio::test]
+async fn upload_reject_failing_validation(directory_service: impl DirectoryService) {
+    let broken_directory = Directory {
+        symlinks: vec![proto::SymlinkNode {
+            name: "".into(), // wrong!
+            target: "doesntmatter".into(),
+        }],
+        ..Default::default()
+    };
+    assert!(broken_directory.validate().is_err());
+
+    // Try to upload via single upload.
+    assert!(
+        directory_service
+            .put(broken_directory.clone())
+            .await
+            .is_err(),
+        "single upload must fail"
+    );
+
+    // Try to upload via put_multiple. We're a bit more permissive here, the
+    // intermediate .put() might succeed, due to client-side bursting (in the
+    // case of gRPC), but then the close MUST fail.
+    let mut handle = directory_service.put_multiple_start();
+    if handle.put(broken_directory).await.is_ok() {
+        assert!(
+            handle.close().await.is_err(),
+            "when succeeding put, close must fail"
+        )
+    }
+}
+
+/// Try uploading a Directory that refers to a previously-uploaded directory.
+/// Both pass their isolated validation, but the size field in the parent is wrong.
+/// This should be rejected.
+#[apply(directory_services)]
+#[tokio::test]
+async fn upload_reject_wrong_size(directory_service: impl DirectoryService) {
+    let wrong_parent_directory = Directory {
+        directories: vec![proto::DirectoryNode {
+            name: "foo".into(),
+            digest: DIRECTORY_A.digest().into(),
+            size: DIRECTORY_A.size() + 42, // wrong!
+        }],
+        ..Default::default()
+    };
+
+    // Make sure isolated validation itself is ok
+    assert!(wrong_parent_directory.validate().is_ok());
+
+    // Now upload both. Ensure it either fails during the second put, or during
+    // the close.
+    let mut handle = directory_service.put_multiple_start();
+    handle.put(DIRECTORY_A.clone()).await.unwrap();
+    if handle.put(wrong_parent_directory).await.is_ok() {
+        assert!(
+            handle.close().await.is_err(),
+            "when second put succeeds, close must fail"
+        )
+    }
+}
diff --git a/tvix/castore/src/directoryservice/tests/utils.rs b/tvix/castore/src/directoryservice/tests/utils.rs
new file mode 100644
index 0000000000..0f706695ee
--- /dev/null
+++ b/tvix/castore/src/directoryservice/tests/utils.rs
@@ -0,0 +1,46 @@
+use crate::directoryservice::{DirectoryService, GRPCDirectoryService};
+use crate::proto::directory_service_client::DirectoryServiceClient;
+use crate::proto::GRPCDirectoryServiceWrapper;
+use crate::{
+    directoryservice::MemoryDirectoryService,
+    proto::directory_service_server::DirectoryServiceServer,
+};
+
+use tonic::transport::{Endpoint, Server, Uri};
+
+/// Constructs and returns a gRPC DirectoryService.
+/// The server part is a [MemoryDirectoryService], exposed via the
+/// [GRPCDirectoryServiceWrapper], and connected through a DuplexStream.
+pub async fn make_grpc_directory_service_client() -> Box<dyn DirectoryService> {
+    let (left, right) = tokio::io::duplex(64);
+
+    // spin up a server, which will only connect once, to the left side.
+    tokio::spawn(async {
+        let directory_service =
+            Box::<MemoryDirectoryService>::default() as Box<dyn DirectoryService>;
+
+        let mut server = Server::builder();
+        let router = server.add_service(DirectoryServiceServer::new(
+            GRPCDirectoryServiceWrapper::new(directory_service),
+        ));
+
+        router
+            .serve_with_incoming(tokio_stream::once(Ok::<_, std::io::Error>(left)))
+            .await
+    });
+
+    // Create a client, connecting to the right side. The URI is unused.
+    let mut maybe_right = Some(right);
+    Box::new(GRPCDirectoryService::from_client(
+        DirectoryServiceClient::new(
+            Endpoint::try_from("http://[::]:50051")
+                .unwrap()
+                .connect_with_connector(tower::service_fn(move |_: Uri| {
+                    let right = maybe_right.take().unwrap();
+                    async move { Ok::<_, std::io::Error>(right) }
+                }))
+                .await
+                .unwrap(),
+        ),
+    ))
+}
diff --git a/tvix/castore/src/directoryservice/traverse.rs b/tvix/castore/src/directoryservice/traverse.rs
new file mode 100644
index 0000000000..8a668c868c
--- /dev/null
+++ b/tvix/castore/src/directoryservice/traverse.rs
@@ -0,0 +1,197 @@
+use super::DirectoryService;
+use crate::{proto::NamedNode, B3Digest, Error, Path};
+use tracing::{instrument, warn};
+
+/// This descends from a (root) node to the given (sub)path, returning the Node
+/// at that path, or none, if there's nothing at that path.
+#[instrument(skip(directory_service, path), fields(%path))]
+pub async fn descend_to<DS>(
+    directory_service: DS,
+    root_node: crate::proto::node::Node,
+    path: impl AsRef<Path> + std::fmt::Display,
+) -> Result<Option<crate::proto::node::Node>, Error>
+where
+    DS: AsRef<dyn DirectoryService>,
+{
+    let mut cur_node = root_node;
+    let mut it = path.as_ref().components();
+
+    loop {
+        match it.next() {
+            None => {
+                // the (remaining) path is empty, return the node we're current at.
+                return Ok(Some(cur_node));
+            }
+            Some(first_component) => {
+                match cur_node {
+                    crate::proto::node::Node::File(_) | crate::proto::node::Node::Symlink(_) => {
+                        // There's still some path left, but the current node is no directory.
+                        // This means the path doesn't exist, as we can't reach it.
+                        return Ok(None);
+                    }
+                    crate::proto::node::Node::Directory(directory_node) => {
+                        let digest: B3Digest = directory_node.digest.try_into().map_err(|_e| {
+                            Error::StorageError("invalid digest length".to_string())
+                        })?;
+
+                        // fetch the linked node from the directory_service
+                        match directory_service.as_ref().get(&digest).await? {
+                            // If we didn't get the directory node that's linked, that's a store inconsistency, bail out!
+                            None => {
+                                warn!("directory {} does not exist", digest);
+
+                                return Err(Error::StorageError(format!(
+                                    "directory {} does not exist",
+                                    digest
+                                )));
+                            }
+                            Some(directory) => {
+                                // look for first_component in the [Directory].
+                                // FUTUREWORK: as the nodes() iterator returns in a sorted fashion, we
+                                // could stop as soon as e.name is larger than the search string.
+                                let child_node =
+                                    directory.nodes().find(|n| n.get_name() == first_component);
+
+                                match child_node {
+                                    // child node not found means there's no such element inside the directory.
+                                    None => {
+                                        return Ok(None);
+                                    }
+                                    // child node found, return to top-of loop to find the next
+                                    // node in the path.
+                                    Some(child_node) => {
+                                        cur_node = child_node;
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::{
+        directoryservice,
+        fixtures::{DIRECTORY_COMPLICATED, DIRECTORY_WITH_KEEP},
+        PathBuf,
+    };
+
+    use super::descend_to;
+
+    #[tokio::test]
+    async fn test_descend_to() {
+        let directory_service = directoryservice::from_addr("memory://").await.unwrap();
+
+        let mut handle = directory_service.put_multiple_start();
+        handle
+            .put(DIRECTORY_WITH_KEEP.clone())
+            .await
+            .expect("must succeed");
+        handle
+            .put(DIRECTORY_COMPLICATED.clone())
+            .await
+            .expect("must succeed");
+
+        handle.close().await.expect("must upload");
+
+        // construct the node for DIRECTORY_COMPLICATED
+        let node_directory_complicated =
+            crate::proto::node::Node::Directory(crate::proto::DirectoryNode {
+                name: "doesntmatter".into(),
+                digest: DIRECTORY_COMPLICATED.digest().into(),
+                size: DIRECTORY_COMPLICATED.size(),
+            });
+
+        // construct the node for DIRECTORY_COMPLICATED
+        let node_directory_with_keep = crate::proto::node::Node::Directory(
+            DIRECTORY_COMPLICATED.directories.first().unwrap().clone(),
+        );
+
+        // construct the node for the .keep file
+        let node_file_keep =
+            crate::proto::node::Node::File(DIRECTORY_WITH_KEEP.files.first().unwrap().clone());
+
+        // traversal to an empty subpath should return the root node.
+        {
+            let resp = descend_to(
+                &directory_service,
+                node_directory_complicated.clone(),
+                "".parse::<PathBuf>().unwrap(),
+            )
+            .await
+            .expect("must succeed");
+
+            assert_eq!(Some(node_directory_complicated.clone()), resp);
+        }
+
+        // traversal to `keep` should return the node for DIRECTORY_WITH_KEEP
+        {
+            let resp = descend_to(
+                &directory_service,
+                node_directory_complicated.clone(),
+                "keep".parse::<PathBuf>().unwrap(),
+            )
+            .await
+            .expect("must succeed");
+
+            assert_eq!(Some(node_directory_with_keep), resp);
+        }
+
+        // traversal to `keep/.keep` should return the node for the .keep file
+        {
+            let resp = descend_to(
+                &directory_service,
+                node_directory_complicated.clone(),
+                "keep/.keep".parse::<PathBuf>().unwrap(),
+            )
+            .await
+            .expect("must succeed");
+
+            assert_eq!(Some(node_file_keep.clone()), resp);
+        }
+
+        // traversal to `void` should return None (doesn't exist)
+        {
+            let resp = descend_to(
+                &directory_service,
+                node_directory_complicated.clone(),
+                "void".parse::<PathBuf>().unwrap(),
+            )
+            .await
+            .expect("must succeed");
+
+            assert_eq!(None, resp);
+        }
+
+        // traversal to `v/oid` should return None (doesn't exist)
+        {
+            let resp = descend_to(
+                &directory_service,
+                node_directory_complicated.clone(),
+                "v/oid".parse::<PathBuf>().unwrap(),
+            )
+            .await
+            .expect("must succeed");
+
+            assert_eq!(None, resp);
+        }
+
+        // traversal to `keep/.keep/404` should return None (the path can't be
+        // reached, as keep/.keep already is a file)
+        {
+            let resp = descend_to(
+                &directory_service,
+                node_directory_complicated.clone(),
+                "keep/.keep/foo".parse::<PathBuf>().unwrap(),
+            )
+            .await
+            .expect("must succeed");
+
+            assert_eq!(None, resp);
+        }
+    }
+}
diff --git a/tvix/castore/src/directoryservice/utils.rs b/tvix/castore/src/directoryservice/utils.rs
new file mode 100644
index 0000000000..01c521076c
--- /dev/null
+++ b/tvix/castore/src/directoryservice/utils.rs
@@ -0,0 +1,82 @@
+use super::DirectoryService;
+use crate::proto;
+use crate::B3Digest;
+use crate::Error;
+use async_stream::stream;
+use futures::stream::BoxStream;
+use std::collections::{HashSet, VecDeque};
+use tracing::warn;
+
+/// Traverses a [proto::Directory] from the root to the children.
+///
+/// This is mostly BFS, but directories are only returned once.
+pub fn traverse_directory<'a, DS: DirectoryService + 'static>(
+    directory_service: DS,
+    root_directory_digest: &B3Digest,
+) -> BoxStream<'a, Result<proto::Directory, Error>> {
+    // The list of all directories that still need to be traversed. The next
+    // element is picked from the front, new elements are enqueued at the
+    // back.
+    let mut worklist_directory_digests: VecDeque<B3Digest> =
+        VecDeque::from([root_directory_digest.clone()]);
+    // The list of directory digests already sent to the consumer.
+    // We omit sending the same directories multiple times.
+    let mut sent_directory_digests: HashSet<B3Digest> = HashSet::new();
+
+    let stream = stream! {
+        while let Some(current_directory_digest) = worklist_directory_digests.pop_front() {
+            match directory_service.get(&current_directory_digest).await {
+                // if it's not there, we have an inconsistent store!
+                Ok(None) => {
+                    warn!("directory {} does not exist", current_directory_digest);
+                    yield Err(Error::StorageError(format!(
+                        "directory {} does not exist",
+                        current_directory_digest
+                    )));
+                }
+                Err(e) => {
+                    warn!("failed to look up directory");
+                    yield Err(Error::StorageError(format!(
+                        "unable to look up directory {}: {}",
+                        current_directory_digest, e
+                    )));
+                }
+
+                // if we got it
+                Ok(Some(current_directory)) => {
+                    // validate, we don't want to send invalid directories.
+                    if let Err(e) = current_directory.validate() {
+                        warn!("directory failed validation: {}", e.to_string());
+                        yield Err(Error::StorageError(format!(
+                            "invalid directory: {}",
+                            current_directory_digest
+                        )));
+                    }
+
+                    // We're about to send this directory, so let's avoid sending it again if a
+                    // descendant has it.
+                    sent_directory_digests.insert(current_directory_digest);
+
+                    // enqueue all child directory digests to the work queue, as
+                    // long as they're not part of the worklist or already sent.
+                    // This panics if the digest looks invalid, it's supposed to be checked first.
+                    for child_directory_node in &current_directory.directories {
+                        // TODO: propagate error
+                        let child_digest: B3Digest = child_directory_node.digest.clone().try_into().unwrap();
+
+                        if worklist_directory_digests.contains(&child_digest)
+                            || sent_directory_digests.contains(&child_digest)
+                        {
+                            continue;
+                        }
+                        worklist_directory_digests.push_back(child_digest);
+                    }
+
+                    yield Ok(current_directory);
+                }
+            };
+        }
+    };
+
+    Box::pin(stream)
+}
diff --git a/tvix/castore/src/errors.rs b/tvix/castore/src/errors.rs
new file mode 100644
index 0000000000..e807a19b9e
--- /dev/null
+++ b/tvix/castore/src/errors.rs
@@ -0,0 +1,61 @@
+use std::sync::PoisonError;
+use thiserror::Error;
+use tokio::task::JoinError;
+use tonic::Status;
+
+/// Errors related to communication with the store.
+#[derive(Debug, Error, PartialEq)]
+pub enum Error {
+    #[error("invalid request: {0}")]
+    InvalidRequest(String),
+
+    #[error("internal storage error: {0}")]
+    StorageError(String),
+}
+
+impl<T> From<PoisonError<T>> for Error {
+    fn from(value: PoisonError<T>) -> Self {
+        Error::StorageError(value.to_string())
+    }
+}
+
+impl From<JoinError> for Error {
+    fn from(value: JoinError) -> Self {
+        Error::StorageError(value.to_string())
+    }
+}
+
+impl From<Error> for Status {
+    fn from(value: Error) -> Self {
+        match value {
+            Error::InvalidRequest(msg) => Status::invalid_argument(msg),
+            Error::StorageError(msg) => Status::data_loss(format!("storage error: {}", msg)),
+        }
+    }
+}
+
+impl From<crate::tonic::Error> for Error {
+    fn from(value: crate::tonic::Error) -> Self {
+        Self::StorageError(value.to_string())
+    }
+}
+
+impl From<std::io::Error> for Error {
+    fn from(value: std::io::Error) -> Self {
+        if value.kind() == std::io::ErrorKind::InvalidInput {
+            Error::InvalidRequest(value.to_string())
+        } else {
+            Error::StorageError(value.to_string())
+        }
+    }
+}
+
+// TODO: this should probably go somewhere else?
+impl From<Error> for std::io::Error {
+    fn from(value: Error) -> Self {
+        match value {
+            Error::InvalidRequest(msg) => Self::new(std::io::ErrorKind::InvalidInput, msg),
+            Error::StorageError(msg) => Self::new(std::io::ErrorKind::Other, msg),
+        }
+    }
+}
diff --git a/tvix/castore/src/fixtures.rs b/tvix/castore/src/fixtures.rs
new file mode 100644
index 0000000000..a206d9b7dd
--- /dev/null
+++ b/tvix/castore/src/fixtures.rs
@@ -0,0 +1,88 @@
+use crate::{
+    proto::{self, Directory, DirectoryNode, FileNode, SymlinkNode},
+    B3Digest,
+};
+use lazy_static::lazy_static;
+
+pub const HELLOWORLD_BLOB_CONTENTS: &[u8] = b"Hello World!";
+pub const EMPTY_BLOB_CONTENTS: &[u8] = b"";
+
+lazy_static! {
+    pub static ref DUMMY_DIGEST: B3Digest = {
+        let u = [0u8; 32];
+        (&u).into()
+    };
+    pub static ref DUMMY_DIGEST_2: B3Digest = {
+        let mut u = [0u8; 32];
+        u[0] = 0x10;
+        (&u).into()
+    };
+    pub static ref DUMMY_DATA_1: bytes::Bytes = vec![0x01, 0x02, 0x03].into();
+    pub static ref DUMMY_DATA_2: bytes::Bytes = vec![0x04, 0x05].into();
+
+    pub static ref HELLOWORLD_BLOB_DIGEST: B3Digest =
+        blake3::hash(HELLOWORLD_BLOB_CONTENTS).as_bytes().into();
+    pub static ref EMPTY_BLOB_DIGEST: B3Digest =
+        blake3::hash(EMPTY_BLOB_CONTENTS).as_bytes().into();
+
+    // 2 bytes
+    pub static ref BLOB_A: bytes::Bytes = vec![0x00, 0x01].into();
+    pub static ref BLOB_A_DIGEST: B3Digest = blake3::hash(&BLOB_A).as_bytes().into();
+
+    // 1MB
+    pub static ref BLOB_B: bytes::Bytes = (0..255).collect::<Vec<u8>>().repeat(4 * 1024).into();
+    pub static ref BLOB_B_DIGEST: B3Digest = blake3::hash(&BLOB_B).as_bytes().into();
+
+    // Directories
+    pub static ref DIRECTORY_WITH_KEEP: proto::Directory = proto::Directory {
+        directories: vec![],
+        files: vec![FileNode {
+            name: b".keep".to_vec().into(),
+            digest: EMPTY_BLOB_DIGEST.clone().into(),
+            size: 0,
+            executable: false,
+        }],
+        symlinks: vec![],
+    };
+    pub static ref DIRECTORY_COMPLICATED: proto::Directory = proto::Directory {
+        directories: vec![DirectoryNode {
+            name: b"keep".to_vec().into(),
+            digest: DIRECTORY_WITH_KEEP.digest().into(),
+            size: DIRECTORY_WITH_KEEP.size(),
+        }],
+        files: vec![FileNode {
+            name: b".keep".to_vec().into(),
+            digest: EMPTY_BLOB_DIGEST.clone().into(),
+            size: 0,
+            executable: false,
+        }],
+        symlinks: vec![SymlinkNode {
+            name: b"aa".to_vec().into(),
+            target: b"/nix/store/somewhereelse".to_vec().into(),
+        }],
+    };
+    pub static ref DIRECTORY_A: Directory = Directory::default();
+    pub static ref DIRECTORY_B: Directory = Directory {
+        directories: vec![DirectoryNode {
+            name: b"a".to_vec().into(),
+            digest: DIRECTORY_A.digest().into(),
+            size: DIRECTORY_A.size(),
+        }],
+        ..Default::default()
+    };
+    pub static ref DIRECTORY_C: Directory = Directory {
+        directories: vec![
+            DirectoryNode {
+                name: b"a".to_vec().into(),
+                digest: DIRECTORY_A.digest().into(),
+                size: DIRECTORY_A.size(),
+            },
+            DirectoryNode {
+                name: b"a'".to_vec().into(),
+                digest: DIRECTORY_A.digest().into(),
+                size: DIRECTORY_A.size(),
+            }
+        ],
+        ..Default::default()
+    };
+}
diff --git a/tvix/castore/src/fs/file_attr.rs b/tvix/castore/src/fs/file_attr.rs
new file mode 100644
index 0000000000..2e0e70e3cd
--- /dev/null
+++ b/tvix/castore/src/fs/file_attr.rs
@@ -0,0 +1,29 @@
+#![allow(clippy::unnecessary_cast)] // libc::S_IFDIR is u32 on Linux and u16 on MacOS
+
+use fuse_backend_rs::abi::fuse_abi::Attr;
+
+/// The [Attr] describing the root
+pub const ROOT_FILE_ATTR: Attr = Attr {
+    ino: fuse_backend_rs::api::filesystem::ROOT_ID,
+    size: 0,
+    blksize: 1024,
+    blocks: 0,
+    mode: libc::S_IFDIR as u32 | 0o555,
+    atime: 0,
+    mtime: 0,
+    ctime: 0,
+    atimensec: 0,
+    mtimensec: 0,
+    ctimensec: 0,
+    nlink: 0,
+    uid: 0,
+    gid: 0,
+    rdev: 0,
+    flags: 0,
+    #[cfg(target_os = "macos")]
+    crtime: 0,
+    #[cfg(target_os = "macos")]
+    crtimensec: 0,
+    #[cfg(target_os = "macos")]
+    padding: 0,
+};
diff --git a/tvix/castore/src/fs/fuse.rs b/tvix/castore/src/fs/fuse.rs
new file mode 100644
index 0000000000..cd50618ff5
--- /dev/null
+++ b/tvix/castore/src/fs/fuse.rs
@@ -0,0 +1,120 @@
+use std::{io, path::Path, sync::Arc, thread};
+
+use fuse_backend_rs::{api::filesystem::FileSystem, transport::FuseSession};
+use tracing::{error, instrument};
+
+struct FuseServer<FS>
+where
+    FS: FileSystem + Sync + Send,
+{
+    server: Arc<fuse_backend_rs::api::server::Server<Arc<FS>>>,
+    channel: fuse_backend_rs::transport::FuseChannel,
+}
+
+#[cfg(target_os = "macos")]
+const BADFD: libc::c_int = libc::EBADF;
+#[cfg(target_os = "linux")]
+const BADFD: libc::c_int = libc::EBADFD;
+
+impl<FS> FuseServer<FS>
+where
+    FS: FileSystem + Sync + Send,
+{
+    fn start(&mut self) -> io::Result<()> {
+        while let Some((reader, writer)) = self
+            .channel
+            .get_request()
+            .map_err(|_| io::Error::from_raw_os_error(libc::EINVAL))?
+        {
+            if let Err(e) = self
+                .server
+                .handle_message(reader, writer.into(), None, None)
+            {
+                match e {
+                    // This indicates the session has been shut down.
+                    fuse_backend_rs::Error::EncodeMessage(e) if e.raw_os_error() == Some(BADFD) => {
+                        break;
+                    }
+                    error => {
+                        error!(?error, "failed to handle fuse request");
+                        continue;
+                    }
+                }
+            }
+        }
+        Ok(())
+    }
+}
+
+pub struct FuseDaemon {
+    session: FuseSession,
+    threads: Vec<thread::JoinHandle<()>>,
+}
+
+impl FuseDaemon {
+    #[instrument(skip(fs, mountpoint), fields(mountpoint=?mountpoint), err)]
+    pub fn new<FS, P>(
+        fs: FS,
+        mountpoint: P,
+        threads: usize,
+        allow_other: bool,
+    ) -> Result<Self, io::Error>
+    where
+        FS: FileSystem + Sync + Send + 'static,
+        P: AsRef<Path> + std::fmt::Debug,
+    {
+        let server = Arc::new(fuse_backend_rs::api::server::Server::new(Arc::new(fs)));
+
+        let mut session = FuseSession::new(mountpoint.as_ref(), "tvix-store", "", true)
+            .map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?;
+
+        #[cfg(target_os = "linux")]
+        session.set_allow_other(allow_other);
+        session
+            .mount()
+            .map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?;
+        let mut join_handles = Vec::with_capacity(threads);
+        for _ in 0..threads {
+            let mut server = FuseServer {
+                server: server.clone(),
+                channel: session
+                    .new_channel()
+                    .map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?,
+            };
+            let join_handle = thread::Builder::new()
+                .name("fuse_server".to_string())
+                .spawn(move || {
+                    let _ = server.start();
+                })?;
+            join_handles.push(join_handle);
+        }
+
+        Ok(FuseDaemon {
+            session,
+            threads: join_handles,
+        })
+    }
+
+    #[instrument(skip_all, err)]
+    pub fn unmount(&mut self) -> Result<(), io::Error> {
+        self.session
+            .umount()
+            .map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?;
+
+        for thread in self.threads.drain(..) {
+            thread.join().map_err(|_| {
+                io::Error::new(io::ErrorKind::Other, "failed to join fuse server thread")
+            })?;
+        }
+
+        Ok(())
+    }
+}
+
+impl Drop for FuseDaemon {
+    fn drop(&mut self) {
+        if let Err(error) = self.unmount() {
+            error!(?error, "failed to unmont fuse filesystem")
+        }
+    }
+}
diff --git a/tvix/castore/src/fs/inode_tracker.rs b/tvix/castore/src/fs/inode_tracker.rs
new file mode 100644
index 0000000000..4a8283b6b1
--- /dev/null
+++ b/tvix/castore/src/fs/inode_tracker.rs
@@ -0,0 +1,207 @@
+use std::{collections::HashMap, sync::Arc};
+
+use super::inodes::{DirectoryInodeData, InodeData};
+use crate::B3Digest;
+
+/// InodeTracker keeps track of inodes, stores data being these inodes and deals
+/// with inode allocation.
+pub struct InodeTracker {
+    data: HashMap<u64, Arc<InodeData>>,
+
+    // lookup table for blobs by their B3Digest
+    blob_digest_to_inode: HashMap<B3Digest, u64>,
+
+    // lookup table for symlinks by their target
+    symlink_target_to_inode: HashMap<bytes::Bytes, u64>,
+
+    // lookup table for directories by their B3Digest.
+    // Note the corresponding directory may not be present in data yet.
+    directory_digest_to_inode: HashMap<B3Digest, u64>,
+
+    // the next inode to allocate
+    next_inode: u64,
+}
+
+impl Default for InodeTracker {
+    fn default() -> Self {
+        Self {
+            data: Default::default(),
+
+            blob_digest_to_inode: Default::default(),
+            symlink_target_to_inode: Default::default(),
+            directory_digest_to_inode: Default::default(),
+
+            next_inode: 2,
+        }
+    }
+}
+
+impl InodeTracker {
+    // Retrieves data for a given inode, if it exists.
+    pub fn get(&self, ino: u64) -> Option<Arc<InodeData>> {
+        self.data.get(&ino).cloned()
+    }
+
+    // Replaces data for a given inode.
+    // Panics if the inode doesn't already exist.
+    pub fn replace(&mut self, ino: u64, data: Arc<InodeData>) {
+        if self.data.insert(ino, data).is_none() {
+            panic!("replace called on unknown inode");
+        }
+    }
+
+    // Stores data and returns the inode for it.
+    // In case an inode has already been allocated for the same data, that inode
+    // is returned, otherwise a new one is allocated.
+    // In case data is a [InodeData::Directory], inodes for all items are looked
+    // up
+    pub fn put(&mut self, data: InodeData) -> u64 {
+        match data {
+            InodeData::Regular(ref digest, _, _) => {
+                match self.blob_digest_to_inode.get(digest) {
+                    Some(found_ino) => {
+                        // We already have it, return the inode.
+                        *found_ino
+                    }
+                    None => self.insert_and_increment(data),
+                }
+            }
+            InodeData::Symlink(ref target) => {
+                match self.symlink_target_to_inode.get(target) {
+                    Some(found_ino) => {
+                        // We already have it, return the inode.
+                        *found_ino
+                    }
+                    None => self.insert_and_increment(data),
+                }
+            }
+            InodeData::Directory(DirectoryInodeData::Sparse(ref digest, _size)) => {
+                // check the lookup table if the B3Digest is known.
+                match self.directory_digest_to_inode.get(digest) {
+                    Some(found_ino) => {
+                        // We already have it, return the inode.
+                        *found_ino
+                    }
+                    None => {
+                        // insert and return the inode
+                        self.insert_and_increment(data)
+                    }
+                }
+            }
+            // Inserting [DirectoryInodeData::Populated] doesn't normally happen,
+            // only via [replace].
+            InodeData::Directory(DirectoryInodeData::Populated(..)) => {
+                unreachable!("should never be called with DirectoryInodeData::Populated")
+            }
+        }
+    }
+
+    // Inserts the data and returns the inode it was stored at, while
+    // incrementing next_inode.
+    fn insert_and_increment(&mut self, data: InodeData) -> u64 {
+        let ino = self.next_inode;
+        // insert into lookup tables
+        match data {
+            InodeData::Regular(ref digest, _, _) => {
+                self.blob_digest_to_inode.insert(digest.clone(), ino);
+            }
+            InodeData::Symlink(ref target) => {
+                self.symlink_target_to_inode.insert(target.clone(), ino);
+            }
+            InodeData::Directory(DirectoryInodeData::Sparse(ref digest, _size)) => {
+                self.directory_digest_to_inode.insert(digest.clone(), ino);
+            }
+            // This is currently not used outside test fixtures.
+            // Usually a [DirectoryInodeData::Sparse] is inserted and later
+            // "upgraded" with more data.
+            // However, as a future optimization, a lookup for a PathInfo could trigger a
+            // [DirectoryService::get_recursive()] request that "forks into
+            // background" and prepopulates all Directories in a closure.
+            InodeData::Directory(DirectoryInodeData::Populated(ref digest, _)) => {
+                self.directory_digest_to_inode.insert(digest.clone(), ino);
+            }
+        }
+        // Insert data
+        self.data.insert(ino, Arc::new(data));
+
+        // increment inode counter and return old inode.
+        self.next_inode += 1;
+        ino
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::fixtures;
+
+    use super::InodeData;
+    use super::InodeTracker;
+
+    /// Getting something non-existent should be none
+    #[test]
+    fn get_nonexistent() {
+        let inode_tracker = InodeTracker::default();
+        assert!(inode_tracker.get(1).is_none());
+    }
+
+    /// Put of a regular file should allocate a uid, which should be the same when inserting again.
+    #[test]
+    fn put_regular() {
+        let mut inode_tracker = InodeTracker::default();
+        let f = InodeData::Regular(
+            fixtures::BLOB_A_DIGEST.clone(),
+            fixtures::BLOB_A.len() as u64,
+            false,
+        );
+
+        // put it in
+        let ino = inode_tracker.put(f.clone());
+
+        // a get should return the right data
+        let data = inode_tracker.get(ino).expect("must be some");
+        match *data {
+            InodeData::Regular(ref digest, _, _) => {
+                assert_eq!(&fixtures::BLOB_A_DIGEST.clone(), digest);
+            }
+            InodeData::Symlink(_) | InodeData::Directory(..) => panic!("wrong type"),
+        }
+
+        // another put should return the same ino
+        assert_eq!(ino, inode_tracker.put(f));
+
+        // inserting another file should return a different ino
+        assert_ne!(
+            ino,
+            inode_tracker.put(InodeData::Regular(
+                fixtures::BLOB_B_DIGEST.clone(),
+                fixtures::BLOB_B.len() as u64,
+                false,
+            ))
+        );
+    }
+
+    // Put of a symlink should allocate a uid, which should be the same when inserting again
+    #[test]
+    fn put_symlink() {
+        let mut inode_tracker = InodeTracker::default();
+        let f = InodeData::Symlink("target".into());
+
+        // put it in
+        let ino = inode_tracker.put(f.clone());
+
+        // a get should return the right data
+        let data = inode_tracker.get(ino).expect("must be some");
+        match *data {
+            InodeData::Symlink(ref target) => {
+                assert_eq!(b"target".to_vec(), *target);
+            }
+            InodeData::Regular(..) | InodeData::Directory(..) => panic!("wrong type"),
+        }
+
+        // another put should return the same ino
+        assert_eq!(ino, inode_tracker.put(f));
+
+        // inserting another file should return a different ino
+        assert_ne!(ino, inode_tracker.put(InodeData::Symlink("target2".into())));
+    }
+}
diff --git a/tvix/castore/src/fs/inodes.rs b/tvix/castore/src/fs/inodes.rs
new file mode 100644
index 0000000000..bdd4595434
--- /dev/null
+++ b/tvix/castore/src/fs/inodes.rs
@@ -0,0 +1,96 @@
+//! This module contains all the data structures used to track information
+//! about inodes, which present tvix-castore nodes in a filesystem.
+use std::time::Duration;
+
+use bytes::Bytes;
+
+use crate::proto as castorepb;
+use crate::B3Digest;
+
+#[derive(Clone, Debug)]
+pub enum InodeData {
+    Regular(B3Digest, u64, bool),  // digest, size, executable
+    Symlink(bytes::Bytes),         // target
+    Directory(DirectoryInodeData), // either [DirectoryInodeData:Sparse] or [DirectoryInodeData:Populated]
+}
+
+/// This encodes the two different states of [InodeData::Directory].
+/// Either the data still is sparse (we only saw a [castorepb::DirectoryNode],
+/// but didn't fetch the [castorepb::Directory] struct yet, or we processed a
+/// lookup and did fetch the data.
+#[derive(Clone, Debug)]
+pub enum DirectoryInodeData {
+    Sparse(B3Digest, u64),                                  // digest, size
+    Populated(B3Digest, Vec<(u64, castorepb::node::Node)>), // [(child_inode, node)]
+}
+
+impl InodeData {
+    /// Constructs a new InodeData by consuming a [Node].
+    /// It splits off the orginal name, so it can be used later.
+    pub fn from_node(node: castorepb::node::Node) -> (Self, Bytes) {
+        match node {
+            castorepb::node::Node::Directory(n) => (
+                Self::Directory(DirectoryInodeData::Sparse(
+                    n.digest.try_into().unwrap(),
+                    n.size,
+                )),
+                n.name,
+            ),
+            castorepb::node::Node::File(n) => (
+                Self::Regular(n.digest.try_into().unwrap(), n.size, n.executable),
+                n.name,
+            ),
+            castorepb::node::Node::Symlink(n) => (Self::Symlink(n.target), n.name),
+        }
+    }
+
+    pub fn as_fuse_file_attr(&self, inode: u64) -> fuse_backend_rs::abi::fuse_abi::Attr {
+        fuse_backend_rs::abi::fuse_abi::Attr {
+            ino: inode,
+            // FUTUREWORK: play with this numbers, as it affects read sizes for client applications.
+            blocks: 1024,
+            size: match self {
+                InodeData::Regular(_, size, _) => *size,
+                InodeData::Symlink(target) => target.len() as u64,
+                InodeData::Directory(DirectoryInodeData::Sparse(_, size)) => *size,
+                InodeData::Directory(DirectoryInodeData::Populated(_, ref children)) => {
+                    children.len() as u64
+                }
+            },
+            mode: self.as_fuse_type() | self.mode(),
+            ..Default::default()
+        }
+    }
+
+    fn mode(&self) -> u32 {
+        match self {
+            InodeData::Regular(_, _, false) | InodeData::Symlink(_) => 0o444,
+            InodeData::Regular(_, _, true) | InodeData::Directory(_) => 0o555,
+        }
+    }
+
+    pub fn as_fuse_entry(&self, inode: u64) -> fuse_backend_rs::api::filesystem::Entry {
+        fuse_backend_rs::api::filesystem::Entry {
+            inode,
+            attr: self.as_fuse_file_attr(inode).into(),
+            attr_timeout: Duration::MAX,
+            entry_timeout: Duration::MAX,
+            ..Default::default()
+        }
+    }
+
+    /// Returns the u32 fuse type
+    pub fn as_fuse_type(&self) -> u32 {
+        #[allow(clippy::let_and_return)]
+        let ty = match self {
+            InodeData::Regular(_, _, _) => libc::S_IFREG,
+            InodeData::Symlink(_) => libc::S_IFLNK,
+            InodeData::Directory(_) => libc::S_IFDIR,
+        };
+        // libc::S_IFDIR is u32 on Linux and u16 on MacOS
+        #[cfg(target_os = "macos")]
+        let ty = ty as u32;
+
+        ty
+    }
+}
diff --git a/tvix/castore/src/fs/mod.rs b/tvix/castore/src/fs/mod.rs
new file mode 100644
index 0000000000..826523131f
--- /dev/null
+++ b/tvix/castore/src/fs/mod.rs
@@ -0,0 +1,877 @@
+mod file_attr;
+mod inode_tracker;
+mod inodes;
+mod root_nodes;
+
+#[cfg(feature = "fuse")]
+pub mod fuse;
+
+#[cfg(feature = "virtiofs")]
+pub mod virtiofs;
+
+#[cfg(test)]
+mod tests;
+
+pub use self::root_nodes::RootNodes;
+use self::{
+    file_attr::ROOT_FILE_ATTR,
+    inode_tracker::InodeTracker,
+    inodes::{DirectoryInodeData, InodeData},
+};
+use crate::proto as castorepb;
+use crate::{
+    blobservice::{BlobReader, BlobService},
+    directoryservice::DirectoryService,
+    proto::{node::Node, NamedNode},
+    B3Digest,
+};
+use bstr::ByteVec;
+use bytes::Bytes;
+use fuse_backend_rs::abi::fuse_abi::{stat64, OpenOptions};
+use fuse_backend_rs::api::filesystem::{
+    Context, FileSystem, FsOptions, GetxattrReply, ListxattrReply, ROOT_ID,
+};
+use futures::StreamExt;
+use parking_lot::RwLock;
+use std::sync::Mutex;
+use std::{
+    collections::HashMap,
+    io,
+    sync::atomic::AtomicU64,
+    sync::{atomic::Ordering, Arc},
+    time::Duration,
+};
+use std::{ffi::CStr, io::Cursor};
+use tokio::{
+    io::{AsyncReadExt, AsyncSeekExt},
+    sync::mpsc,
+};
+use tracing::{debug, error, instrument, warn, Span};
+
+/// This implements a read-only FUSE filesystem for a tvix-store
+/// with the passed [BlobService], [DirectoryService] and [RootNodes].
+///
+/// Linux uses inodes in filesystems. When implementing FUSE, most calls are
+/// *for* a given inode.
+///
+/// This means, we need to have a stable mapping of inode numbers to the
+/// corresponding store nodes.
+///
+/// We internally delegate all inode allocation and state keeping to the
+/// inode tracker.
+/// We store a mapping from currently "explored" names in the root to their
+/// inode.
+///
+/// There's some places where inodes are allocated / data inserted into
+/// the inode tracker, if not allocated before already:
+///  - Processing a `lookup` request, either in the mount root, or somewhere
+///    deeper.
+///  - Processing a `readdir` request
+///
+///  Things pointing to the same contents get the same inodes, irrespective of
+///  their own location.
+///  This means:
+///  - Symlinks with the same target will get the same inode.
+///  - Regular/executable files with the same contents will get the same inode
+///  - Directories with the same contents will get the same inode.
+///
+/// Due to the above being valid across the whole store, and considering the
+/// merkle structure is a DAG, not a tree, this also means we can't do "bucketed
+/// allocation", aka reserve Directory.size inodes for each directory node we
+/// explore.
+/// Tests for this live in the tvix-store crate.
+pub struct TvixStoreFs<BS, DS, RN> {
+    blob_service: BS,
+    directory_service: DS,
+    root_nodes_provider: RN,
+
+    /// Whether to (try) listing elements in the root.
+    list_root: bool,
+
+    /// Whether to expose blob and directory digests as extended attributes.
+    show_xattr: bool,
+
+    /// This maps a given basename in the root to the inode we allocated for the node.
+    root_nodes: RwLock<HashMap<Bytes, u64>>,
+
+    /// This keeps track of inodes and data alongside them.
+    inode_tracker: RwLock<InodeTracker>,
+
+    // FUTUREWORK: have a generic container type for dir/file handles and handle
+    // allocation.
+    /// Maps from the handle returned from an opendir to
+    /// This holds all opendir handles (for the root inode)
+    /// They point to the rx part of the channel producing the listing.
+    #[allow(clippy::type_complexity)]
+    dir_handles: RwLock<
+        HashMap<
+            u64,
+            (
+                Span,
+                Arc<Mutex<mpsc::Receiver<(usize, Result<Node, crate::Error>)>>>,
+            ),
+        >,
+    >,
+
+    next_dir_handle: AtomicU64,
+
+    /// This holds all open file handles
+    #[allow(clippy::type_complexity)]
+    file_handles: RwLock<HashMap<u64, (Span, Arc<Mutex<Box<dyn BlobReader>>>)>>,
+
+    next_file_handle: AtomicU64,
+
+    tokio_handle: tokio::runtime::Handle,
+}
+
+impl<BS, DS, RN> TvixStoreFs<BS, DS, RN>
+where
+    BS: AsRef<dyn BlobService> + Clone + Send,
+    DS: AsRef<dyn DirectoryService> + Clone + Send + 'static,
+    RN: RootNodes + Clone + 'static,
+{
+    pub fn new(
+        blob_service: BS,
+        directory_service: DS,
+        root_nodes_provider: RN,
+        list_root: bool,
+        show_xattr: bool,
+    ) -> Self {
+        Self {
+            blob_service,
+            directory_service,
+            root_nodes_provider,
+
+            list_root,
+            show_xattr,
+
+            root_nodes: RwLock::new(HashMap::default()),
+            inode_tracker: RwLock::new(Default::default()),
+
+            dir_handles: RwLock::new(Default::default()),
+            next_dir_handle: AtomicU64::new(1),
+
+            file_handles: RwLock::new(Default::default()),
+            next_file_handle: AtomicU64::new(1),
+            tokio_handle: tokio::runtime::Handle::current(),
+        }
+    }
+
+    /// Retrieves the inode for a given root node basename, if present.
+    /// This obtains a read lock on self.root_nodes.
+    fn get_inode_for_root_name(&self, name: &[u8]) -> Option<u64> {
+        self.root_nodes.read().get(name).cloned()
+    }
+
+    /// For a given inode, look up the given directory behind it (from
+    /// self.inode_tracker), and return its children.
+    /// The inode_tracker MUST know about this inode already, and it MUST point
+    /// to a [InodeData::Directory].
+    /// It is ok if it's a [DirectoryInodeData::Sparse] - in that case, a lookup
+    /// in self.directory_service is performed, and self.inode_tracker is updated with the
+    /// [DirectoryInodeData::Populated].
+    #[instrument(skip(self), err)]
+    fn get_directory_children(&self, ino: u64) -> io::Result<(B3Digest, Vec<(u64, Node)>)> {
+        let data = self.inode_tracker.read().get(ino).unwrap();
+        match *data {
+            // if it's populated already, return children.
+            InodeData::Directory(DirectoryInodeData::Populated(
+                ref parent_digest,
+                ref children,
+            )) => Ok((parent_digest.clone(), children.clone())),
+            // if it's sparse, fetch data using directory_service, populate child nodes
+            // and update it in [self.inode_tracker].
+            InodeData::Directory(DirectoryInodeData::Sparse(ref parent_digest, _)) => {
+                let directory = self
+                    .tokio_handle
+                    .block_on({
+                        let directory_service = self.directory_service.clone();
+                        let parent_digest = parent_digest.to_owned();
+                        async move { directory_service.as_ref().get(&parent_digest).await }
+                    })?
+                    .ok_or_else(|| {
+                        warn!(directory.digest=%parent_digest, "directory not found");
+                        // If the Directory can't be found, this is a hole, bail out.
+                        io::Error::from_raw_os_error(libc::EIO)
+                    })?;
+
+                // Turn the retrieved directory into a InodeData::Directory(DirectoryInodeData::Populated(..)),
+                // allocating inodes for the children on the way.
+                // FUTUREWORK: there's a bunch of cloning going on here, which we can probably avoid.
+                let children = {
+                    let mut inode_tracker = self.inode_tracker.write();
+
+                    let children: Vec<(u64, castorepb::node::Node)> = directory
+                        .nodes()
+                        .map(|child_node| {
+                            let (inode_data, _) = InodeData::from_node(child_node.clone());
+
+                            let child_ino = inode_tracker.put(inode_data);
+                            (child_ino, child_node)
+                        })
+                        .collect();
+
+                    // replace.
+                    inode_tracker.replace(
+                        ino,
+                        Arc::new(InodeData::Directory(DirectoryInodeData::Populated(
+                            parent_digest.clone(),
+                            children.clone(),
+                        ))),
+                    );
+
+                    children
+                };
+
+                Ok((parent_digest.clone(), children))
+            }
+            // if the parent inode was not a directory, this doesn't make sense
+            InodeData::Regular(..) | InodeData::Symlink(_) => {
+                Err(io::Error::from_raw_os_error(libc::ENOTDIR))
+            }
+        }
+    }
+
+    /// This will turn a lookup request for a name in the root to a ino and
+    /// [InodeData].
+    /// It will peek in [self.root_nodes], and then either look it up from
+    /// [self.inode_tracker],
+    /// or otherwise fetch from [self.root_nodes], and then insert into
+    /// [self.inode_tracker].
+    /// In the case the name can't be found, a libc::ENOENT is returned.
+    fn name_in_root_to_ino_and_data(
+        &self,
+        name: &std::ffi::CStr,
+    ) -> io::Result<(u64, Arc<InodeData>)> {
+        // Look up the inode for that root node.
+        // If there's one, [self.inode_tracker] MUST also contain the data,
+        // which we can then return.
+        if let Some(inode) = self.get_inode_for_root_name(name.to_bytes()) {
+            return Ok((
+                inode,
+                self.inode_tracker
+                    .read()
+                    .get(inode)
+                    .expect("must exist")
+                    .to_owned(),
+            ));
+        }
+
+        // We don't have it yet, look it up in [self.root_nodes].
+        match self.tokio_handle.block_on({
+            let root_nodes_provider = self.root_nodes_provider.clone();
+            async move { root_nodes_provider.get_by_basename(name.to_bytes()).await }
+        }) {
+            // if there was an error looking up the root node, propagate up an IO error.
+            Err(_e) => Err(io::Error::from_raw_os_error(libc::EIO)),
+            // the root node doesn't exist, so the file doesn't exist.
+            Ok(None) => Err(io::Error::from_raw_os_error(libc::ENOENT)),
+            // The root node does exist
+            Ok(Some(root_node)) => {
+                // The name must match what's passed in the lookup, otherwise this is also a ENOENT.
+                if root_node.get_name() != name.to_bytes() {
+                    debug!(root_node.name=?root_node.get_name(), found_node.name=%name.to_string_lossy(), "node name mismatch");
+                    return Err(io::Error::from_raw_os_error(libc::ENOENT));
+                }
+
+                // Let's check if someone else beat us to updating the inode tracker and
+                // root_nodes map. This avoids locking inode_tracker for writing.
+                if let Some(ino) = self.root_nodes.read().get(name.to_bytes()) {
+                    return Ok((
+                        *ino,
+                        self.inode_tracker.read().get(*ino).expect("must exist"),
+                    ));
+                }
+
+                // Only in case it doesn't, lock [self.root_nodes] and
+                // [self.inode_tracker] for writing.
+                let mut root_nodes = self.root_nodes.write();
+                let mut inode_tracker = self.inode_tracker.write();
+
+                // insert the (sparse) inode data and register in
+                // self.root_nodes.
+                let (inode_data, name) = InodeData::from_node(root_node);
+                let ino = inode_tracker.put(inode_data.clone());
+                root_nodes.insert(name, ino);
+
+                Ok((ino, Arc::new(inode_data)))
+            }
+        }
+    }
+}
+
+/// Buffer size of the channel providing nodes in the mount root
+const ROOT_NODES_BUFFER_SIZE: usize = 16;
+
+const XATTR_NAME_DIRECTORY_DIGEST: &[u8] = b"user.tvix.castore.directory.digest";
+const XATTR_NAME_BLOB_DIGEST: &[u8] = b"user.tvix.castore.blob.digest";
+
+impl<BS, DS, RN> FileSystem for TvixStoreFs<BS, DS, RN>
+where
+    BS: AsRef<dyn BlobService> + Clone + Send + 'static,
+    DS: AsRef<dyn DirectoryService> + Send + Clone + 'static,
+    RN: RootNodes + Clone + 'static,
+{
+    type Handle = u64;
+    type Inode = u64;
+
+    fn init(&self, _capable: FsOptions) -> io::Result<FsOptions> {
+        Ok(FsOptions::empty())
+    }
+
+    #[tracing::instrument(skip_all, fields(rq.inode = inode))]
+    fn getattr(
+        &self,
+        _ctx: &Context,
+        inode: Self::Inode,
+        _handle: Option<Self::Handle>,
+    ) -> io::Result<(stat64, Duration)> {
+        if inode == ROOT_ID {
+            return Ok((ROOT_FILE_ATTR.into(), Duration::MAX));
+        }
+
+        match self.inode_tracker.read().get(inode) {
+            None => Err(io::Error::from_raw_os_error(libc::ENOENT)),
+            Some(inode_data) => {
+                debug!(inode_data = ?inode_data, "found node");
+                Ok((inode_data.as_fuse_file_attr(inode).into(), Duration::MAX))
+            }
+        }
+    }
+
+    #[tracing::instrument(skip_all, fields(rq.parent_inode = parent, rq.name = ?name))]
+    fn lookup(
+        &self,
+        _ctx: &Context,
+        parent: Self::Inode,
+        name: &std::ffi::CStr,
+    ) -> io::Result<fuse_backend_rs::api::filesystem::Entry> {
+        debug!("lookup");
+
+        // This goes from a parent inode to a node.
+        // - If the parent is [ROOT_ID], we need to check
+        //   [self.root_nodes] (fetching from a [RootNode] provider if needed)
+        // - Otherwise, lookup the parent in [self.inode_tracker] (which must be
+        //   a [InodeData::Directory]), and find the child with that name.
+        if parent == ROOT_ID {
+            let (ino, inode_data) = self.name_in_root_to_ino_and_data(name)?;
+
+            debug!(inode_data=?&inode_data, ino=ino, "Some");
+            return Ok(inode_data.as_fuse_entry(ino));
+        }
+        // This is the "lookup for "a" inside inode 42.
+        // We already know that inode 42 must be a directory.
+        let (parent_digest, children) = self.get_directory_children(parent)?;
+
+        Span::current().record("directory.digest", parent_digest.to_string());
+        // Search for that name in the list of children and return the FileAttrs.
+
+        // in the children, find the one with the desired name.
+        if let Some((child_ino, _)) = children.iter().find(|e| e.1.get_name() == name.to_bytes()) {
+            // lookup the child [InodeData] in [self.inode_tracker].
+            // We know the inodes for children have already been allocated.
+            let child_inode_data = self.inode_tracker.read().get(*child_ino).unwrap();
+
+            // Reply with the file attributes for the child.
+            // For child directories, we still have all data we need to reply.
+            Ok(child_inode_data.as_fuse_entry(*child_ino))
+        } else {
+            // Child not found, return ENOENT.
+            Err(io::Error::from_raw_os_error(libc::ENOENT))
+        }
+    }
+
+    #[tracing::instrument(skip_all, fields(rq.inode = inode))]
+    fn opendir(
+        &self,
+        _ctx: &Context,
+        inode: Self::Inode,
+        _flags: u32,
+    ) -> io::Result<(Option<Self::Handle>, OpenOptions)> {
+        // In case opendir on the root is called, we provide the handle, as re-entering that listing is expensive.
+        // For all other directory inodes we just let readdir take care of it.
+        if inode == ROOT_ID {
+            if !self.list_root {
+                return Err(io::Error::from_raw_os_error(libc::EPERM)); // same error code as ipfs/kubo
+            }
+
+            let root_nodes_provider = self.root_nodes_provider.clone();
+            let (tx, rx) = mpsc::channel(ROOT_NODES_BUFFER_SIZE);
+
+            // This task will run in the background immediately and will exit
+            // after the stream ends or if we no longer want any more entries.
+            self.tokio_handle.spawn(async move {
+                let mut stream = root_nodes_provider.list().enumerate();
+                while let Some(node) = stream.next().await {
+                    if tx.send(node).await.is_err() {
+                        // If we get a send error, it means the sync code
+                        // doesn't want any more entries.
+                        break;
+                    }
+                }
+            });
+
+            // Put the rx part into [self.dir_handles].
+            // TODO: this will overflow after 2**64 operations,
+            // which is fine for now.
+            // See https://cl.tvl.fyi/c/depot/+/8834/comment/a6684ce0_d72469d1
+            // for the discussion on alternatives.
+            let dh = self.next_dir_handle.fetch_add(1, Ordering::SeqCst);
+
+            self.dir_handles
+                .write()
+                .insert(dh, (Span::current(), Arc::new(Mutex::new(rx))));
+
+            return Ok((
+                Some(dh),
+                fuse_backend_rs::api::filesystem::OpenOptions::empty(), // TODO: non-seekable
+            ));
+        }
+
+        Ok((None, OpenOptions::empty()))
+    }
+
+    #[tracing::instrument(skip_all, fields(rq.inode = inode, rq.handle = handle, rq.offset = offset), parent = self.dir_handles.read().get(&handle).and_then(|x| x.0.id()))]
+    fn readdir(
+        &self,
+        _ctx: &Context,
+        inode: Self::Inode,
+        handle: Self::Handle,
+        _size: u32,
+        offset: u64,
+        add_entry: &mut dyn FnMut(fuse_backend_rs::api::filesystem::DirEntry) -> io::Result<usize>,
+    ) -> io::Result<()> {
+        debug!("readdir");
+
+        if inode == ROOT_ID {
+            if !self.list_root {
+                return Err(io::Error::from_raw_os_error(libc::EPERM)); // same error code as ipfs/kubo
+            }
+
+            // get the handle from [self.dir_handles]
+            let (_span, rx) = match self.dir_handles.read().get(&handle) {
+                Some(rx) => rx.clone(),
+                None => {
+                    warn!("dir handle {} unknown", handle);
+                    return Err(io::Error::from_raw_os_error(libc::EIO));
+                }
+            };
+
+            let mut rx = rx
+                .lock()
+                .map_err(|_| crate::Error::StorageError("mutex poisoned".into()))?;
+
+            while let Some((i, n)) = rx.blocking_recv() {
+                let root_node = n.map_err(|e| {
+                    warn!("failed to retrieve root node: {}", e);
+                    io::Error::from_raw_os_error(libc::EIO)
+                })?;
+
+                let (inode_data, name) = InodeData::from_node(root_node);
+
+                // obtain the inode, or allocate a new one.
+                let ino = self.get_inode_for_root_name(&name).unwrap_or_else(|| {
+                    // insert the (sparse) inode data and register in
+                    // self.root_nodes.
+                    let ino = self.inode_tracker.write().put(inode_data.clone());
+                    self.root_nodes.write().insert(name.clone(), ino);
+                    ino
+                });
+
+                let written = add_entry(fuse_backend_rs::api::filesystem::DirEntry {
+                    ino,
+                    offset: offset + (i as u64) + 1,
+                    type_: inode_data.as_fuse_type(),
+                    name: &name,
+                })?;
+                // If the buffer is full, add_entry will return `Ok(0)`.
+                if written == 0 {
+                    break;
+                }
+            }
+            return Ok(());
+        }
+
+        // Non root-node case: lookup the children, or return an error if it's not a directory.
+        let (parent_digest, children) = self.get_directory_children(inode)?;
+        Span::current().record("directory.digest", parent_digest.to_string());
+
+        for (i, (ino, child_node)) in children.into_iter().skip(offset as usize).enumerate() {
+            let (inode_data, name) = InodeData::from_node(child_node);
+
+            // the second parameter will become the "offset" parameter on the next call.
+            let written = add_entry(fuse_backend_rs::api::filesystem::DirEntry {
+                ino,
+                offset: offset + (i as u64) + 1,
+                type_: inode_data.as_fuse_type(),
+                name: &name,
+            })?;
+            // If the buffer is full, add_entry will return `Ok(0)`.
+            if written == 0 {
+                break;
+            }
+        }
+
+        Ok(())
+    }
+
+    #[tracing::instrument(skip_all, fields(rq.inode = inode, rq.handle = handle), parent = self.dir_handles.read().get(&handle).and_then(|x| x.0.id()))]
+    fn readdirplus(
+        &self,
+        _ctx: &Context,
+        inode: Self::Inode,
+        handle: Self::Handle,
+        _size: u32,
+        offset: u64,
+        add_entry: &mut dyn FnMut(
+            fuse_backend_rs::api::filesystem::DirEntry,
+            fuse_backend_rs::api::filesystem::Entry,
+        ) -> io::Result<usize>,
+    ) -> io::Result<()> {
+        debug!("readdirplus");
+
+        if inode == ROOT_ID {
+            if !self.list_root {
+                return Err(io::Error::from_raw_os_error(libc::EPERM)); // same error code as ipfs/kubo
+            }
+
+            // get the handle from [self.dir_handles]
+            let (_span, rx) = match self.dir_handles.read().get(&handle) {
+                Some(rx) => rx.clone(),
+                None => {
+                    warn!("dir handle {} unknown", handle);
+                    return Err(io::Error::from_raw_os_error(libc::EIO));
+                }
+            };
+
+            let mut rx = rx
+                .lock()
+                .map_err(|_| crate::Error::StorageError("mutex poisoned".into()))?;
+
+            while let Some((i, n)) = rx.blocking_recv() {
+                let root_node = n.map_err(|e| {
+                    warn!("failed to retrieve root node: {}", e);
+                    io::Error::from_raw_os_error(libc::EPERM)
+                })?;
+
+                let (inode_data, name) = InodeData::from_node(root_node);
+
+                // obtain the inode, or allocate a new one.
+                let ino = self.get_inode_for_root_name(&name).unwrap_or_else(|| {
+                    // insert the (sparse) inode data and register in
+                    // self.root_nodes.
+                    let ino = self.inode_tracker.write().put(inode_data.clone());
+                    self.root_nodes.write().insert(name.clone(), ino);
+                    ino
+                });
+
+                let written = add_entry(
+                    fuse_backend_rs::api::filesystem::DirEntry {
+                        ino,
+                        offset: offset + (i as u64) + 1,
+                        type_: inode_data.as_fuse_type(),
+                        name: &name,
+                    },
+                    inode_data.as_fuse_entry(ino),
+                )?;
+                // If the buffer is full, add_entry will return `Ok(0)`.
+                if written == 0 {
+                    break;
+                }
+            }
+            return Ok(());
+        }
+
+        // Non root-node case: lookup the children, or return an error if it's not a directory.
+        let (parent_digest, children) = self.get_directory_children(inode)?;
+        Span::current().record("directory.digest", parent_digest.to_string());
+
+        for (i, (ino, child_node)) in children.into_iter().skip(offset as usize).enumerate() {
+            let (inode_data, name) = InodeData::from_node(child_node);
+
+            // the second parameter will become the "offset" parameter on the next call.
+            let written = add_entry(
+                fuse_backend_rs::api::filesystem::DirEntry {
+                    ino,
+                    offset: offset + (i as u64) + 1,
+                    type_: inode_data.as_fuse_type(),
+                    name: &name,
+                },
+                inode_data.as_fuse_entry(ino),
+            )?;
+            // If the buffer is full, add_entry will return `Ok(0)`.
+            if written == 0 {
+                break;
+            }
+        }
+
+        Ok(())
+    }
+
+    #[tracing::instrument(skip_all, fields(rq.inode = inode, rq.handle = handle), parent = self.dir_handles.read().get(&handle).and_then(|x| x.0.id()))]
+    fn releasedir(
+        &self,
+        _ctx: &Context,
+        inode: Self::Inode,
+        _flags: u32,
+        handle: Self::Handle,
+    ) -> io::Result<()> {
+        if inode == ROOT_ID {
+            // drop the rx part of the channel.
+            match self.dir_handles.write().remove(&handle) {
+                // drop it, which will close it.
+                Some(rx) => drop(rx),
+                None => {
+                    warn!("dir handle not found");
+                }
+            }
+        }
+
+        Ok(())
+    }
+
+    #[tracing::instrument(skip_all, fields(rq.inode = inode))]
+    fn open(
+        &self,
+        _ctx: &Context,
+        inode: Self::Inode,
+        _flags: u32,
+        _fuse_flags: u32,
+    ) -> io::Result<(
+        Option<Self::Handle>,
+        fuse_backend_rs::api::filesystem::OpenOptions,
+    )> {
+        if inode == ROOT_ID {
+            return Err(io::Error::from_raw_os_error(libc::ENOSYS));
+        }
+
+        // lookup the inode
+        match *self.inode_tracker.read().get(inode).unwrap() {
+            // read is invalid on non-files.
+            InodeData::Directory(..) | InodeData::Symlink(_) => {
+                warn!("is directory");
+                Err(io::Error::from_raw_os_error(libc::EISDIR))
+            }
+            InodeData::Regular(ref blob_digest, _blob_size, _) => {
+                Span::current().record("blob.digest", blob_digest.to_string());
+
+                match self.tokio_handle.block_on({
+                    let blob_service = self.blob_service.clone();
+                    let blob_digest = blob_digest.clone();
+                    async move { blob_service.as_ref().open_read(&blob_digest).await }
+                }) {
+                    Ok(None) => {
+                        warn!("blob not found");
+                        Err(io::Error::from_raw_os_error(libc::EIO))
+                    }
+                    Err(e) => {
+                        warn!(e=?e, "error opening blob");
+                        Err(io::Error::from_raw_os_error(libc::EIO))
+                    }
+                    Ok(Some(blob_reader)) => {
+                        // get a new file handle
+                        // TODO: this will overflow after 2**64 operations,
+                        // which is fine for now.
+                        // See https://cl.tvl.fyi/c/depot/+/8834/comment/a6684ce0_d72469d1
+                        // for the discussion on alternatives.
+                        let fh = self.next_file_handle.fetch_add(1, Ordering::SeqCst);
+
+                        self.file_handles
+                            .write()
+                            .insert(fh, (Span::current(), Arc::new(Mutex::new(blob_reader))));
+
+                        Ok((
+                            Some(fh),
+                            fuse_backend_rs::api::filesystem::OpenOptions::empty(),
+                        ))
+                    }
+                }
+            }
+        }
+    }
+
+    #[tracing::instrument(skip_all, fields(rq.inode = inode, rq.handle = handle), parent = self.file_handles.read().get(&handle).and_then(|x| x.0.id()))]
+    fn release(
+        &self,
+        _ctx: &Context,
+        inode: Self::Inode,
+        _flags: u32,
+        handle: Self::Handle,
+        _flush: bool,
+        _flock_release: bool,
+        _lock_owner: Option<u64>,
+    ) -> io::Result<()> {
+        match self.file_handles.write().remove(&handle) {
+            // drop the blob reader, which will close it.
+            Some(blob_reader) => drop(blob_reader),
+            None => {
+                // These might already be dropped if a read error occured.
+                warn!("file handle not found");
+            }
+        }
+
+        Ok(())
+    }
+
+    #[tracing::instrument(skip_all, fields(rq.inode = inode, rq.handle = handle, rq.offset = offset, rq.size = size), parent = self.file_handles.read().get(&handle).and_then(|x| x.0.id()))]
+    fn read(
+        &self,
+        _ctx: &Context,
+        inode: Self::Inode,
+        handle: Self::Handle,
+        w: &mut dyn fuse_backend_rs::api::filesystem::ZeroCopyWriter,
+        size: u32,
+        offset: u64,
+        _lock_owner: Option<u64>,
+        _flags: u32,
+    ) -> io::Result<usize> {
+        debug!("read");
+
+        // We need to take out the blob reader from self.file_handles, so we can
+        // interact with it in the separate task.
+        // On success, we pass it back out of the task, so we can put it back in self.file_handles.
+        let (_span, blob_reader) = self
+            .file_handles
+            .read()
+            .get(&handle)
+            .ok_or_else(|| {
+                warn!("file handle {} unknown", handle);
+                io::Error::from_raw_os_error(libc::EIO)
+            })
+            .cloned()?;
+
+        let mut blob_reader = blob_reader
+            .lock()
+            .map_err(|_| crate::Error::StorageError("mutex poisoned".into()))?;
+
+        let buf = self.tokio_handle.block_on(async move {
+            // seek to the offset specified, which is relative to the start of the file.
+            let pos = blob_reader
+                .seek(io::SeekFrom::Start(offset))
+                .await
+                .map_err(|e| {
+                    warn!("failed to seek to offset {}: {}", offset, e);
+                    io::Error::from_raw_os_error(libc::EIO)
+                })?;
+
+            debug_assert_eq!(offset, pos);
+
+            // As written in the fuse docs, read should send exactly the number
+            // of bytes requested except on EOF or error.
+
+            let mut buf: Vec<u8> = Vec::with_capacity(size as usize);
+
+            // copy things from the internal buffer into buf to fill it till up until size
+            tokio::io::copy(&mut blob_reader.as_mut().take(size as u64), &mut buf).await?;
+
+            Ok::<_, std::io::Error>(buf)
+        })?;
+
+        // We cannot use w.write() here, we're required to call write multiple
+        // times until we wrote the entirety of the buffer (which is `size`, except on EOF).
+        let buf_len = buf.len();
+        let bytes_written = io::copy(&mut Cursor::new(buf), w)?;
+        if bytes_written != buf_len as u64 {
+            error!(bytes_written=%bytes_written, "unable to write all of buf to kernel");
+            return Err(io::Error::from_raw_os_error(libc::EIO));
+        }
+
+        Ok(bytes_written as usize)
+    }
+
+    #[tracing::instrument(skip_all, fields(rq.inode = inode))]
+    fn readlink(&self, _ctx: &Context, inode: Self::Inode) -> io::Result<Vec<u8>> {
+        if inode == ROOT_ID {
+            return Err(io::Error::from_raw_os_error(libc::ENOSYS));
+        }
+
+        // lookup the inode
+        match *self.inode_tracker.read().get(inode).unwrap() {
+            InodeData::Directory(..) | InodeData::Regular(..) => {
+                Err(io::Error::from_raw_os_error(libc::EINVAL))
+            }
+            InodeData::Symlink(ref target) => Ok(target.to_vec()),
+        }
+    }
+
+    #[tracing::instrument(skip_all, fields(rq.inode = inode, name=?name))]
+    fn getxattr(
+        &self,
+        _ctx: &Context,
+        inode: Self::Inode,
+        name: &CStr,
+        size: u32,
+    ) -> io::Result<GetxattrReply> {
+        if !self.show_xattr {
+            return Err(io::Error::from_raw_os_error(libc::ENOSYS));
+        }
+
+        // Peek at the inode requested, and construct the response.
+        let digest_str = match *self
+            .inode_tracker
+            .read()
+            .get(inode)
+            .ok_or_else(|| io::Error::from_raw_os_error(libc::ENODATA))?
+        {
+            InodeData::Directory(DirectoryInodeData::Sparse(ref digest, _))
+            | InodeData::Directory(DirectoryInodeData::Populated(ref digest, _))
+                if name.to_bytes() == XATTR_NAME_DIRECTORY_DIGEST =>
+            {
+                digest.to_string()
+            }
+            InodeData::Regular(ref digest, _, _) if name.to_bytes() == XATTR_NAME_BLOB_DIGEST => {
+                digest.to_string()
+            }
+            _ => {
+                return Err(io::Error::from_raw_os_error(libc::ENODATA));
+            }
+        };
+
+        if size == 0 {
+            Ok(GetxattrReply::Count(digest_str.len() as u32))
+        } else if size < digest_str.len() as u32 {
+            Err(io::Error::from_raw_os_error(libc::ERANGE))
+        } else {
+            Ok(GetxattrReply::Value(digest_str.into_bytes()))
+        }
+    }
+
+    #[tracing::instrument(skip_all, fields(rq.inode = inode))]
+    fn listxattr(
+        &self,
+        _ctx: &Context,
+        inode: Self::Inode,
+        size: u32,
+    ) -> io::Result<ListxattrReply> {
+        if !self.show_xattr {
+            return Err(io::Error::from_raw_os_error(libc::ENOSYS));
+        }
+
+        // determine the (\0-terminated list) to of xattr keys present, depending on the type of the inode.
+        let xattrs_names = {
+            let mut out = Vec::new();
+            if let Some(inode_data) = self.inode_tracker.read().get(inode) {
+                match *inode_data {
+                    InodeData::Directory(_) => {
+                        out.extend_from_slice(XATTR_NAME_DIRECTORY_DIGEST);
+                        out.push_byte(b'\x00');
+                    }
+                    InodeData::Regular(..) => {
+                        out.extend_from_slice(XATTR_NAME_BLOB_DIGEST);
+                        out.push_byte(b'\x00');
+                    }
+                    _ => {}
+                }
+            }
+            out
+        };
+
+        if size == 0 {
+            Ok(ListxattrReply::Count(xattrs_names.len() as u32))
+        } else if size < xattrs_names.len() as u32 {
+            Err(io::Error::from_raw_os_error(libc::ERANGE))
+        } else {
+            Ok(ListxattrReply::Names(xattrs_names.to_vec()))
+        }
+    }
+}
diff --git a/tvix/castore/src/fs/root_nodes.rs b/tvix/castore/src/fs/root_nodes.rs
new file mode 100644
index 0000000000..6609e049a1
--- /dev/null
+++ b/tvix/castore/src/fs/root_nodes.rs
@@ -0,0 +1,37 @@
+use std::collections::BTreeMap;
+
+use crate::{proto::node::Node, Error};
+use bytes::Bytes;
+use futures::stream::BoxStream;
+use tonic::async_trait;
+
+/// Provides an interface for looking up root nodes  in tvix-castore by given
+/// a lookup key (usually the basename), and optionally allow a listing.
+#[async_trait]
+pub trait RootNodes: Send + Sync {
+    /// Looks up a root CA node based on the basename of the node in the root
+    /// directory of the filesystem.
+    async fn get_by_basename(&self, name: &[u8]) -> Result<Option<Node>, Error>;
+
+    /// Lists all root CA nodes in the filesystem. An error can be returned
+    /// in case listing is not allowed
+    fn list(&self) -> BoxStream<Result<Node, Error>>;
+}
+
+#[async_trait]
+/// Implements RootNodes for something deref'ing to a BTreeMap of Nodes, where
+/// the key is the node name.
+impl<T> RootNodes for T
+where
+    T: AsRef<BTreeMap<Bytes, Node>> + Send + Sync,
+{
+    async fn get_by_basename(&self, name: &[u8]) -> Result<Option<Node>, Error> {
+        Ok(self.as_ref().get(name).cloned())
+    }
+
+    fn list(&self) -> BoxStream<Result<Node, Error>> {
+        Box::pin(tokio_stream::iter(
+            self.as_ref().iter().map(|(_, v)| Ok(v.clone())),
+        ))
+    }
+}
diff --git a/tvix/castore/src/fs/tests.rs b/tvix/castore/src/fs/tests.rs
new file mode 100644
index 0000000000..d6eeb8a411
--- /dev/null
+++ b/tvix/castore/src/fs/tests.rs
@@ -0,0 +1,1244 @@
+use bstr::ByteSlice;
+use bytes::Bytes;
+use std::{
+    collections::BTreeMap,
+    ffi::{OsStr, OsString},
+    io::{self, Cursor},
+    os::unix::{ffi::OsStrExt, fs::MetadataExt},
+    path::Path,
+    sync::Arc,
+};
+use tempfile::TempDir;
+use tokio_stream::{wrappers::ReadDirStream, StreamExt};
+
+use super::{fuse::FuseDaemon, TvixStoreFs};
+use crate::proto as castorepb;
+use crate::proto::node::Node;
+use crate::{
+    blobservice::{BlobService, MemoryBlobService},
+    directoryservice::{DirectoryService, MemoryDirectoryService},
+    fixtures,
+};
+
+const BLOB_A_NAME: &str = "00000000000000000000000000000000-test";
+const BLOB_B_NAME: &str = "55555555555555555555555555555555-test";
+const HELLOWORLD_BLOB_NAME: &str = "66666666666666666666666666666666-test";
+const SYMLINK_NAME: &str = "11111111111111111111111111111111-test";
+const SYMLINK_NAME2: &str = "44444444444444444444444444444444-test";
+const DIRECTORY_WITH_KEEP_NAME: &str = "22222222222222222222222222222222-test";
+const DIRECTORY_COMPLICATED_NAME: &str = "33333333333333333333333333333333-test";
+
+fn gen_svcs() -> (Arc<dyn BlobService>, Arc<dyn DirectoryService>) {
+    (
+        Arc::new(MemoryBlobService::default()) as Arc<dyn BlobService>,
+        Arc::new(MemoryDirectoryService::default()) as Arc<dyn DirectoryService>,
+    )
+}
+
+fn do_mount<P: AsRef<Path>, BS, DS>(
+    blob_service: BS,
+    directory_service: DS,
+    root_nodes: BTreeMap<bytes::Bytes, Node>,
+    mountpoint: P,
+    list_root: bool,
+    show_xattr: bool,
+) -> io::Result<FuseDaemon>
+where
+    BS: AsRef<dyn BlobService> + Send + Sync + Clone + 'static,
+    DS: AsRef<dyn DirectoryService> + Send + Sync + Clone + 'static,
+{
+    let fs = TvixStoreFs::new(
+        blob_service,
+        directory_service,
+        Arc::new(root_nodes),
+        list_root,
+        show_xattr,
+    );
+    FuseDaemon::new(Arc::new(fs), mountpoint.as_ref(), 4, false)
+}
+
+async fn populate_blob_a(
+    blob_service: &Arc<dyn BlobService>,
+    root_nodes: &mut BTreeMap<Bytes, Node>,
+) {
+    let mut bw = blob_service.open_write().await;
+    tokio::io::copy(&mut Cursor::new(fixtures::BLOB_A.to_vec()), &mut bw)
+        .await
+        .expect("must succeed uploading");
+    bw.close().await.expect("must succeed closing");
+
+    root_nodes.insert(
+        BLOB_A_NAME.into(),
+        Node::File(castorepb::FileNode {
+            name: BLOB_A_NAME.into(),
+            digest: fixtures::BLOB_A_DIGEST.clone().into(),
+            size: fixtures::BLOB_A.len() as u64,
+            executable: false,
+        }),
+    );
+}
+
+async fn populate_blob_b(
+    blob_service: &Arc<dyn BlobService>,
+    root_nodes: &mut BTreeMap<Bytes, Node>,
+) {
+    let mut bw = blob_service.open_write().await;
+    tokio::io::copy(&mut Cursor::new(fixtures::BLOB_B.to_vec()), &mut bw)
+        .await
+        .expect("must succeed uploading");
+    bw.close().await.expect("must succeed closing");
+
+    root_nodes.insert(
+        BLOB_B_NAME.into(),
+        Node::File(castorepb::FileNode {
+            name: BLOB_B_NAME.into(),
+            digest: fixtures::BLOB_B_DIGEST.clone().into(),
+            size: fixtures::BLOB_B.len() as u64,
+            executable: false,
+        }),
+    );
+}
+
+/// adds a blob containing helloworld and marks it as executable
+async fn populate_blob_helloworld(
+    blob_service: &Arc<dyn BlobService>,
+    root_nodes: &mut BTreeMap<Bytes, Node>,
+) {
+    let mut bw = blob_service.open_write().await;
+    tokio::io::copy(
+        &mut Cursor::new(fixtures::HELLOWORLD_BLOB_CONTENTS.to_vec()),
+        &mut bw,
+    )
+    .await
+    .expect("must succeed uploading");
+    bw.close().await.expect("must succeed closing");
+
+    root_nodes.insert(
+        HELLOWORLD_BLOB_NAME.into(),
+        Node::File(castorepb::FileNode {
+            name: HELLOWORLD_BLOB_NAME.into(),
+            digest: fixtures::HELLOWORLD_BLOB_DIGEST.clone().into(),
+            size: fixtures::HELLOWORLD_BLOB_CONTENTS.len() as u64,
+            executable: true,
+        }),
+    );
+}
+
+async fn populate_symlink(root_nodes: &mut BTreeMap<Bytes, Node>) {
+    root_nodes.insert(
+        SYMLINK_NAME.into(),
+        Node::Symlink(castorepb::SymlinkNode {
+            name: SYMLINK_NAME.into(),
+            target: BLOB_A_NAME.into(),
+        }),
+    );
+}
+
+/// This writes a symlink pointing to /nix/store/somewhereelse,
+/// which is the same symlink target as "aa" inside DIRECTORY_COMPLICATED.
+async fn populate_symlink2(root_nodes: &mut BTreeMap<Bytes, Node>) {
+    root_nodes.insert(
+        SYMLINK_NAME2.into(),
+        Node::Symlink(castorepb::SymlinkNode {
+            name: SYMLINK_NAME2.into(),
+            target: "/nix/store/somewhereelse".into(),
+        }),
+    );
+}
+
+async fn populate_directory_with_keep(
+    blob_service: &Arc<dyn BlobService>,
+    directory_service: &Arc<dyn DirectoryService>,
+    root_nodes: &mut BTreeMap<Bytes, Node>,
+) {
+    // upload empty blob
+    let mut bw = blob_service.open_write().await;
+    assert_eq!(
+        fixtures::EMPTY_BLOB_DIGEST.as_slice(),
+        bw.close().await.expect("must succeed closing").as_slice(),
+    );
+
+    // upload directory
+    directory_service
+        .put(fixtures::DIRECTORY_WITH_KEEP.clone())
+        .await
+        .expect("must succeed uploading");
+
+    root_nodes.insert(
+        DIRECTORY_WITH_KEEP_NAME.into(),
+        castorepb::node::Node::Directory(castorepb::DirectoryNode {
+            name: DIRECTORY_WITH_KEEP_NAME.into(),
+            digest: fixtures::DIRECTORY_WITH_KEEP.digest().into(),
+            size: fixtures::DIRECTORY_WITH_KEEP.size(),
+        }),
+    );
+}
+
+/// Create a root node for DIRECTORY_WITH_KEEP, but don't upload the Directory
+/// itself.
+async fn populate_directorynode_without_directory(root_nodes: &mut BTreeMap<Bytes, Node>) {
+    root_nodes.insert(
+        DIRECTORY_WITH_KEEP_NAME.into(),
+        castorepb::node::Node::Directory(castorepb::DirectoryNode {
+            name: DIRECTORY_WITH_KEEP_NAME.into(),
+            digest: fixtures::DIRECTORY_WITH_KEEP.digest().into(),
+            size: fixtures::DIRECTORY_WITH_KEEP.size(),
+        }),
+    );
+}
+
+/// Insert BLOB_A, but don't provide the blob .keep is pointing to.
+async fn populate_filenode_without_blob(root_nodes: &mut BTreeMap<Bytes, Node>) {
+    root_nodes.insert(
+        BLOB_A_NAME.into(),
+        Node::File(castorepb::FileNode {
+            name: BLOB_A_NAME.into(),
+            digest: fixtures::BLOB_A_DIGEST.clone().into(),
+            size: fixtures::BLOB_A.len() as u64,
+            executable: false,
+        }),
+    );
+}
+
+async fn populate_directory_complicated(
+    blob_service: &Arc<dyn BlobService>,
+    directory_service: &Arc<dyn DirectoryService>,
+    root_nodes: &mut BTreeMap<Bytes, Node>,
+) {
+    // upload empty blob
+    let mut bw = blob_service.open_write().await;
+    assert_eq!(
+        fixtures::EMPTY_BLOB_DIGEST.as_slice(),
+        bw.close().await.expect("must succeed closing").as_slice(),
+    );
+
+    // upload inner directory
+    directory_service
+        .put(fixtures::DIRECTORY_WITH_KEEP.clone())
+        .await
+        .expect("must succeed uploading");
+
+    // upload parent directory
+    directory_service
+        .put(fixtures::DIRECTORY_COMPLICATED.clone())
+        .await
+        .expect("must succeed uploading");
+
+    root_nodes.insert(
+        DIRECTORY_COMPLICATED_NAME.into(),
+        Node::Directory(castorepb::DirectoryNode {
+            name: DIRECTORY_COMPLICATED_NAME.into(),
+            digest: fixtures::DIRECTORY_COMPLICATED.digest().into(),
+            size: fixtures::DIRECTORY_COMPLICATED.size(),
+        }),
+    );
+}
+
+/// Ensure mounting itself doesn't fail
+#[tokio::test]
+async fn mount() {
+    // https://plume.benboeckel.net/~/JustAnotherBlog/skipping-tests-in-rust
+    if !std::path::Path::new("/dev/fuse").exists() {
+        eprintln!("skipping test");
+        return;
+    }
+
+    let tmpdir = TempDir::new().unwrap();
+
+    let (blob_service, directory_service) = gen_svcs();
+
+    let mut fuse_daemon = do_mount(
+        blob_service,
+        directory_service,
+        BTreeMap::default(),
+        tmpdir.path(),
+        false,
+        false,
+    )
+    .expect("must succeed");
+
+    fuse_daemon.unmount().expect("unmount");
+}
+/// Ensure listing the root isn't allowed
+#[tokio::test]
+async fn root() {
+    // https://plume.benboeckel.net/~/JustAnotherBlog/skipping-tests-in-rust
+    if !std::path::Path::new("/dev/fuse").exists() {
+        eprintln!("skipping test");
+        return;
+    }
+    let tmpdir = TempDir::new().unwrap();
+
+    let (blob_service, directory_service) = gen_svcs();
+    let mut fuse_daemon = do_mount(
+        blob_service,
+        directory_service,
+        BTreeMap::default(),
+        tmpdir.path(),
+        false,
+        false,
+    )
+    .expect("must succeed");
+
+    {
+        // read_dir fails (as opendir fails).
+        let err = tokio::fs::read_dir(tmpdir).await.expect_err("must fail");
+        assert_eq!(std::io::ErrorKind::PermissionDenied, err.kind());
+    }
+
+    fuse_daemon.unmount().expect("unmount");
+}
+
+/// Ensure listing the root is allowed if configured explicitly
+#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
+async fn root_with_listing() {
+    // https://plume.benboeckel.net/~/JustAnotherBlog/skipping-tests-in-rust
+    if !std::path::Path::new("/dev/fuse").exists() {
+        eprintln!("skipping test");
+        return;
+    }
+    let tmpdir = TempDir::new().unwrap();
+
+    let (blob_service, directory_service) = gen_svcs();
+    let mut root_nodes = BTreeMap::default();
+
+    populate_blob_a(&blob_service, &mut root_nodes).await;
+
+    let mut fuse_daemon = do_mount(
+        blob_service,
+        directory_service,
+        root_nodes,
+        tmpdir.path(),
+        true, /* allow listing */
+        false,
+    )
+    .expect("must succeed");
+
+    {
+        // read_dir succeeds, but getting the first element will fail.
+        let mut it = ReadDirStream::new(tokio::fs::read_dir(tmpdir).await.expect("must succeed"));
+
+        let e = it
+            .next()
+            .await
+            .expect("must be some")
+            .expect("must succeed");
+
+        let metadata = e.metadata().await.expect("must succeed");
+        assert!(metadata.is_file());
+        assert!(metadata.permissions().readonly());
+        assert_eq!(fixtures::BLOB_A.len() as u64, metadata.len());
+    }
+
+    fuse_daemon.unmount().expect("unmount");
+}
+
+/// Ensure we can stat a file at the root
+#[tokio::test]
+async fn stat_file_at_root() {
+    // https://plume.benboeckel.net/~/JustAnotherBlog/skipping-tests-in-rust
+    if !std::path::Path::new("/dev/fuse").exists() {
+        eprintln!("skipping test");
+        return;
+    }
+    let tmpdir = TempDir::new().unwrap();
+
+    let (blob_service, directory_service) = gen_svcs();
+    let mut root_nodes = BTreeMap::default();
+
+    populate_blob_a(&blob_service, &mut root_nodes).await;
+
+    let mut fuse_daemon = do_mount(
+        blob_service,
+        directory_service,
+        root_nodes,
+        tmpdir.path(),
+        false,
+        false,
+    )
+    .expect("must succeed");
+
+    let p = tmpdir.path().join(BLOB_A_NAME);
+
+    // peek at the file metadata
+    let metadata = tokio::fs::metadata(p).await.expect("must succeed");
+
+    assert!(metadata.is_file());
+    assert!(metadata.permissions().readonly());
+    assert_eq!(fixtures::BLOB_A.len() as u64, metadata.len());
+
+    fuse_daemon.unmount().expect("unmount");
+}
+
+/// Ensure we can read a file at the root
+#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
+async fn read_file_at_root() {
+    // https://plume.benboeckel.net/~/JustAnotherBlog/skipping-tests-in-rust
+    if !std::path::Path::new("/dev/fuse").exists() {
+        eprintln!("skipping test");
+        return;
+    }
+    let tmpdir = TempDir::new().unwrap();
+
+    let (blob_service, directory_service) = gen_svcs();
+    let mut root_nodes = BTreeMap::default();
+
+    populate_blob_a(&blob_service, &mut root_nodes).await;
+
+    let mut fuse_daemon = do_mount(
+        blob_service,
+        directory_service,
+        root_nodes,
+        tmpdir.path(),
+        false,
+        false,
+    )
+    .expect("must succeed");
+
+    let p = tmpdir.path().join(BLOB_A_NAME);
+
+    // read the file contents
+    let data = tokio::fs::read(p).await.expect("must succeed");
+
+    // ensure size and contents match
+    assert_eq!(fixtures::BLOB_A.len(), data.len());
+    assert_eq!(fixtures::BLOB_A.to_vec(), data);
+
+    fuse_daemon.unmount().expect("unmount");
+}
+
+/// Ensure we can read a large file at the root
+#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
+async fn read_large_file_at_root() {
+    // https://plume.benboeckel.net/~/JustAnotherBlog/skipping-tests-in-rust
+    if !std::path::Path::new("/dev/fuse").exists() {
+        eprintln!("skipping test");
+        return;
+    }
+    let tmpdir = TempDir::new().unwrap();
+
+    let (blob_service, directory_service) = gen_svcs();
+    let mut root_nodes = BTreeMap::default();
+
+    populate_blob_b(&blob_service, &mut root_nodes).await;
+
+    let mut fuse_daemon = do_mount(
+        blob_service,
+        directory_service,
+        root_nodes,
+        tmpdir.path(),
+        false,
+        false,
+    )
+    .expect("must succeed");
+
+    let p = tmpdir.path().join(BLOB_B_NAME);
+    {
+        // peek at the file metadata
+        let metadata = tokio::fs::metadata(&p).await.expect("must succeed");
+
+        assert!(metadata.is_file());
+        assert!(metadata.permissions().readonly());
+        assert_eq!(fixtures::BLOB_B.len() as u64, metadata.len());
+    }
+
+    // read the file contents
+    let data = tokio::fs::read(p).await.expect("must succeed");
+
+    // ensure size and contents match
+    assert_eq!(fixtures::BLOB_B.len(), data.len());
+    assert_eq!(fixtures::BLOB_B.to_vec(), data);
+
+    fuse_daemon.unmount().expect("unmount");
+}
+
+/// Read the target of a symlink
+#[tokio::test]
+async fn symlink_readlink() {
+    // https://plume.benboeckel.net/~/JustAnotherBlog/skipping-tests-in-rust
+    if !std::path::Path::new("/dev/fuse").exists() {
+        eprintln!("skipping test");
+        return;
+    }
+    let tmpdir = TempDir::new().unwrap();
+
+    let (blob_service, directory_service) = gen_svcs();
+    let mut root_nodes = BTreeMap::default();
+
+    populate_symlink(&mut root_nodes).await;
+
+    let mut fuse_daemon = do_mount(
+        blob_service,
+        directory_service,
+        root_nodes,
+        tmpdir.path(),
+        false,
+        false,
+    )
+    .expect("must succeed");
+
+    let p = tmpdir.path().join(SYMLINK_NAME);
+
+    let target = tokio::fs::read_link(&p).await.expect("must succeed");
+    assert_eq!(BLOB_A_NAME, target.to_str().unwrap());
+
+    // peek at the file metadata, which follows symlinks.
+    // this must fail, as we didn't populate the target.
+    let e = tokio::fs::metadata(&p).await.expect_err("must fail");
+    assert_eq!(std::io::ErrorKind::NotFound, e.kind());
+
+    // peeking at the file metadata without following symlinks will succeed.
+    let metadata = tokio::fs::symlink_metadata(&p).await.expect("must succeed");
+    assert!(metadata.is_symlink());
+
+    // reading from the symlink (which follows) will fail, because the target doesn't exist.
+    let e = tokio::fs::read(p).await.expect_err("must fail");
+    assert_eq!(std::io::ErrorKind::NotFound, e.kind());
+
+    fuse_daemon.unmount().expect("unmount");
+}
+
+/// Read and stat a regular file through a symlink pointing to it.
+#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
+async fn read_stat_through_symlink() {
+    // https://plume.benboeckel.net/~/JustAnotherBlog/skipping-tests-in-rust
+    if !std::path::Path::new("/dev/fuse").exists() {
+        eprintln!("skipping test");
+        return;
+    }
+    let tmpdir = TempDir::new().unwrap();
+
+    let (blob_service, directory_service) = gen_svcs();
+    let mut root_nodes = BTreeMap::default();
+
+    populate_blob_a(&blob_service, &mut root_nodes).await;
+    populate_symlink(&mut root_nodes).await;
+
+    let mut fuse_daemon = do_mount(
+        blob_service,
+        directory_service,
+        root_nodes,
+        tmpdir.path(),
+        false,
+        false,
+    )
+    .expect("must succeed");
+
+    let p_symlink = tmpdir.path().join(SYMLINK_NAME);
+    let p_blob = tmpdir.path().join(SYMLINK_NAME);
+
+    // peek at the file metadata, which follows symlinks.
+    // this must now return the same metadata as when statting at the target directly.
+    let metadata_symlink = tokio::fs::metadata(&p_symlink).await.expect("must succeed");
+    let metadata_blob = tokio::fs::metadata(&p_blob).await.expect("must succeed");
+    assert_eq!(metadata_blob.file_type(), metadata_symlink.file_type());
+    assert_eq!(metadata_blob.len(), metadata_symlink.len());
+
+    // reading from the symlink (which follows) will return the same data as if
+    // we were reading from the file directly.
+    assert_eq!(
+        tokio::fs::read(p_blob).await.expect("must succeed"),
+        tokio::fs::read(p_symlink).await.expect("must succeed"),
+    );
+
+    fuse_daemon.unmount().expect("unmount");
+}
+
+/// Read a directory in the root, and validate some attributes.
+#[tokio::test]
+async fn read_stat_directory() {
+    // https://plume.benboeckel.net/~/JustAnotherBlog/skipping-tests-in-rust
+    if !std::path::Path::new("/dev/fuse").exists() {
+        eprintln!("skipping test");
+        return;
+    }
+    let tmpdir = TempDir::new().unwrap();
+
+    let (blob_service, directory_service) = gen_svcs();
+    let mut root_nodes = BTreeMap::default();
+
+    populate_directory_with_keep(&blob_service, &directory_service, &mut root_nodes).await;
+
+    let mut fuse_daemon = do_mount(
+        blob_service,
+        directory_service,
+        root_nodes,
+        tmpdir.path(),
+        false,
+        false,
+    )
+    .expect("must succeed");
+
+    let p = tmpdir.path().join(DIRECTORY_WITH_KEEP_NAME);
+
+    // peek at the metadata of the directory
+    let metadata = tokio::fs::metadata(p).await.expect("must succeed");
+    assert!(metadata.is_dir());
+    assert!(metadata.permissions().readonly());
+
+    fuse_daemon.unmount().expect("unmount");
+}
+
+/// Read a directory and file in the root, and ensure the xattrs expose blob or
+/// directory digests.
+#[tokio::test]
+async fn xattr() {
+    // https://plume.benboeckel.net/~/JustAnotherBlog/skipping-tests-in-rust
+    if !std::path::Path::new("/dev/fuse").exists() {
+        eprintln!("skipping test");
+        return;
+    }
+    let tmpdir = TempDir::new().unwrap();
+
+    let (blob_service, directory_service) = gen_svcs();
+    let mut root_nodes = BTreeMap::default();
+
+    populate_directory_with_keep(&blob_service, &directory_service, &mut root_nodes).await;
+    populate_blob_a(&blob_service, &mut root_nodes).await;
+
+    let mut fuse_daemon = do_mount(
+        blob_service,
+        directory_service,
+        root_nodes,
+        tmpdir.path(),
+        false,
+        true, /* support xattr */
+    )
+    .expect("must succeed");
+
+    // peek at the directory
+    {
+        let p = tmpdir.path().join(DIRECTORY_WITH_KEEP_NAME);
+
+        let xattr_names: Vec<OsString> = xattr::list(&p).expect("must succeed").collect();
+        // There should be 1 key, XATTR_NAME_DIRECTORY_DIGEST.
+        assert_eq!(1, xattr_names.len(), "there should be 1 xattr name");
+        assert_eq!(
+            super::XATTR_NAME_DIRECTORY_DIGEST,
+            xattr_names.first().unwrap().as_encoded_bytes()
+        );
+
+        // The key should equal to the string-formatted b3 digest.
+        let val = xattr::get(&p, OsStr::from_bytes(super::XATTR_NAME_DIRECTORY_DIGEST))
+            .expect("must succeed")
+            .expect("must be some");
+        assert_eq!(
+            fixtures::DIRECTORY_WITH_KEEP
+                .digest()
+                .to_string()
+                .as_bytes()
+                .as_bstr(),
+            val.as_bstr()
+        );
+
+        // Reading another xattr key is gonna return None.
+        let val = xattr::get(&p, OsStr::from_bytes(b"user.cheesecake")).expect("must succeed");
+        assert_eq!(None, val);
+    }
+    // peek at the file
+    {
+        let p = tmpdir.path().join(BLOB_A_NAME);
+
+        let xattr_names: Vec<OsString> = xattr::list(&p).expect("must succeed").collect();
+        // There should be 1 key, XATTR_NAME_BLOB_DIGEST.
+        assert_eq!(1, xattr_names.len(), "there should be 1 xattr name");
+        assert_eq!(
+            super::XATTR_NAME_BLOB_DIGEST,
+            xattr_names.first().unwrap().as_encoded_bytes()
+        );
+
+        // The key should equal to the string-formatted b3 digest.
+        let val = xattr::get(&p, OsStr::from_bytes(super::XATTR_NAME_BLOB_DIGEST))
+            .expect("must succeed")
+            .expect("must be some");
+        assert_eq!(
+            fixtures::BLOB_A_DIGEST.to_string().as_bytes().as_bstr(),
+            val.as_bstr()
+        );
+
+        // Reading another xattr key is gonna return None.
+        let val = xattr::get(&p, OsStr::from_bytes(b"user.cheesecake")).expect("must succeed");
+        assert_eq!(None, val);
+    }
+
+    fuse_daemon.unmount().expect("unmount");
+}
+
+#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
+/// Read a blob inside a directory. This ensures we successfully populate directory data.
+async fn read_blob_inside_dir() {
+    // https://plume.benboeckel.net/~/JustAnotherBlog/skipping-tests-in-rust
+    if !std::path::Path::new("/dev/fuse").exists() {
+        eprintln!("skipping test");
+        return;
+    }
+    let tmpdir = TempDir::new().unwrap();
+
+    let (blob_service, directory_service) = gen_svcs();
+    let mut root_nodes = BTreeMap::default();
+
+    populate_directory_with_keep(&blob_service, &directory_service, &mut root_nodes).await;
+
+    let mut fuse_daemon = do_mount(
+        blob_service,
+        directory_service,
+        root_nodes,
+        tmpdir.path(),
+        false,
+        false,
+    )
+    .expect("must succeed");
+
+    let p = tmpdir.path().join(DIRECTORY_WITH_KEEP_NAME).join(".keep");
+
+    // peek at metadata.
+    let metadata = tokio::fs::metadata(&p).await.expect("must succeed");
+    assert!(metadata.is_file());
+    assert!(metadata.permissions().readonly());
+
+    // read from it
+    let data = tokio::fs::read(&p).await.expect("must succeed");
+    assert_eq!(fixtures::EMPTY_BLOB_CONTENTS.to_vec(), data);
+
+    fuse_daemon.unmount().expect("unmount");
+}
+
+#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
+/// Read a blob inside a directory inside a directory. This ensures we properly
+/// populate directories as we traverse down the structure.
+async fn read_blob_deep_inside_dir() {
+    // https://plume.benboeckel.net/~/JustAnotherBlog/skipping-tests-in-rust
+    if !std::path::Path::new("/dev/fuse").exists() {
+        eprintln!("skipping test");
+        return;
+    }
+    let tmpdir = TempDir::new().unwrap();
+
+    let (blob_service, directory_service) = gen_svcs();
+    let mut root_nodes = BTreeMap::default();
+
+    populate_directory_complicated(&blob_service, &directory_service, &mut root_nodes).await;
+
+    let mut fuse_daemon = do_mount(
+        blob_service,
+        directory_service,
+        root_nodes,
+        tmpdir.path(),
+        false,
+        false,
+    )
+    .expect("must succeed");
+
+    let p = tmpdir
+        .path()
+        .join(DIRECTORY_COMPLICATED_NAME)
+        .join("keep")
+        .join(".keep");
+
+    // peek at metadata.
+    let metadata = tokio::fs::metadata(&p).await.expect("must succeed");
+    assert!(metadata.is_file());
+    assert!(metadata.permissions().readonly());
+
+    // read from it
+    let data = tokio::fs::read(&p).await.expect("must succeed");
+    assert_eq!(fixtures::EMPTY_BLOB_CONTENTS.to_vec(), data);
+
+    fuse_daemon.unmount().expect("unmount");
+}
+
+/// Ensure readdir works.
+#[tokio::test]
+async fn readdir() {
+    // https://plume.benboeckel.net/~/JustAnotherBlog/skipping-tests-in-rust
+    if !std::path::Path::new("/dev/fuse").exists() {
+        eprintln!("skipping test");
+        return;
+    }
+    let tmpdir = TempDir::new().unwrap();
+
+    let (blob_service, directory_service) = gen_svcs();
+    let mut root_nodes = BTreeMap::default();
+
+    populate_directory_complicated(&blob_service, &directory_service, &mut root_nodes).await;
+
+    let mut fuse_daemon = do_mount(
+        blob_service,
+        directory_service,
+        root_nodes,
+        tmpdir.path(),
+        false,
+        false,
+    )
+    .expect("must succeed");
+
+    let p = tmpdir.path().join(DIRECTORY_COMPLICATED_NAME);
+
+    {
+        // read_dir should succeed. Collect all elements
+        let elements: Vec<_> =
+            ReadDirStream::new(tokio::fs::read_dir(p).await.expect("must succeed"))
+                .map(|e| e.expect("must not be err"))
+                .collect()
+                .await;
+
+        assert_eq!(3, elements.len(), "number of elements should be 3"); // rust skips . and ..
+
+        // We explicitly look at specific positions here, because we always emit
+        // them ordered.
+
+        // ".keep", 0 byte file.
+        let e = &elements[0];
+        assert_eq!(".keep", e.file_name());
+        assert!(e.file_type().await.expect("must succeed").is_file());
+        assert_eq!(0, e.metadata().await.expect("must succeed").len());
+
+        // "aa", symlink.
+        let e = &elements[1];
+        assert_eq!("aa", e.file_name());
+        assert!(e.file_type().await.expect("must succeed").is_symlink());
+
+        // "keep", directory
+        let e = &elements[2];
+        assert_eq!("keep", e.file_name());
+        assert!(e.file_type().await.expect("must succeed").is_dir());
+    }
+
+    fuse_daemon.unmount().expect("unmount");
+}
+
+#[tokio::test]
+/// Do a readdir deeper inside a directory, without doing readdir or stat in the parent directory.
+async fn readdir_deep() {
+    // https://plume.benboeckel.net/~/JustAnotherBlog/skipping-tests-in-rust
+    if !std::path::Path::new("/dev/fuse").exists() {
+        eprintln!("skipping test");
+        return;
+    }
+    let tmpdir = TempDir::new().unwrap();
+
+    let (blob_service, directory_service) = gen_svcs();
+    let mut root_nodes = BTreeMap::default();
+
+    populate_directory_complicated(&blob_service, &directory_service, &mut root_nodes).await;
+
+    let mut fuse_daemon = do_mount(
+        blob_service,
+        directory_service,
+        root_nodes,
+        tmpdir.path(),
+        false,
+        false,
+    )
+    .expect("must succeed");
+
+    let p = tmpdir.path().join(DIRECTORY_COMPLICATED_NAME).join("keep");
+
+    {
+        // read_dir should succeed. Collect all elements
+        let elements: Vec<_> =
+            ReadDirStream::new(tokio::fs::read_dir(p).await.expect("must succeed"))
+                .map(|e| e.expect("must not be err"))
+                .collect()
+                .await;
+
+        assert_eq!(1, elements.len(), "number of elements should be 1"); // rust skips . and ..
+
+        // ".keep", 0 byte file.
+        let e = &elements[0];
+        assert_eq!(".keep", e.file_name());
+        assert!(e.file_type().await.expect("must succeed").is_file());
+        assert_eq!(0, e.metadata().await.expect("must succeed").len());
+    }
+
+    fuse_daemon.unmount().expect("unmount");
+}
+
+/// Check attributes match how they show up in /nix/store normally.
+#[tokio::test]
+async fn check_attributes() {
+    // https://plume.benboeckel.net/~/JustAnotherBlog/skipping-tests-in-rust
+    if !std::path::Path::new("/dev/fuse").exists() {
+        eprintln!("skipping test");
+        return;
+    }
+    let tmpdir = TempDir::new().unwrap();
+
+    let (blob_service, directory_service) = gen_svcs();
+    let mut root_nodes = BTreeMap::default();
+
+    populate_blob_a(&blob_service, &mut root_nodes).await;
+    populate_directory_with_keep(&blob_service, &directory_service, &mut root_nodes).await;
+    populate_symlink(&mut root_nodes).await;
+    populate_blob_helloworld(&blob_service, &mut root_nodes).await;
+
+    let mut fuse_daemon = do_mount(
+        blob_service,
+        directory_service,
+        root_nodes,
+        tmpdir.path(),
+        false,
+        false,
+    )
+    .expect("must succeed");
+
+    let p_file = tmpdir.path().join(BLOB_A_NAME);
+    let p_directory = tmpdir.path().join(DIRECTORY_WITH_KEEP_NAME);
+    let p_symlink = tmpdir.path().join(SYMLINK_NAME);
+    let p_executable_file = tmpdir.path().join(HELLOWORLD_BLOB_NAME);
+
+    // peek at metadata. We use symlink_metadata to ensure we don't traverse a symlink by accident.
+    let metadata_file = tokio::fs::symlink_metadata(&p_file)
+        .await
+        .expect("must succeed");
+    let metadata_executable_file = tokio::fs::symlink_metadata(&p_executable_file)
+        .await
+        .expect("must succeed");
+    let metadata_directory = tokio::fs::symlink_metadata(&p_directory)
+        .await
+        .expect("must succeed");
+    let metadata_symlink = tokio::fs::symlink_metadata(&p_symlink)
+        .await
+        .expect("must succeed");
+
+    // modes should match. We & with 0o777 to remove any higher bits.
+    assert_eq!(0o444, metadata_file.mode() & 0o777);
+    assert_eq!(0o555, metadata_executable_file.mode() & 0o777);
+    assert_eq!(0o555, metadata_directory.mode() & 0o777);
+    assert_eq!(0o444, metadata_symlink.mode() & 0o777);
+
+    // files should have the correct filesize
+    assert_eq!(fixtures::BLOB_A.len() as u64, metadata_file.len());
+    // directories should have their "size" as filesize
+    assert_eq!(
+        { fixtures::DIRECTORY_WITH_KEEP.size() },
+        metadata_directory.size()
+    );
+
+    for metadata in &[&metadata_file, &metadata_directory, &metadata_symlink] {
+        // uid and gid should be 0.
+        assert_eq!(0, metadata.uid());
+        assert_eq!(0, metadata.gid());
+
+        // all times should be set to the unix epoch.
+        assert_eq!(0, metadata.atime());
+        assert_eq!(0, metadata.mtime());
+        assert_eq!(0, metadata.ctime());
+        // crtime seems MacOS only
+    }
+
+    fuse_daemon.unmount().expect("unmount");
+}
+
+#[tokio::test]
+/// Ensure we allocate the same inodes for the same directory contents.
+/// $DIRECTORY_COMPLICATED_NAME/keep contains the same data as $DIRECTORY_WITH_KEEP.
+async fn compare_inodes_directories() {
+    // https://plume.benboeckel.net/~/JustAnotherBlog/skipping-tests-in-rust
+    if !std::path::Path::new("/dev/fuse").exists() {
+        eprintln!("skipping test");
+        return;
+    }
+    let tmpdir = TempDir::new().unwrap();
+
+    let (blob_service, directory_service) = gen_svcs();
+    let mut root_nodes = BTreeMap::default();
+
+    populate_directory_with_keep(&blob_service, &directory_service, &mut root_nodes).await;
+    populate_directory_complicated(&blob_service, &directory_service, &mut root_nodes).await;
+
+    let mut fuse_daemon = do_mount(
+        blob_service,
+        directory_service,
+        root_nodes,
+        tmpdir.path(),
+        false,
+        false,
+    )
+    .expect("must succeed");
+
+    let p_dir_with_keep = tmpdir.path().join(DIRECTORY_WITH_KEEP_NAME);
+    let p_sibling_dir = tmpdir.path().join(DIRECTORY_COMPLICATED_NAME).join("keep");
+
+    // peek at metadata.
+    assert_eq!(
+        tokio::fs::metadata(p_dir_with_keep)
+            .await
+            .expect("must succeed")
+            .ino(),
+        tokio::fs::metadata(p_sibling_dir)
+            .await
+            .expect("must succeed")
+            .ino()
+    );
+
+    fuse_daemon.unmount().expect("unmount");
+}
+
+/// Ensure we allocate the same inodes for the same directory contents.
+/// $DIRECTORY_COMPLICATED_NAME/keep/,keep contains the same data as $DIRECTORY_COMPLICATED_NAME/.keep
+#[tokio::test]
+async fn compare_inodes_files() {
+    // https://plume.benboeckel.net/~/JustAnotherBlog/skipping-tests-in-rust
+    if !std::path::Path::new("/dev/fuse").exists() {
+        eprintln!("skipping test");
+        return;
+    }
+    let tmpdir = TempDir::new().unwrap();
+
+    let (blob_service, directory_service) = gen_svcs();
+    let mut root_nodes = BTreeMap::default();
+
+    populate_directory_complicated(&blob_service, &directory_service, &mut root_nodes).await;
+
+    let mut fuse_daemon = do_mount(
+        blob_service,
+        directory_service,
+        root_nodes,
+        tmpdir.path(),
+        false,
+        false,
+    )
+    .expect("must succeed");
+
+    let p_keep1 = tmpdir.path().join(DIRECTORY_COMPLICATED_NAME).join(".keep");
+    let p_keep2 = tmpdir
+        .path()
+        .join(DIRECTORY_COMPLICATED_NAME)
+        .join("keep")
+        .join(".keep");
+
+    // peek at metadata.
+    assert_eq!(
+        tokio::fs::metadata(p_keep1)
+            .await
+            .expect("must succeed")
+            .ino(),
+        tokio::fs::metadata(p_keep2)
+            .await
+            .expect("must succeed")
+            .ino()
+    );
+
+    fuse_daemon.unmount().expect("unmount");
+}
+
+/// Ensure we allocate the same inode for symlinks pointing to the same targets.
+/// $DIRECTORY_COMPLICATED_NAME/aa points to the same target as SYMLINK_NAME2.
+#[tokio::test]
+async fn compare_inodes_symlinks() {
+    // https://plume.benboeckel.net/~/JustAnotherBlog/skipping-tests-in-rust
+    if !std::path::Path::new("/dev/fuse").exists() {
+        eprintln!("skipping test");
+        return;
+    }
+    let tmpdir = TempDir::new().unwrap();
+
+    let (blob_service, directory_service) = gen_svcs();
+    let mut root_nodes = BTreeMap::default();
+
+    populate_directory_complicated(&blob_service, &directory_service, &mut root_nodes).await;
+    populate_symlink2(&mut root_nodes).await;
+
+    let mut fuse_daemon = do_mount(
+        blob_service,
+        directory_service,
+        root_nodes,
+        tmpdir.path(),
+        false,
+        false,
+    )
+    .expect("must succeed");
+
+    let p1 = tmpdir.path().join(DIRECTORY_COMPLICATED_NAME).join("aa");
+    let p2 = tmpdir.path().join(SYMLINK_NAME2);
+
+    // peek at metadata.
+    assert_eq!(
+        tokio::fs::symlink_metadata(p1)
+            .await
+            .expect("must succeed")
+            .ino(),
+        tokio::fs::symlink_metadata(p2)
+            .await
+            .expect("must succeed")
+            .ino()
+    );
+
+    fuse_daemon.unmount().expect("unmount");
+}
+
+/// Check we match paths exactly.
+#[tokio::test]
+async fn read_wrong_paths_in_root() {
+    // https://plume.benboeckel.net/~/JustAnotherBlog/skipping-tests-in-rust
+    if !std::path::Path::new("/dev/fuse").exists() {
+        eprintln!("skipping test");
+        return;
+    }
+    let tmpdir = TempDir::new().unwrap();
+
+    let (blob_service, directory_service) = gen_svcs();
+    let mut root_nodes = BTreeMap::default();
+
+    populate_blob_a(&blob_service, &mut root_nodes).await;
+
+    let mut fuse_daemon = do_mount(
+        blob_service,
+        directory_service,
+        root_nodes,
+        tmpdir.path(),
+        false,
+        false,
+    )
+    .expect("must succeed");
+
+    // wrong name
+    assert!(
+        tokio::fs::metadata(tmpdir.path().join("00000000000000000000000000000000-tes"))
+            .await
+            .is_err()
+    );
+
+    // invalid hash
+    assert!(
+        tokio::fs::metadata(tmpdir.path().join("0000000000000000000000000000000-test"))
+            .await
+            .is_err()
+    );
+
+    // right name, must exist
+    assert!(
+        tokio::fs::metadata(tmpdir.path().join("00000000000000000000000000000000-test"))
+            .await
+            .is_ok()
+    );
+
+    // now wrong name with right hash still may not exist
+    assert!(
+        tokio::fs::metadata(tmpdir.path().join("00000000000000000000000000000000-tes"))
+            .await
+            .is_err()
+    );
+
+    fuse_daemon.unmount().expect("unmount");
+}
+
+/// Make sure writes are not allowed
+#[tokio::test]
+async fn disallow_writes() {
+    // https://plume.benboeckel.net/~/JustAnotherBlog/skipping-tests-in-rust
+    if !std::path::Path::new("/dev/fuse").exists() {
+        eprintln!("skipping test");
+        return;
+    }
+
+    let tmpdir = TempDir::new().unwrap();
+
+    let (blob_service, directory_service) = gen_svcs();
+    let root_nodes = BTreeMap::default();
+
+    let mut fuse_daemon = do_mount(
+        blob_service,
+        directory_service,
+        root_nodes,
+        tmpdir.path(),
+        false,
+        false,
+    )
+    .expect("must succeed");
+
+    let p = tmpdir.path().join(BLOB_A_NAME);
+    let e = tokio::fs::File::create(p).await.expect_err("must fail");
+
+    assert_eq!(Some(libc::EROFS), e.raw_os_error());
+
+    fuse_daemon.unmount().expect("unmount");
+}
+
+#[tokio::test]
+/// Ensure we get an IO error if the directory service does not have the Directory object.
+async fn missing_directory() {
+    if !std::path::Path::new("/dev/fuse").exists() {
+        eprintln!("skipping test");
+        return;
+    }
+    let tmpdir = TempDir::new().unwrap();
+
+    let (blob_service, directory_service) = gen_svcs();
+    let mut root_nodes = BTreeMap::default();
+
+    populate_directorynode_without_directory(&mut root_nodes).await;
+
+    let mut fuse_daemon = do_mount(
+        blob_service,
+        directory_service,
+        root_nodes,
+        tmpdir.path(),
+        false,
+        false,
+    )
+    .expect("must succeed");
+
+    let p = tmpdir.path().join(DIRECTORY_WITH_KEEP_NAME);
+
+    {
+        // `stat` on the path should succeed, because it doesn't trigger the directory request.
+        tokio::fs::metadata(&p).await.expect("must succeed");
+
+        // However, calling either `readdir` or `stat` on a child should fail with an IO error.
+        // It fails when trying to pull the first entry, because we don't implement opendir separately
+        ReadDirStream::new(tokio::fs::read_dir(&p).await.unwrap())
+            .next()
+            .await
+            .expect("must be some")
+            .expect_err("must be err");
+
+        // rust currently sets e.kind() to Uncategorized, which isn't very
+        // helpful, so we don't look at the error more closely than that..
+        tokio::fs::metadata(p.join(".keep"))
+            .await
+            .expect_err("must fail");
+    }
+
+    fuse_daemon.unmount().expect("unmount");
+}
+
+#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
+/// Ensure we get an IO error if the blob service does not have the blob
+async fn missing_blob() {
+    if !std::path::Path::new("/dev/fuse").exists() {
+        eprintln!("skipping test");
+        return;
+    }
+    let tmpdir = TempDir::new().unwrap();
+
+    let (blob_service, directory_service) = gen_svcs();
+    let mut root_nodes = BTreeMap::default();
+
+    populate_filenode_without_blob(&mut root_nodes).await;
+
+    let mut fuse_daemon = do_mount(
+        blob_service,
+        directory_service,
+        root_nodes,
+        tmpdir.path(),
+        false,
+        false,
+    )
+    .expect("must succeed");
+
+    let p = tmpdir.path().join(BLOB_A_NAME);
+
+    {
+        // `stat` on the blob should succeed, because it doesn't trigger a request to the blob service.
+        tokio::fs::metadata(&p).await.expect("must succeed");
+
+        // However, calling read on the blob should fail.
+        // rust currently sets e.kind() to Uncategorized, which isn't very
+        // helpful, so we don't look at the error more closely than that..
+        tokio::fs::read(p).await.expect_err("must fail");
+    }
+
+    fuse_daemon.unmount().expect("unmount");
+}
diff --git a/tvix/castore/src/fs/virtiofs.rs b/tvix/castore/src/fs/virtiofs.rs
new file mode 100644
index 0000000000..d63e2f2bdd
--- /dev/null
+++ b/tvix/castore/src/fs/virtiofs.rs
@@ -0,0 +1,238 @@
+use std::{
+    convert, error, fmt, io,
+    ops::Deref,
+    path::Path,
+    sync::{Arc, MutexGuard, RwLock},
+};
+
+use fuse_backend_rs::{
+    api::{filesystem::FileSystem, server::Server},
+    transport::{FsCacheReqHandler, Reader, VirtioFsWriter},
+};
+use tracing::error;
+use vhost::vhost_user::{
+    Listener, SlaveFsCacheReq, VhostUserProtocolFeatures, VhostUserVirtioFeatures,
+};
+use vhost_user_backend::{VhostUserBackendMut, VhostUserDaemon, VringMutex, VringState, VringT};
+use virtio_bindings::bindings::virtio_ring::{
+    VIRTIO_RING_F_EVENT_IDX, VIRTIO_RING_F_INDIRECT_DESC,
+};
+use virtio_queue::QueueT;
+use vm_memory::{GuestAddressSpace, GuestMemoryAtomic, GuestMemoryMmap};
+use vmm_sys_util::epoll::EventSet;
+
+const VIRTIO_F_VERSION_1: u32 = 32;
+const NUM_QUEUES: usize = 2;
+const QUEUE_SIZE: usize = 1024;
+
+#[derive(Debug)]
+enum Error {
+    /// Failed to handle non-input event.
+    HandleEventNotEpollIn,
+    /// Failed to handle unknown event.
+    HandleEventUnknownEvent,
+    /// Invalid descriptor chain.
+    InvalidDescriptorChain,
+    /// Failed to handle filesystem requests.
+    #[allow(dead_code)]
+    HandleRequests(fuse_backend_rs::Error),
+    /// Failed to construct new vhost user daemon.
+    NewDaemon,
+    /// Failed to start the vhost user daemon.
+    StartDaemon,
+    /// Failed to wait for the vhost user daemon.
+    WaitDaemon,
+}
+
+impl fmt::Display for Error {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "vhost_user_fs_error: {self:?}")
+    }
+}
+
+impl error::Error for Error {}
+
+impl convert::From<Error> for io::Error {
+    fn from(e: Error) -> Self {
+        io::Error::new(io::ErrorKind::Other, e)
+    }
+}
+
+struct VhostUserFsBackend<FS>
+where
+    FS: FileSystem + Send + Sync,
+{
+    server: Arc<Server<Arc<FS>>>,
+    event_idx: bool,
+    guest_mem: GuestMemoryAtomic<GuestMemoryMmap>,
+    cache_req: Option<SlaveFsCacheReq>,
+}
+
+impl<FS> VhostUserFsBackend<FS>
+where
+    FS: FileSystem + Send + Sync,
+{
+    fn process_queue(&mut self, vring: &mut MutexGuard<VringState>) -> std::io::Result<bool> {
+        let mut used_descs = false;
+
+        while let Some(desc_chain) = vring
+            .get_queue_mut()
+            .pop_descriptor_chain(self.guest_mem.memory())
+        {
+            let memory = desc_chain.memory();
+            let reader = Reader::from_descriptor_chain(memory, desc_chain.clone())
+                .map_err(|_| Error::InvalidDescriptorChain)?;
+            let writer = VirtioFsWriter::new(memory, desc_chain.clone())
+                .map_err(|_| Error::InvalidDescriptorChain)?;
+
+            self.server
+                .handle_message(
+                    reader,
+                    writer.into(),
+                    self.cache_req
+                        .as_mut()
+                        .map(|req| req as &mut dyn FsCacheReqHandler),
+                    None,
+                )
+                .map_err(Error::HandleRequests)?;
+
+            // TODO: Is len 0 correct?
+            if let Err(error) = vring
+                .get_queue_mut()
+                .add_used(memory, desc_chain.head_index(), 0)
+            {
+                error!(?error, "failed to add desc back to ring");
+            }
+
+            // TODO: What happens if we error out before here?
+            used_descs = true;
+        }
+
+        let needs_notification = if self.event_idx {
+            match vring
+                .get_queue_mut()
+                .needs_notification(self.guest_mem.memory().deref())
+            {
+                Ok(needs_notification) => needs_notification,
+                Err(error) => {
+                    error!(?error, "failed to check if queue needs notification");
+                    true
+                }
+            }
+        } else {
+            true
+        };
+
+        if needs_notification {
+            if let Err(error) = vring.signal_used_queue() {
+                error!(?error, "failed to signal used queue");
+            }
+        }
+
+        Ok(used_descs)
+    }
+}
+
+impl<FS> VhostUserBackendMut<VringMutex> for VhostUserFsBackend<FS>
+where
+    FS: FileSystem + Send + Sync,
+{
+    fn num_queues(&self) -> usize {
+        NUM_QUEUES
+    }
+
+    fn max_queue_size(&self) -> usize {
+        QUEUE_SIZE
+    }
+
+    fn features(&self) -> u64 {
+        1 << VIRTIO_F_VERSION_1
+            | 1 << VIRTIO_RING_F_INDIRECT_DESC
+            | 1 << VIRTIO_RING_F_EVENT_IDX
+            | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits()
+    }
+
+    fn protocol_features(&self) -> VhostUserProtocolFeatures {
+        VhostUserProtocolFeatures::MQ | VhostUserProtocolFeatures::SLAVE_REQ
+    }
+
+    fn set_event_idx(&mut self, enabled: bool) {
+        self.event_idx = enabled;
+    }
+
+    fn update_memory(&mut self, _mem: GuestMemoryAtomic<GuestMemoryMmap>) -> std::io::Result<()> {
+        // This is what most the vhost user implementations do...
+        Ok(())
+    }
+
+    fn set_slave_req_fd(&mut self, cache_req: SlaveFsCacheReq) {
+        self.cache_req = Some(cache_req);
+    }
+
+    fn handle_event(
+        &mut self,
+        device_event: u16,
+        evset: vmm_sys_util::epoll::EventSet,
+        vrings: &[VringMutex],
+        _thread_id: usize,
+    ) -> std::io::Result<bool> {
+        if evset != EventSet::IN {
+            return Err(Error::HandleEventNotEpollIn.into());
+        }
+
+        let mut queue = match device_event {
+            // High priority queue
+            0 => vrings[0].get_mut(),
+            // Regurlar priority queue
+            1 => vrings[1].get_mut(),
+            _ => {
+                return Err(Error::HandleEventUnknownEvent.into());
+            }
+        };
+
+        if self.event_idx {
+            loop {
+                queue
+                    .get_queue_mut()
+                    .enable_notification(self.guest_mem.memory().deref())
+                    .map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?;
+                if !self.process_queue(&mut queue)? {
+                    break;
+                }
+            }
+        } else {
+            self.process_queue(&mut queue)?;
+        }
+
+        Ok(false)
+    }
+}
+
+pub fn start_virtiofs_daemon<FS, P>(fs: FS, socket: P) -> io::Result<()>
+where
+    FS: FileSystem + Send + Sync + 'static,
+    P: AsRef<Path>,
+{
+    let guest_mem = GuestMemoryAtomic::new(GuestMemoryMmap::new());
+
+    let server = Arc::new(fuse_backend_rs::api::server::Server::new(Arc::new(fs)));
+
+    let backend = Arc::new(RwLock::new(VhostUserFsBackend {
+        server,
+        guest_mem: guest_mem.clone(),
+        event_idx: false,
+        cache_req: None,
+    }));
+
+    let listener = Listener::new(socket, true).unwrap();
+
+    let mut fs_daemon =
+        VhostUserDaemon::new(String::from("vhost-user-fs-tvix-store"), backend, guest_mem)
+            .map_err(|_| Error::NewDaemon)?;
+
+    fs_daemon.start(listener).map_err(|_| Error::StartDaemon)?;
+
+    fs_daemon.wait().map_err(|_| Error::WaitDaemon)?;
+
+    Ok(())
+}
diff --git a/tvix/castore/src/hashing_reader.rs b/tvix/castore/src/hashing_reader.rs
new file mode 100644
index 0000000000..7d78cae587
--- /dev/null
+++ b/tvix/castore/src/hashing_reader.rs
@@ -0,0 +1,89 @@
+use pin_project_lite::pin_project;
+use tokio::io::AsyncRead;
+
+pin_project! {
+    /// Wraps an existing AsyncRead, and allows querying for the digest of all
+    /// data read "through" it.
+    /// The hash function is configurable by type parameter.
+    pub struct HashingReader<R, H>
+    where
+        R: AsyncRead,
+        H: digest::Digest,
+    {
+        #[pin]
+        inner: R,
+        hasher: H,
+    }
+}
+
+pub type B3HashingReader<R> = HashingReader<R, blake3::Hasher>;
+
+impl<R, H> HashingReader<R, H>
+where
+    R: AsyncRead,
+    H: digest::Digest,
+{
+    pub fn from(r: R) -> Self {
+        Self {
+            inner: r,
+            hasher: H::new(),
+        }
+    }
+
+    /// Return the digest.
+    pub fn digest(self) -> digest::Output<H> {
+        self.hasher.finalize()
+    }
+}
+
+impl<R, H> tokio::io::AsyncRead for HashingReader<R, H>
+where
+    R: AsyncRead,
+    H: digest::Digest,
+{
+    fn poll_read(
+        self: std::pin::Pin<&mut Self>,
+        cx: &mut std::task::Context<'_>,
+        buf: &mut tokio::io::ReadBuf<'_>,
+    ) -> std::task::Poll<std::io::Result<()>> {
+        let buf_filled_len_before = buf.filled().len();
+
+        let this = self.project();
+        let ret = this.inner.poll_read(cx, buf);
+
+        // write everything new filled into the hasher.
+        this.hasher.update(&buf.filled()[buf_filled_len_before..]);
+
+        ret
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use std::io::Cursor;
+
+    use rstest::rstest;
+
+    use crate::fixtures::BLOB_A;
+    use crate::fixtures::BLOB_A_DIGEST;
+    use crate::fixtures::BLOB_B;
+    use crate::fixtures::BLOB_B_DIGEST;
+    use crate::fixtures::EMPTY_BLOB_DIGEST;
+    use crate::{B3Digest, B3HashingReader};
+
+    #[rstest]
+    #[case::blob_a(&BLOB_A, &BLOB_A_DIGEST)]
+    #[case::blob_b(&BLOB_B, &BLOB_B_DIGEST)]
+    #[case::empty_blob(&[], &EMPTY_BLOB_DIGEST)]
+    #[tokio::test]
+    async fn test_b3_hashing_reader(#[case] data: &[u8], #[case] b3_digest: &B3Digest) {
+        let r = Cursor::new(data);
+        let mut hr = B3HashingReader::from(r);
+
+        tokio::io::copy(&mut hr, &mut tokio::io::sink())
+            .await
+            .expect("read must succeed");
+
+        assert_eq!(*b3_digest, hr.digest().into());
+    }
+}
diff --git a/tvix/castore/src/import/archive.rs b/tvix/castore/src/import/archive.rs
new file mode 100644
index 0000000000..0ebb4a2361
--- /dev/null
+++ b/tvix/castore/src/import/archive.rs
@@ -0,0 +1,458 @@
+//! Imports from an archive (tarballs)
+
+use std::collections::HashMap;
+use std::io::{Cursor, Write};
+use std::sync::Arc;
+
+use petgraph::graph::{DiGraph, NodeIndex};
+use petgraph::visit::{DfsPostOrder, EdgeRef};
+use petgraph::Direction;
+use tokio::io::AsyncRead;
+use tokio::sync::Semaphore;
+use tokio::task::JoinSet;
+use tokio_stream::StreamExt;
+use tokio_tar::Archive;
+use tokio_util::io::InspectReader;
+use tracing::{instrument, warn, Level};
+
+use crate::blobservice::BlobService;
+use crate::directoryservice::DirectoryService;
+use crate::import::{ingest_entries, IngestionEntry, IngestionError};
+use crate::proto::node::Node;
+use crate::B3Digest;
+
+type TarPathBuf = std::path::PathBuf;
+
+/// Files smaller than this threshold, in bytes, are uploaded to the [BlobService] in the
+/// background.
+///
+/// This is a u32 since we acquire a weighted semaphore using the size of the blob.
+/// [Semaphore::acquire_many_owned] takes a u32, so we need to ensure the size of
+/// the blob can be represented using a u32 and will not cause an overflow.
+const CONCURRENT_BLOB_UPLOAD_THRESHOLD: u32 = 1024 * 1024;
+
+/// The maximum amount of bytes allowed to be buffered in memory to perform async blob uploads.
+const MAX_TARBALL_BUFFER_SIZE: usize = 128 * 1024 * 1024;
+
+#[derive(Debug, thiserror::Error)]
+pub enum Error {
+    #[error("unable to construct stream of entries: {0}")]
+    Entries(std::io::Error),
+
+    #[error("unable to read next entry: {0}")]
+    NextEntry(std::io::Error),
+
+    #[error("unable to read path for entry: {0}")]
+    PathRead(std::io::Error),
+
+    #[error("unable to convert path {0} for entry: {1}")]
+    PathConvert(TarPathBuf, std::io::Error),
+
+    #[error("unable to read size field for {0}: {1}")]
+    Size(TarPathBuf, std::io::Error),
+
+    #[error("unable to read mode field for {0}: {1}")]
+    Mode(TarPathBuf, std::io::Error),
+
+    #[error("unable to read link name field for {0}: {1}")]
+    LinkName(TarPathBuf, std::io::Error),
+
+    #[error("unable to read blob contents for {0}: {1}")]
+    BlobRead(TarPathBuf, std::io::Error),
+
+    // FUTUREWORK: proper error for blob finalize
+    #[error("unable to finalize blob {0}: {1}")]
+    BlobFinalize(TarPathBuf, std::io::Error),
+
+    #[error("unsupported tar entry {0} type: {1:?}")]
+    EntryType(TarPathBuf, tokio_tar::EntryType),
+
+    #[error("symlink missing target {0}")]
+    MissingSymlinkTarget(TarPathBuf),
+
+    #[error("unexpected number of top level directory entries")]
+    UnexpectedNumberOfTopLevelEntries,
+}
+
+/// Ingests elements from the given tar [`Archive`] into a the passed [`BlobService`] and
+/// [`DirectoryService`].
+#[instrument(skip_all, ret(level = Level::TRACE), err)]
+pub async fn ingest_archive<BS, DS, R>(
+    blob_service: BS,
+    directory_service: DS,
+    mut archive: Archive<R>,
+) -> Result<Node, IngestionError<Error>>
+where
+    BS: BlobService + Clone + 'static,
+    DS: DirectoryService,
+    R: AsyncRead + Unpin,
+{
+    // Since tarballs can have entries in any arbitrary order, we need to
+    // buffer all of the directory metadata so we can reorder directory
+    // contents and entries to meet the requires of the castore.
+
+    // In the first phase, collect up all the regular files and symlinks.
+    let mut nodes = IngestionEntryGraph::new();
+
+    let semaphore = Arc::new(Semaphore::new(MAX_TARBALL_BUFFER_SIZE));
+    let mut async_blob_uploads: JoinSet<Result<(), Error>> = JoinSet::new();
+
+    let mut entries_iter = archive.entries().map_err(Error::Entries)?;
+    while let Some(mut entry) = entries_iter.try_next().await.map_err(Error::NextEntry)? {
+        let tar_path: TarPathBuf = entry.path().map_err(Error::PathRead)?.into();
+
+        // construct a castore PathBuf, which we use in the produced IngestionEntry.
+        let path = crate::path::PathBuf::from_host_path(tar_path.as_path(), true)
+            .map_err(|e| Error::PathConvert(tar_path.clone(), e))?;
+
+        let header = entry.header();
+        let entry = match header.entry_type() {
+            tokio_tar::EntryType::Regular
+            | tokio_tar::EntryType::GNUSparse
+            | tokio_tar::EntryType::Continuous => {
+                let header_size = header
+                    .size()
+                    .map_err(|e| Error::Size(tar_path.clone(), e))?;
+
+                // If the blob is small enough, read it off the wire, compute the digest,
+                // and upload it to the [BlobService] in the background.
+                let (size, digest) = if header_size <= CONCURRENT_BLOB_UPLOAD_THRESHOLD as u64 {
+                    let mut buffer = Vec::with_capacity(header_size as usize);
+                    let mut hasher = blake3::Hasher::new();
+                    let mut reader = InspectReader::new(&mut entry, |bytes| {
+                        hasher.write_all(bytes).unwrap();
+                    });
+
+                    // Ensure that we don't buffer into memory until we've acquired a permit.
+                    // This prevents consuming too much memory when performing concurrent
+                    // blob uploads.
+                    let permit = semaphore
+                        .clone()
+                        // This cast is safe because ensure the header_size is less than
+                        // CONCURRENT_BLOB_UPLOAD_THRESHOLD which is a u32.
+                        .acquire_many_owned(header_size as u32)
+                        .await
+                        .unwrap();
+                    let size = tokio::io::copy(&mut reader, &mut buffer)
+                        .await
+                        .map_err(|e| Error::Size(tar_path.clone(), e))?;
+
+                    let digest: B3Digest = hasher.finalize().as_bytes().into();
+
+                    {
+                        let blob_service = blob_service.clone();
+                        let digest = digest.clone();
+                        async_blob_uploads.spawn({
+                            let tar_path = tar_path.clone();
+                            async move {
+                                let mut writer = blob_service.open_write().await;
+
+                                tokio::io::copy(&mut Cursor::new(buffer), &mut writer)
+                                    .await
+                                    .map_err(|e| Error::BlobRead(tar_path.clone(), e))?;
+
+                                let blob_digest = writer
+                                    .close()
+                                    .await
+                                    .map_err(|e| Error::BlobFinalize(tar_path, e))?;
+
+                                assert_eq!(digest, blob_digest, "Tvix bug: blob digest mismatch");
+
+                                // Make sure we hold the permit until we finish writing the blob
+                                // to the [BlobService].
+                                drop(permit);
+                                Ok(())
+                            }
+                        });
+                    }
+
+                    (size, digest)
+                } else {
+                    let mut writer = blob_service.open_write().await;
+
+                    let size = tokio::io::copy(&mut entry, &mut writer)
+                        .await
+                        .map_err(|e| Error::BlobRead(tar_path.clone(), e))?;
+
+                    let digest = writer
+                        .close()
+                        .await
+                        .map_err(|e| Error::BlobFinalize(tar_path.clone(), e))?;
+
+                    (size, digest)
+                };
+
+                let executable = entry
+                    .header()
+                    .mode()
+                    .map_err(|e| Error::Mode(tar_path, e))?
+                    & 64
+                    != 0;
+
+                IngestionEntry::Regular {
+                    path,
+                    size,
+                    executable,
+                    digest,
+                }
+            }
+            tokio_tar::EntryType::Symlink => IngestionEntry::Symlink {
+                target: entry
+                    .link_name()
+                    .map_err(|e| Error::LinkName(tar_path.clone(), e))?
+                    .ok_or_else(|| Error::MissingSymlinkTarget(tar_path.clone()))?
+                    .into_owned()
+                    .into_os_string()
+                    .into_encoded_bytes(),
+                path,
+            },
+            // Push a bogus directory marker so we can make sure this directoy gets
+            // created. We don't know the digest and size until after reading the full
+            // tarball.
+            tokio_tar::EntryType::Directory => IngestionEntry::Dir { path },
+
+            tokio_tar::EntryType::XGlobalHeader | tokio_tar::EntryType::XHeader => continue,
+
+            entry_type => return Err(Error::EntryType(tar_path, entry_type).into()),
+        };
+
+        nodes.add(entry)?;
+    }
+
+    while let Some(result) = async_blob_uploads.join_next().await {
+        result.expect("task panicked")?;
+    }
+
+    let root_node = ingest_entries(
+        directory_service,
+        futures::stream::iter(nodes.finalize()?.into_iter().map(Ok)),
+    )
+    .await?;
+
+    Ok(root_node)
+}
+
+/// Keep track of the directory structure of a file tree being ingested. This is used
+/// for ingestion sources which do not provide any ordering or uniqueness guarantees
+/// like tarballs.
+///
+/// If we ingest multiple entries with the same paths and both entries are not directories,
+/// the newer entry will replace the latter entry, disconnecting the old node's children
+/// from the graph.
+///
+/// Once all nodes are ingested a call to [IngestionEntryGraph::finalize] will return
+/// a list of entries compute by performaing a DFS post order traversal of the graph
+/// from the top-level directory entry.
+///
+/// This expects the directory structure to contain a single top-level directory entry.
+/// An error is returned if this is not the case and ingestion will fail.
+struct IngestionEntryGraph {
+    graph: DiGraph<IngestionEntry, ()>,
+    path_to_index: HashMap<crate::path::PathBuf, NodeIndex>,
+    root_node: Option<NodeIndex>,
+}
+
+impl Default for IngestionEntryGraph {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+impl IngestionEntryGraph {
+    /// Creates a new ingestion entry graph.
+    pub fn new() -> Self {
+        IngestionEntryGraph {
+            graph: DiGraph::new(),
+            path_to_index: HashMap::new(),
+            root_node: None,
+        }
+    }
+
+    /// Adds a new entry to the graph. Parent directories are automatically inserted.
+    /// If a node exists in the graph with the same name as the new entry and both the old
+    /// and new nodes are not directories, the node is replaced and is disconnected from its
+    /// children.
+    pub fn add(&mut self, entry: IngestionEntry) -> Result<NodeIndex, Error> {
+        let path = entry.path().to_owned();
+
+        let index = match self.path_to_index.get(entry.path()) {
+            Some(&index) => {
+                // If either the old entry or new entry are not directories, we'll replace the old
+                // entry.
+                if !entry.is_dir() || !self.get_node(index).is_dir() {
+                    self.replace_node(index, entry);
+                }
+
+                index
+            }
+            None => self.graph.add_node(entry),
+        };
+
+        // for archives, a path with 1 component is the root node
+        if path.components().count() == 1 {
+            // We expect archives to contain a single root node, if there is another root node
+            // entry with a different path name, this is unsupported.
+            if let Some(root_node) = self.root_node {
+                if self.get_node(root_node).path() != path.as_ref() {
+                    return Err(Error::UnexpectedNumberOfTopLevelEntries);
+                }
+            }
+
+            self.root_node = Some(index)
+        } else if let Some(parent_path) = path.parent() {
+            // Recursively add the parent node until it hits the root node.
+            let parent_index = self.add(IngestionEntry::Dir {
+                path: parent_path.to_owned(),
+            })?;
+
+            // Insert an edge from the parent directory to the child entry.
+            self.graph.add_edge(parent_index, index, ());
+        }
+
+        self.path_to_index.insert(path, index);
+
+        Ok(index)
+    }
+
+    /// Traverses the graph in DFS post order and collects the entries into a [Vec<IngestionEntry>].
+    ///
+    /// Unreachable parts of the graph are not included in the result.
+    pub fn finalize(self) -> Result<Vec<IngestionEntry>, Error> {
+        // There must be a root node.
+        let Some(root_node_index) = self.root_node else {
+            return Err(Error::UnexpectedNumberOfTopLevelEntries);
+        };
+
+        // The root node must be a directory.
+        if !self.get_node(root_node_index).is_dir() {
+            return Err(Error::UnexpectedNumberOfTopLevelEntries);
+        }
+
+        let mut traversal = DfsPostOrder::new(&self.graph, root_node_index);
+        let mut nodes = Vec::with_capacity(self.graph.node_count());
+        while let Some(node_index) = traversal.next(&self.graph) {
+            nodes.push(self.get_node(node_index).clone());
+        }
+
+        Ok(nodes)
+    }
+
+    /// Replaces the node with the specified entry. The node's children are disconnected.
+    ///
+    /// This should never be called if both the old and new nodes are directories.
+    fn replace_node(&mut self, index: NodeIndex, new_entry: IngestionEntry) {
+        let entry = self
+            .graph
+            .node_weight_mut(index)
+            .expect("Tvix bug: missing node entry");
+
+        debug_assert!(!(entry.is_dir() && new_entry.is_dir()));
+
+        // Replace the node itself.
+        warn!(
+            "saw duplicate entry in archive at path {:?}. old: {:?} new: {:?}",
+            entry.path(),
+            &entry,
+            &new_entry
+        );
+        *entry = new_entry;
+
+        // Remove any outgoing edges to disconnect the old node's children.
+        let edges = self
+            .graph
+            .edges_directed(index, Direction::Outgoing)
+            .map(|edge| edge.id())
+            .collect::<Vec<_>>();
+        for edge in edges {
+            self.graph.remove_edge(edge);
+        }
+    }
+
+    fn get_node(&self, index: NodeIndex) -> &IngestionEntry {
+        self.graph
+            .node_weight(index)
+            .expect("Tvix bug: missing node entry")
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use crate::import::IngestionEntry;
+    use crate::B3Digest;
+
+    use super::{Error, IngestionEntryGraph};
+
+    use lazy_static::lazy_static;
+    use rstest::rstest;
+
+    lazy_static! {
+        pub static ref EMPTY_DIGEST: B3Digest = blake3::hash(&[]).as_bytes().into();
+        pub static ref DIR_A: IngestionEntry = IngestionEntry::Dir {
+            path: "a".parse().unwrap()
+        };
+        pub static ref DIR_B: IngestionEntry = IngestionEntry::Dir {
+            path: "b".parse().unwrap()
+        };
+        pub static ref DIR_A_B: IngestionEntry = IngestionEntry::Dir {
+            path: "a/b".parse().unwrap()
+        };
+        pub static ref FILE_A: IngestionEntry = IngestionEntry::Regular {
+            path: "a".parse().unwrap(),
+            size: 0,
+            executable: false,
+            digest: EMPTY_DIGEST.clone(),
+        };
+        pub static ref FILE_A_B: IngestionEntry = IngestionEntry::Regular {
+            path: "a/b".parse().unwrap(),
+            size: 0,
+            executable: false,
+            digest: EMPTY_DIGEST.clone(),
+        };
+        pub static ref FILE_A_B_C: IngestionEntry = IngestionEntry::Regular {
+            path: "a/b/c".parse().unwrap(),
+            size: 0,
+            executable: false,
+            digest: EMPTY_DIGEST.clone(),
+        };
+    }
+
+    #[rstest]
+    #[case::implicit_directories(&[&*FILE_A_B_C], &[&*FILE_A_B_C, &*DIR_A_B, &*DIR_A])]
+    #[case::explicit_directories(&[&*DIR_A, &*DIR_A_B, &*FILE_A_B_C], &[&*FILE_A_B_C, &*DIR_A_B, &*DIR_A])]
+    #[case::inaccesible_tree(&[&*DIR_A, &*DIR_A_B, &*FILE_A_B], &[&*FILE_A_B, &*DIR_A])]
+    fn node_ingestion_success(
+        #[case] in_entries: &[&IngestionEntry],
+        #[case] exp_entries: &[&IngestionEntry],
+    ) {
+        let mut nodes = IngestionEntryGraph::new();
+
+        for entry in in_entries {
+            nodes.add((*entry).clone()).expect("failed to add entry");
+        }
+
+        let entries = nodes.finalize().expect("invalid entries");
+
+        let exp_entries: Vec<IngestionEntry> =
+            exp_entries.iter().map(|entry| (*entry).clone()).collect();
+
+        assert_eq!(entries, exp_entries);
+    }
+
+    #[rstest]
+    #[case::no_top_level_entries(&[], Error::UnexpectedNumberOfTopLevelEntries)]
+    #[case::multiple_top_level_dirs(&[&*DIR_A, &*DIR_B], Error::UnexpectedNumberOfTopLevelEntries)]
+    #[case::top_level_file_entry(&[&*FILE_A], Error::UnexpectedNumberOfTopLevelEntries)]
+    fn node_ingestion_error(#[case] in_entries: &[&IngestionEntry], #[case] exp_error: Error) {
+        let mut nodes = IngestionEntryGraph::new();
+
+        let result = (|| {
+            for entry in in_entries {
+                nodes.add((*entry).clone())?;
+            }
+            nodes.finalize()
+        })();
+
+        let error = result.expect_err("expected error");
+        assert_eq!(error.to_string(), exp_error.to_string());
+    }
+}
diff --git a/tvix/castore/src/import/error.rs b/tvix/castore/src/import/error.rs
new file mode 100644
index 0000000000..e3fba617e0
--- /dev/null
+++ b/tvix/castore/src/import/error.rs
@@ -0,0 +1,20 @@
+use super::PathBuf;
+
+use crate::Error as CastoreError;
+
+/// Represents all error types that emitted by ingest_entries.
+/// It can represent errors uploading individual Directories and finalizing
+/// the upload.
+/// It also contains a generic error kind that'll carry ingestion-method
+/// specific errors.
+#[derive(Debug, thiserror::Error)]
+pub enum IngestionError<E: std::fmt::Display> {
+    #[error("error from producer: {0}")]
+    Producer(#[from] E),
+
+    #[error("failed to upload directory at {0}: {1}")]
+    UploadDirectoryError(PathBuf, CastoreError),
+
+    #[error("failed to finalize directory upload: {0}")]
+    FinalizeDirectoryUpload(CastoreError),
+}
diff --git a/tvix/castore/src/import/fs.rs b/tvix/castore/src/import/fs.rs
new file mode 100644
index 0000000000..9d3ecfe6ab
--- /dev/null
+++ b/tvix/castore/src/import/fs.rs
@@ -0,0 +1,185 @@
+//! Import from a real filesystem.
+
+use futures::stream::BoxStream;
+use futures::StreamExt;
+use std::fs::FileType;
+use std::os::unix::ffi::OsStringExt;
+use std::os::unix::fs::MetadataExt;
+use std::os::unix::fs::PermissionsExt;
+use tracing::instrument;
+use walkdir::DirEntry;
+use walkdir::WalkDir;
+
+use crate::blobservice::BlobService;
+use crate::directoryservice::DirectoryService;
+use crate::proto::node::Node;
+use crate::B3Digest;
+
+use super::ingest_entries;
+use super::IngestionEntry;
+use super::IngestionError;
+
+/// Ingests the contents at a given path into the tvix store, interacting with a [BlobService] and
+/// [DirectoryService]. It returns the root node or an error.
+///
+/// It does not follow symlinks at the root, they will be ingested as actual symlinks.
+///
+/// This function will walk the filesystem using `walkdir` and will consume
+/// `O(#number of entries)` space.
+#[instrument(skip(blob_service, directory_service), fields(path), err)]
+pub async fn ingest_path<BS, DS, P>(
+    blob_service: BS,
+    directory_service: DS,
+    path: P,
+) -> Result<Node, IngestionError<Error>>
+where
+    P: AsRef<std::path::Path> + std::fmt::Debug,
+    BS: BlobService + Clone,
+    DS: DirectoryService,
+{
+    let iter = WalkDir::new(path.as_ref())
+        .follow_links(false)
+        .follow_root_links(false)
+        .contents_first(true)
+        .into_iter();
+
+    let entries = dir_entries_to_ingestion_stream(blob_service, iter, path.as_ref());
+    ingest_entries(directory_service, entries).await
+}
+
+/// Converts an iterator of [walkdir::DirEntry]s into a stream of ingestion entries.
+/// This can then be fed into [ingest_entries] to ingest all the entries into the castore.
+///
+/// The produced stream is buffered, so uploads can happen concurrently.
+///
+/// The root is the [Path] in the filesystem that is being ingested into the castore.
+pub fn dir_entries_to_ingestion_stream<'a, BS, I>(
+    blob_service: BS,
+    iter: I,
+    root: &'a std::path::Path,
+) -> BoxStream<'a, Result<IngestionEntry, Error>>
+where
+    BS: BlobService + Clone + 'a,
+    I: Iterator<Item = Result<DirEntry, walkdir::Error>> + Send + 'a,
+{
+    let prefix = root.parent().unwrap_or_else(|| std::path::Path::new(""));
+
+    Box::pin(
+        futures::stream::iter(iter)
+            .map(move |x| {
+                let blob_service = blob_service.clone();
+                async move {
+                    match x {
+                        Ok(dir_entry) => {
+                            dir_entry_to_ingestion_entry(blob_service, &dir_entry, prefix).await
+                        }
+                        Err(e) => Err(Error::Stat(
+                            prefix.to_path_buf(),
+                            e.into_io_error().expect("walkdir err must be some"),
+                        )),
+                    }
+                }
+            })
+            .buffered(50),
+    )
+}
+
+/// Converts a [walkdir::DirEntry] into an [IngestionEntry], uploading blobs to the
+/// provided [BlobService].
+///
+/// The prefix path is stripped from the path of each entry. This is usually the parent path
+/// of the path being ingested so that the last element of the stream only has one component.
+pub async fn dir_entry_to_ingestion_entry<BS>(
+    blob_service: BS,
+    entry: &DirEntry,
+    prefix: &std::path::Path,
+) -> Result<IngestionEntry, Error>
+where
+    BS: BlobService,
+{
+    let file_type = entry.file_type();
+
+    let fs_path = entry
+        .path()
+        .strip_prefix(prefix)
+        .expect("Tvix bug: failed to strip root path prefix");
+
+    // convert to castore PathBuf
+    let path = crate::path::PathBuf::from_host_path(fs_path, false)
+        .unwrap_or_else(|e| panic!("Tvix bug: walkdir direntry cannot be parsed: {}", e));
+
+    if file_type.is_dir() {
+        Ok(IngestionEntry::Dir { path })
+    } else if file_type.is_symlink() {
+        let target = std::fs::read_link(entry.path())
+            .map_err(|e| Error::Stat(entry.path().to_path_buf(), e))?
+            .into_os_string()
+            .into_vec();
+
+        Ok(IngestionEntry::Symlink { path, target })
+    } else if file_type.is_file() {
+        let metadata = entry
+            .metadata()
+            .map_err(|e| Error::Stat(entry.path().to_path_buf(), e.into()))?;
+
+        let digest = upload_blob(blob_service, entry.path().to_path_buf()).await?;
+
+        Ok(IngestionEntry::Regular {
+            path,
+            size: metadata.size(),
+            // If it's executable by the user, it'll become executable.
+            // This matches nix's dump() function behaviour.
+            executable: metadata.permissions().mode() & 64 != 0,
+            digest,
+        })
+    } else {
+        return Err(Error::FileType(fs_path.to_path_buf(), file_type));
+    }
+}
+
+/// Uploads the file at the provided [Path] the the [BlobService].
+#[instrument(skip(blob_service), fields(path), err)]
+async fn upload_blob<BS>(
+    blob_service: BS,
+    path: impl AsRef<std::path::Path>,
+) -> Result<B3Digest, Error>
+where
+    BS: BlobService,
+{
+    let mut file = match tokio::fs::File::open(path.as_ref()).await {
+        Ok(file) => file,
+        Err(e) => return Err(Error::BlobRead(path.as_ref().to_path_buf(), e)),
+    };
+
+    let mut writer = blob_service.open_write().await;
+
+    if let Err(e) = tokio::io::copy(&mut file, &mut writer).await {
+        return Err(Error::BlobRead(path.as_ref().to_path_buf(), e));
+    };
+
+    let digest = writer
+        .close()
+        .await
+        .map_err(|e| Error::BlobFinalize(path.as_ref().to_path_buf(), e))?;
+
+    Ok(digest)
+}
+
+#[derive(Debug, thiserror::Error)]
+pub enum Error {
+    #[error("unsupported file type at {0}: {1:?}")]
+    FileType(std::path::PathBuf, FileType),
+
+    #[error("unable to stat {0}: {1}")]
+    Stat(std::path::PathBuf, std::io::Error),
+
+    #[error("unable to open {0}: {1}")]
+    Open(std::path::PathBuf, std::io::Error),
+
+    #[error("unable to read {0}: {1}")]
+    BlobRead(std::path::PathBuf, std::io::Error),
+
+    // TODO: proper error for blob finalize
+    #[error("unable to finalize blob {0}: {1}")]
+    BlobFinalize(std::path::PathBuf, std::io::Error),
+}
diff --git a/tvix/castore/src/import/mod.rs b/tvix/castore/src/import/mod.rs
new file mode 100644
index 0000000000..e8b27e469c
--- /dev/null
+++ b/tvix/castore/src/import/mod.rs
@@ -0,0 +1,340 @@
+//! The main library function here is [ingest_entries], receiving a stream of
+//! [IngestionEntry].
+//!
+//! Specific implementations, such as ingesting from the filesystem, live in
+//! child modules.
+
+use crate::directoryservice::DirectoryPutter;
+use crate::directoryservice::DirectoryService;
+use crate::path::{Path, PathBuf};
+use crate::proto::node::Node;
+use crate::proto::Directory;
+use crate::proto::DirectoryNode;
+use crate::proto::FileNode;
+use crate::proto::SymlinkNode;
+use crate::B3Digest;
+use futures::{Stream, StreamExt};
+
+use tracing::Level;
+
+use std::collections::HashMap;
+use tracing::instrument;
+
+mod error;
+pub use error::IngestionError;
+
+pub mod archive;
+pub mod fs;
+
+/// Ingests [IngestionEntry] from the given stream into a the passed [DirectoryService].
+/// On success, returns the root [Node].
+///
+/// The stream must have the following invariants:
+/// - All children entries must come before their parents.
+/// - The last entry must be the root node which must have a single path component.
+/// - Every entry should have a unique path, and only consist of normal components.
+///   This means, no windows path prefixes, absolute paths, `.` or `..`.
+/// - All referenced directories must have an associated directory entry in the stream.
+///   This means if there is a file entry for `foo/bar`, there must also be a `foo` directory
+///   entry.
+///
+/// Internally we maintain a [HashMap] of [PathBuf] to partially populated [Directory] at that
+/// path. Once we receive an [IngestionEntry] for the directory itself, we remove it from the
+/// map and upload it to the [DirectoryService] through a lazily created [DirectoryPutter].
+///
+/// On success, returns the root node.
+#[instrument(skip_all, ret(level = Level::TRACE), err)]
+pub async fn ingest_entries<DS, S, E>(
+    directory_service: DS,
+    mut entries: S,
+) -> Result<Node, IngestionError<E>>
+where
+    DS: DirectoryService,
+    S: Stream<Item = Result<IngestionEntry, E>> + Send + std::marker::Unpin,
+    E: std::error::Error,
+{
+    // For a given path, this holds the [Directory] structs as they are populated.
+    let mut directories: HashMap<PathBuf, Directory> = HashMap::default();
+    let mut maybe_directory_putter: Option<Box<dyn DirectoryPutter>> = None;
+
+    let root_node = loop {
+        let mut entry = entries
+            .next()
+            .await
+            // The last entry of the stream must have 1 path component, after which
+            // we break the loop manually.
+            .expect("Tvix bug: unexpected end of stream")?;
+
+        let name = entry
+            .path()
+            .file_name()
+            // If this is the root node, it will have an empty name.
+            .unwrap_or_default()
+            .to_owned()
+            .into();
+
+        let node = match &mut entry {
+            IngestionEntry::Dir { .. } => {
+                // If the entry is a directory, we traversed all its children (and
+                // populated it in `directories`).
+                // If we don't have it in directories, it's a directory without
+                // children.
+                let directory = directories
+                    .remove(entry.path())
+                    // In that case, it contained no children
+                    .unwrap_or_default();
+
+                let directory_size = directory.size();
+                let directory_digest = directory.digest();
+
+                // Use the directory_putter to upload the directory.
+                // If we don't have one yet (as that's the first one to upload),
+                // initialize the putter.
+                maybe_directory_putter
+                    .get_or_insert_with(|| directory_service.put_multiple_start())
+                    .put(directory)
+                    .await
+                    .map_err(|e| {
+                        IngestionError::UploadDirectoryError(entry.path().to_owned(), e)
+                    })?;
+
+                Node::Directory(DirectoryNode {
+                    name,
+                    digest: directory_digest.into(),
+                    size: directory_size,
+                })
+            }
+            IngestionEntry::Symlink { ref target, .. } => Node::Symlink(SymlinkNode {
+                name,
+                target: target.to_owned().into(),
+            }),
+            IngestionEntry::Regular {
+                size,
+                executable,
+                digest,
+                ..
+            } => Node::File(FileNode {
+                name,
+                digest: digest.to_owned().into(),
+                size: *size,
+                executable: *executable,
+            }),
+        };
+
+        let parent = entry
+            .path()
+            .parent()
+            .expect("Tvix bug: got entry with root node");
+
+        if parent == crate::Path::ROOT {
+            break node;
+        } else {
+            // record node in parent directory, creating a new [Directory] if not there yet.
+            directories.entry(parent.to_owned()).or_default().add(node);
+        }
+    };
+
+    assert!(
+        entries.count().await == 0,
+        "Tvix bug: left over elements in the stream"
+    );
+
+    assert!(
+        directories.is_empty(),
+        "Tvix bug: left over directories after processing ingestion stream"
+    );
+
+    // if there were directories uploaded, make sure we flush the putter, so
+    // they're all persisted to the backend.
+    if let Some(mut directory_putter) = maybe_directory_putter {
+        #[cfg_attr(not(debug_assertions), allow(unused))]
+        let root_directory_digest = directory_putter
+            .close()
+            .await
+            .map_err(|e| IngestionError::FinalizeDirectoryUpload(e))?;
+
+        #[cfg(debug_assertions)]
+        {
+            if let Node::Directory(directory_node) = &root_node {
+                debug_assert_eq!(
+                    root_directory_digest,
+                    directory_node
+                        .digest
+                        .to_vec()
+                        .try_into()
+                        .expect("invalid digest len")
+                )
+            } else {
+                unreachable!("Tvix bug: directory putter initialized but no root directory node");
+            }
+        }
+    };
+
+    Ok(root_node)
+}
+
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub enum IngestionEntry {
+    Regular {
+        path: PathBuf,
+        size: u64,
+        executable: bool,
+        digest: B3Digest,
+    },
+    Symlink {
+        path: PathBuf,
+        target: Vec<u8>,
+    },
+    Dir {
+        path: PathBuf,
+    },
+}
+
+impl IngestionEntry {
+    fn path(&self) -> &Path {
+        match self {
+            IngestionEntry::Regular { path, .. } => path,
+            IngestionEntry::Symlink { path, .. } => path,
+            IngestionEntry::Dir { path } => path,
+        }
+    }
+
+    fn is_dir(&self) -> bool {
+        matches!(self, IngestionEntry::Dir { .. })
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use rstest::rstest;
+
+    use crate::fixtures::{DIRECTORY_COMPLICATED, DIRECTORY_WITH_KEEP, EMPTY_BLOB_DIGEST};
+    use crate::proto::node::Node;
+    use crate::proto::{Directory, DirectoryNode, FileNode, SymlinkNode};
+    use crate::{directoryservice::MemoryDirectoryService, fixtures::DUMMY_DIGEST};
+
+    use super::ingest_entries;
+    use super::IngestionEntry;
+
+    #[rstest]
+    #[case::single_file(vec![IngestionEntry::Regular {
+        path: "foo".parse().unwrap(),
+        size: 42,
+        executable: true,
+        digest: DUMMY_DIGEST.clone(),
+    }],
+        Node::File(FileNode { name: "foo".into(), digest: DUMMY_DIGEST.clone().into(), size: 42, executable: true }
+    ))]
+    #[case::single_symlink(vec![IngestionEntry::Symlink {
+        path: "foo".parse().unwrap(),
+        target: b"blub".into(),
+    }],
+        Node::Symlink(SymlinkNode { name: "foo".into(), target: "blub".into()})
+    )]
+    #[case::single_dir(vec![IngestionEntry::Dir {
+        path: "foo".parse().unwrap(),
+    }],
+        Node::Directory(DirectoryNode { name: "foo".into(), digest: Directory::default().digest().into(), size: Directory::default().size()})
+    )]
+    #[case::dir_with_keep(vec![
+        IngestionEntry::Regular {
+            path: "foo/.keep".parse().unwrap(),
+            size: 0,
+            executable: false,
+            digest: EMPTY_BLOB_DIGEST.clone(),
+        },
+        IngestionEntry::Dir {
+            path: "foo".parse().unwrap(),
+        },
+    ],
+        Node::Directory(DirectoryNode { name: "foo".into(), digest: DIRECTORY_WITH_KEEP.digest().into(), size: DIRECTORY_WITH_KEEP.size() })
+    )]
+    /// This is intentionally a bit unsorted, though it still satisfies all
+    /// requirements we have on the order of elements in the stream.
+    #[case::directory_complicated(vec![
+        IngestionEntry::Regular {
+            path: "blub/.keep".parse().unwrap(),
+            size: 0,
+            executable: false,
+            digest: EMPTY_BLOB_DIGEST.clone(),
+        },
+        IngestionEntry::Regular {
+            path: "blub/keep/.keep".parse().unwrap(),
+            size: 0,
+            executable: false,
+            digest: EMPTY_BLOB_DIGEST.clone(),
+        },
+        IngestionEntry::Dir {
+            path: "blub/keep".parse().unwrap(),
+        },
+        IngestionEntry::Symlink {
+            path: "blub/aa".parse().unwrap(),
+            target: b"/nix/store/somewhereelse".into(),
+        },
+        IngestionEntry::Dir {
+            path: "blub".parse().unwrap(),
+        },
+    ],
+        Node::Directory(DirectoryNode { name: "blub".into(), digest: DIRECTORY_COMPLICATED.digest().into(), size:DIRECTORY_COMPLICATED.size() })
+    )]
+    #[tokio::test]
+    async fn test_ingestion(#[case] entries: Vec<IngestionEntry>, #[case] exp_root_node: Node) {
+        let directory_service = MemoryDirectoryService::default();
+
+        let root_node = ingest_entries(
+            directory_service.clone(),
+            futures::stream::iter(entries.into_iter().map(Ok::<_, std::io::Error>)),
+        )
+        .await
+        .expect("must succeed");
+
+        assert_eq!(exp_root_node, root_node, "root node should match");
+    }
+
+    #[rstest]
+    #[should_panic]
+    #[case::empty_entries(vec![])]
+    #[should_panic]
+    #[case::missing_intermediate_dir(vec![
+        IngestionEntry::Regular {
+            path: "blub/.keep".parse().unwrap(),
+            size: 0,
+            executable: false,
+            digest: EMPTY_BLOB_DIGEST.clone(),
+        },
+    ])]
+    #[should_panic]
+    #[case::leaf_after_parent(vec![
+        IngestionEntry::Dir {
+            path: "blub".parse().unwrap(),
+        },
+        IngestionEntry::Regular {
+            path: "blub/.keep".parse().unwrap(),
+            size: 0,
+            executable: false,
+            digest: EMPTY_BLOB_DIGEST.clone(),
+        },
+    ])]
+    #[should_panic]
+    #[case::root_in_entry(vec![
+        IngestionEntry::Regular {
+            path: ".keep".parse().unwrap(),
+            size: 0,
+            executable: false,
+            digest: EMPTY_BLOB_DIGEST.clone(),
+        },
+        IngestionEntry::Dir {
+            path: "".parse().unwrap(),
+        },
+    ])]
+    #[tokio::test]
+    async fn test_ingestion_fail(#[case] entries: Vec<IngestionEntry>) {
+        let directory_service = MemoryDirectoryService::default();
+
+        let _ = ingest_entries(
+            directory_service.clone(),
+            futures::stream::iter(entries.into_iter().map(Ok::<_, std::io::Error>)),
+        )
+        .await;
+    }
+}
diff --git a/tvix/castore/src/lib.rs b/tvix/castore/src/lib.rs
new file mode 100644
index 0000000000..bdc533a8c5
--- /dev/null
+++ b/tvix/castore/src/lib.rs
@@ -0,0 +1,30 @@
+mod digests;
+mod errors;
+mod hashing_reader;
+
+pub mod blobservice;
+pub mod directoryservice;
+pub mod fixtures;
+
+#[cfg(feature = "fs")]
+pub mod fs;
+
+mod path;
+pub use path::{Path, PathBuf};
+
+pub mod import;
+pub mod proto;
+pub mod tonic;
+
+pub use digests::{B3Digest, B3_LEN};
+pub use errors::Error;
+pub use hashing_reader::{B3HashingReader, HashingReader};
+
+#[cfg(test)]
+mod tests;
+
+// That's what the rstest_reuse README asks us do, and fails about being unable
+// to find rstest_reuse in crate root.
+#[cfg(test)]
+#[allow(clippy::single_component_path_imports)]
+use rstest_reuse;
diff --git a/tvix/castore/src/path.rs b/tvix/castore/src/path.rs
new file mode 100644
index 0000000000..fcc2bd01fb
--- /dev/null
+++ b/tvix/castore/src/path.rs
@@ -0,0 +1,446 @@
+//! Contains data structures to deal with Paths in the tvix-castore model.
+
+use std::{
+    borrow::Borrow,
+    fmt::{self, Debug, Display},
+    mem,
+    ops::Deref,
+    str::FromStr,
+};
+
+use bstr::ByteSlice;
+
+use crate::proto::validate_node_name;
+
+/// Represents a Path in the castore model.
+/// These are always relative, and platform-independent, which distinguishes
+/// them from the ones provided in the standard library.
+#[derive(Eq, Hash, PartialEq)]
+#[repr(transparent)] // SAFETY: Representation has to match [u8]
+pub struct Path {
+    // As node names in the castore model cannot contain slashes,
+    // we use them as component separators here.
+    inner: [u8],
+}
+
+#[allow(dead_code)]
+impl Path {
+    // SAFETY: The empty path is valid.
+    pub const ROOT: &'static Path = unsafe { Path::from_bytes_unchecked(&[]) };
+
+    /// Convert a byte slice to a path, without checking validity.
+    const unsafe fn from_bytes_unchecked(bytes: &[u8]) -> &Path {
+        // SAFETY: &[u8] and &Path have the same representation.
+        unsafe { mem::transmute(bytes) }
+    }
+
+    fn from_bytes(bytes: &[u8]) -> Option<&Path> {
+        if !bytes.is_empty() {
+            // Ensure all components are valid castore node names.
+            for component in bytes.split_str(b"/") {
+                validate_node_name(component).ok()?;
+            }
+        }
+
+        // SAFETY: We have verified that the path contains no empty components.
+        Some(unsafe { Path::from_bytes_unchecked(bytes) })
+    }
+
+    pub fn into_boxed_bytes(self: Box<Path>) -> Box<[u8]> {
+        // SAFETY: Box<Path> and Box<[u8]> have the same representation.
+        unsafe { mem::transmute(self) }
+    }
+
+    /// Returns the path without its final component, if there is one.
+    ///
+    /// Note that the parent of a bare file name is [Path::ROOT].
+    /// [Path::ROOT] is the only path without a parent.
+    pub fn parent(&self) -> Option<&Path> {
+        // The root does not have a parent.
+        if self.inner.is_empty() {
+            return None;
+        }
+
+        Some(
+            if let Some((parent, _file_name)) = self.inner.rsplit_once_str(b"/") {
+                // SAFETY: The parent of a valid Path is a valid Path.
+                unsafe { Path::from_bytes_unchecked(parent) }
+            } else {
+                // The parent of a bare file name is the root.
+                Path::ROOT
+            },
+        )
+    }
+
+    /// Creates a PathBuf with `name` adjoined to self.
+    pub fn try_join(&self, name: &[u8]) -> Result<PathBuf, std::io::Error> {
+        let mut v = PathBuf::with_capacity(self.inner.len() + name.len() + 1);
+        v.inner.extend_from_slice(&self.inner);
+        v.try_push(name)?;
+
+        Ok(v)
+    }
+
+    /// Produces an iterator over the components of the path, which are
+    /// individual byte slices.
+    /// In case the path is empty, an empty iterator is returned.
+    pub fn components(&self) -> impl Iterator<Item = &[u8]> {
+        let mut iter = self.inner.split_str(&b"/");
+
+        // We don't want to return an empty element, consume it if it's the only one.
+        if self.inner.is_empty() {
+            let _ = iter.next();
+        }
+
+        iter
+    }
+
+    /// Returns the final component of the Path, if there is one.
+    pub fn file_name(&self) -> Option<&[u8]> {
+        self.components().last()
+    }
+
+    pub fn as_bytes(&self) -> &[u8] {
+        &self.inner
+    }
+}
+
+impl Debug for Path {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        Debug::fmt(self.inner.as_bstr(), f)
+    }
+}
+
+impl Display for Path {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        Display::fmt(self.inner.as_bstr(), f)
+    }
+}
+
+impl AsRef<Path> for Path {
+    fn as_ref(&self) -> &Path {
+        self
+    }
+}
+
+/// Represents a owned PathBuf in the castore model.
+/// These are always relative, and platform-independent, which distinguishes
+/// them from the ones provided in the standard library.
+#[derive(Clone, Default, Eq, Hash, PartialEq)]
+pub struct PathBuf {
+    inner: Vec<u8>,
+}
+
+impl Deref for PathBuf {
+    type Target = Path;
+
+    fn deref(&self) -> &Self::Target {
+        // SAFETY: PathBuf always contains a valid Path.
+        unsafe { Path::from_bytes_unchecked(&self.inner) }
+    }
+}
+
+impl AsRef<Path> for PathBuf {
+    fn as_ref(&self) -> &Path {
+        self
+    }
+}
+
+impl ToOwned for Path {
+    type Owned = PathBuf;
+
+    fn to_owned(&self) -> Self::Owned {
+        PathBuf {
+            inner: self.inner.to_owned(),
+        }
+    }
+}
+
+impl Borrow<Path> for PathBuf {
+    fn borrow(&self) -> &Path {
+        self
+    }
+}
+
+impl From<Box<Path>> for PathBuf {
+    fn from(value: Box<Path>) -> Self {
+        // SAFETY: Box<Path> is always a valid path.
+        unsafe { PathBuf::from_bytes_unchecked(value.into_boxed_bytes().into_vec()) }
+    }
+}
+
+impl From<&Path> for PathBuf {
+    fn from(value: &Path) -> Self {
+        value.to_owned()
+    }
+}
+
+impl FromStr for PathBuf {
+    type Err = std::io::Error;
+
+    fn from_str(s: &str) -> Result<PathBuf, Self::Err> {
+        Ok(Path::from_bytes(s.as_bytes())
+            .ok_or(std::io::ErrorKind::InvalidData)?
+            .to_owned())
+    }
+}
+
+impl Debug for PathBuf {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        Debug::fmt(&**self, f)
+    }
+}
+
+impl Display for PathBuf {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        Display::fmt(&**self, f)
+    }
+}
+
+impl PathBuf {
+    pub fn new() -> PathBuf {
+        Self::default()
+    }
+
+    pub fn with_capacity(capacity: usize) -> PathBuf {
+        // SAFETY: The empty path is a valid path.
+        Self {
+            inner: Vec::with_capacity(capacity),
+        }
+    }
+
+    /// Adjoins `name` to self.
+    pub fn try_push(&mut self, name: &[u8]) -> Result<(), std::io::Error> {
+        validate_node_name(name).map_err(|_| std::io::ErrorKind::InvalidData)?;
+
+        if !self.inner.is_empty() {
+            self.inner.push(b'/');
+        }
+
+        self.inner.extend_from_slice(name);
+
+        Ok(())
+    }
+
+    /// Convert a byte vector to a PathBuf, without checking validity.
+    unsafe fn from_bytes_unchecked(bytes: Vec<u8>) -> PathBuf {
+        PathBuf { inner: bytes }
+    }
+
+    /// Convert from a [&std::path::Path] to [Self].
+    ///
+    /// - Self uses `/` as path separator.
+    /// - Absolute paths are always rejected, are are these with custom prefixes.
+    /// - Repeated separators are deduplicated.
+    /// - Occurrences of `.` are normalized away.
+    /// - A trailing slash is normalized away.
+    ///
+    /// A `canonicalize_dotdot` boolean controls whether `..` will get
+    /// canonicalized if possible, or should return an error.
+    ///
+    /// For more exotic paths, this conversion might produce different results
+    /// on different platforms, due to different underlying byte
+    /// representations, which is why it's restricted to unix for now.
+    #[cfg(unix)]
+    pub fn from_host_path(
+        host_path: &std::path::Path,
+        canonicalize_dotdot: bool,
+    ) -> Result<Self, std::io::Error> {
+        let mut p = PathBuf::with_capacity(host_path.as_os_str().len());
+
+        for component in host_path.components() {
+            match component {
+                std::path::Component::Prefix(_) | std::path::Component::RootDir => {
+                    return Err(std::io::Error::new(
+                        std::io::ErrorKind::InvalidData,
+                        "found disallowed prefix or rootdir",
+                    ))
+                }
+                std::path::Component::CurDir => continue, // ignore
+                std::path::Component::ParentDir => {
+                    if canonicalize_dotdot {
+                        // Try popping the last element from the path being constructed.
+                        // FUTUREWORK: pop method?
+                        p = p
+                            .parent()
+                            .ok_or_else(|| {
+                                std::io::Error::new(
+                                    std::io::ErrorKind::InvalidData,
+                                    "found .. going too far up",
+                                )
+                            })?
+                            .to_owned();
+                    } else {
+                        return Err(std::io::Error::new(
+                            std::io::ErrorKind::InvalidData,
+                            "found disallowed ..",
+                        ));
+                    }
+                }
+                std::path::Component::Normal(s) => {
+                    // append the new component to the path being constructed.
+                    p.try_push(s.as_encoded_bytes()).map_err(|_| {
+                        std::io::Error::new(
+                            std::io::ErrorKind::InvalidData,
+                            "encountered invalid node in sub_path component",
+                        )
+                    })?
+                }
+            }
+        }
+
+        Ok(p)
+    }
+
+    pub fn into_boxed_path(self) -> Box<Path> {
+        // SAFETY: Box<[u8]> and Box<Path> have the same representation,
+        // and PathBuf always contains a valid Path.
+        unsafe { mem::transmute(self.inner.into_boxed_slice()) }
+    }
+
+    pub fn into_bytes(self) -> Vec<u8> {
+        self.inner
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::{Path, PathBuf};
+    use bstr::ByteSlice;
+    use rstest::rstest;
+
+    // TODO: add some manual tests including invalid UTF-8 (hard to express
+    // with rstest)
+
+    #[rstest]
+    #[case::empty("", 0)]
+    #[case("a", 1)]
+    #[case("a/b", 2)]
+    #[case("a/b/c", 3)]
+    // add two slightly more cursed variants.
+    // Technically nothing prevents us from representing this with castore,
+    // but maybe we want to disallow constructing paths like this as it's a
+    // bad idea.
+    #[case::cursed("C:\\a/b", 2)]
+    #[case::cursed("\\\\tvix-store", 1)]
+    pub fn from_str(#[case] s: &str, #[case] num_components: usize) {
+        let p: PathBuf = s.parse().expect("must parse");
+
+        assert_eq!(s.as_bytes(), p.as_bytes(), "inner bytes mismatch");
+        assert_eq!(
+            num_components,
+            p.components().count(),
+            "number of components mismatch"
+        );
+    }
+
+    #[rstest]
+    #[case::absolute("/a/b")]
+    #[case::two_forward_slashes_start("//a/b")]
+    #[case::two_forward_slashes_middle("a/b//c/d")]
+    #[case::trailing_slash("a/b/")]
+    #[case::dot(".")]
+    #[case::dotdot("..")]
+    #[case::dot_start("./a")]
+    #[case::dotdot_start("../a")]
+    #[case::dot_middle("a/./b")]
+    #[case::dotdot_middle("a/../b")]
+    #[case::dot_end("a/b/.")]
+    #[case::dotdot_end("a/b/..")]
+    #[case::null("fo\0o")]
+    pub fn from_str_fail(#[case] s: &str) {
+        s.parse::<PathBuf>().expect_err("must fail");
+    }
+
+    #[rstest]
+    #[case("foo", "")]
+    #[case("foo/bar", "foo")]
+    #[case("foo2/bar2", "foo2")]
+    #[case("foo/bar/baz", "foo/bar")]
+    pub fn parent(#[case] p: PathBuf, #[case] exp_parent: PathBuf) {
+        assert_eq!(Some(&*exp_parent), p.parent());
+    }
+
+    #[rstest]
+    pub fn no_parent() {
+        assert!(Path::ROOT.parent().is_none());
+    }
+
+    #[rstest]
+    #[case("a", "b", "a/b")]
+    #[case("a", "b", "a/b")]
+    pub fn join_push(#[case] mut p: PathBuf, #[case] name: &str, #[case] exp_p: PathBuf) {
+        assert_eq!(exp_p, p.try_join(name.as_bytes()).expect("join failed"));
+        p.try_push(name.as_bytes()).expect("push failed");
+        assert_eq!(exp_p, p);
+    }
+
+    #[rstest]
+    #[case("a", "/")]
+    #[case("a", "")]
+    #[case("a", "b/c")]
+    #[case("", "/")]
+    #[case("", "")]
+    #[case("", "b/c")]
+    #[case("", ".")]
+    #[case("", "..")]
+    pub fn join_push_fail(#[case] mut p: PathBuf, #[case] name: &str) {
+        p.try_join(name.as_bytes())
+            .expect_err("join succeeded unexpectedly");
+        p.try_push(name.as_bytes())
+            .expect_err("push succeeded unexpectedly");
+    }
+
+    #[rstest]
+    #[case::empty("", vec![])]
+    #[case("a", vec!["a"])]
+    #[case("a/b", vec!["a", "b"])]
+    #[case("a/b/c", vec!["a","b", "c"])]
+    pub fn components(#[case] p: PathBuf, #[case] exp_components: Vec<&str>) {
+        assert_eq!(
+            exp_components,
+            p.components()
+                .map(|x| x.to_str().unwrap())
+                .collect::<Vec<_>>()
+        );
+    }
+
+    #[rstest]
+    #[case::empty("", "", false)]
+    #[case::path("a", "a", false)]
+    #[case::path2("a/b", "a/b", false)]
+    #[case::double_slash_middle("a//b", "a/b", false)]
+    #[case::dot(".", "", false)]
+    #[case::dot_start("./a/b", "a/b", false)]
+    #[case::dot_middle("a/./b", "a/b", false)]
+    #[case::dot_end("a/b/.", "a/b", false)]
+    #[case::trailing_slash("a/b/", "a/b", false)]
+    #[case::dotdot_canonicalize("a/..", "", true)]
+    #[case::dotdot_canonicalize2("a/../b", "b", true)]
+    #[cfg_attr(unix, case::faux_prefix("\\\\nix-store", "\\\\nix-store", false))]
+    #[cfg_attr(unix, case::faux_letter("C:\\foo.txt", "C:\\foo.txt", false))]
+    pub fn from_host_path(
+        #[case] host_path: std::path::PathBuf,
+        #[case] exp_path: PathBuf,
+        #[case] canonicalize_dotdot: bool,
+    ) {
+        let p = PathBuf::from_host_path(&host_path, canonicalize_dotdot).expect("must succeed");
+
+        assert_eq!(exp_path, p);
+    }
+
+    #[rstest]
+    #[case::absolute("/", false)]
+    #[case::dotdot_root("..", false)]
+    #[case::dotdot_root_canonicalize("..", true)]
+    #[case::dotdot_root_no_canonicalize("a/..", false)]
+    #[case::invalid_name("foo/bar\0", false)]
+    // #[cfg_attr(windows, case::prefix("\\\\nix-store", false))]
+    // #[cfg_attr(windows, case::letter("C:\\foo.txt", false))]
+    pub fn from_host_path_fail(
+        #[case] host_path: std::path::PathBuf,
+        #[case] canonicalize_dotdot: bool,
+    ) {
+        PathBuf::from_host_path(&host_path, canonicalize_dotdot).expect_err("must fail");
+    }
+}
diff --git a/tvix/castore/src/proto/grpc_blobservice_wrapper.rs b/tvix/castore/src/proto/grpc_blobservice_wrapper.rs
new file mode 100644
index 0000000000..41bd0698ec
--- /dev/null
+++ b/tvix/castore/src/proto/grpc_blobservice_wrapper.rs
@@ -0,0 +1,175 @@
+use crate::blobservice::BlobService;
+use core::pin::pin;
+use data_encoding::BASE64;
+use futures::{stream::BoxStream, TryFutureExt};
+use std::{
+    collections::VecDeque,
+    ops::{Deref, DerefMut},
+};
+use tokio_stream::StreamExt;
+use tokio_util::io::ReaderStream;
+use tonic::{async_trait, Request, Response, Status, Streaming};
+use tracing::{instrument, warn};
+
+pub struct GRPCBlobServiceWrapper<T> {
+    blob_service: T,
+}
+
+impl<T> GRPCBlobServiceWrapper<T> {
+    pub fn new(blob_service: T) -> Self {
+        Self { blob_service }
+    }
+}
+
+// This is necessary because bytes::BytesMut comes up with
+// a default 64 bytes capacity that cannot be changed
+// easily if you assume a bytes::BufMut trait implementation
+// Therefore, we override the Default implementation here
+// TODO(raitobezarius?): upstream me properly
+struct BytesMutWithDefaultCapacity<const N: usize> {
+    inner: bytes::BytesMut,
+}
+
+impl<const N: usize> Deref for BytesMutWithDefaultCapacity<N> {
+    type Target = bytes::BytesMut;
+    fn deref(&self) -> &Self::Target {
+        &self.inner
+    }
+}
+
+impl<const N: usize> DerefMut for BytesMutWithDefaultCapacity<N> {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.inner
+    }
+}
+
+impl<const N: usize> Default for BytesMutWithDefaultCapacity<N> {
+    fn default() -> Self {
+        BytesMutWithDefaultCapacity {
+            inner: bytes::BytesMut::with_capacity(N),
+        }
+    }
+}
+
+impl<const N: usize> bytes::Buf for BytesMutWithDefaultCapacity<N> {
+    fn remaining(&self) -> usize {
+        self.inner.remaining()
+    }
+
+    fn chunk(&self) -> &[u8] {
+        self.inner.chunk()
+    }
+
+    fn advance(&mut self, cnt: usize) {
+        self.inner.advance(cnt);
+    }
+}
+
+unsafe impl<const N: usize> bytes::BufMut for BytesMutWithDefaultCapacity<N> {
+    fn remaining_mut(&self) -> usize {
+        self.inner.remaining_mut()
+    }
+
+    unsafe fn advance_mut(&mut self, cnt: usize) {
+        self.inner.advance_mut(cnt);
+    }
+
+    fn chunk_mut(&mut self) -> &mut bytes::buf::UninitSlice {
+        self.inner.chunk_mut()
+    }
+}
+
+#[async_trait]
+impl<T> super::blob_service_server::BlobService for GRPCBlobServiceWrapper<T>
+where
+    T: Deref<Target = dyn BlobService> + Send + Sync + 'static,
+{
+    // https://github.com/tokio-rs/tokio/issues/2723#issuecomment-1534723933
+    type ReadStream = BoxStream<'static, Result<super::BlobChunk, Status>>;
+
+    #[instrument(skip_all, fields(blob.digest=format!("b3:{}", BASE64.encode(&request.get_ref().digest))))]
+    async fn stat(
+        &self,
+        request: Request<super::StatBlobRequest>,
+    ) -> Result<Response<super::StatBlobResponse>, Status> {
+        let rq = request.into_inner();
+        let req_digest = rq
+            .digest
+            .try_into()
+            .map_err(|_e| Status::invalid_argument("invalid digest length"))?;
+
+        match self.blob_service.chunks(&req_digest).await {
+            Ok(None) => Err(Status::not_found(format!("blob {} not found", &req_digest))),
+            Ok(Some(chunk_metas)) => Ok(Response::new(super::StatBlobResponse {
+                chunks: chunk_metas,
+                ..Default::default()
+            })),
+            Err(e) => {
+                warn!(err=%e, "failed to request chunks");
+                Err(e.into())
+            }
+        }
+    }
+
+    #[instrument(skip_all, fields(blob.digest=format!("b3:{}", BASE64.encode(&request.get_ref().digest))))]
+    async fn read(
+        &self,
+        request: Request<super::ReadBlobRequest>,
+    ) -> Result<Response<Self::ReadStream>, Status> {
+        let rq = request.into_inner();
+
+        let req_digest = rq
+            .digest
+            .try_into()
+            .map_err(|_e| Status::invalid_argument("invalid digest length"))?;
+
+        match self.blob_service.open_read(&req_digest).await {
+            Ok(Some(r)) => {
+                let chunks_stream =
+                    ReaderStream::new(r).map(|chunk| Ok(super::BlobChunk { data: chunk? }));
+                Ok(Response::new(Box::pin(chunks_stream)))
+            }
+            Ok(None) => Err(Status::not_found(format!("blob {} not found", &req_digest))),
+            Err(e) => {
+                warn!(err=%e, "failed to call open_read");
+                Err(e.into())
+            }
+        }
+    }
+
+    #[instrument(skip_all)]
+    async fn put(
+        &self,
+        request: Request<Streaming<super::BlobChunk>>,
+    ) -> Result<Response<super::PutBlobResponse>, Status> {
+        let req_inner = request.into_inner();
+
+        let data_stream = req_inner.map(|x| {
+            x.map(|x| VecDeque::from(x.data.to_vec()))
+                .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))
+        });
+
+        let mut data_reader = tokio_util::io::StreamReader::new(data_stream);
+
+        let mut blob_writer = pin!(self.blob_service.open_write().await);
+
+        tokio::io::copy(&mut data_reader, &mut blob_writer)
+            .await
+            .map_err(|e| {
+                warn!("error copying: {}", e);
+                Status::internal("error copying")
+            })?;
+
+        let digest = blob_writer
+            .close()
+            .map_err(|e| {
+                warn!("error closing stream: {}", e);
+                Status::internal("error closing stream")
+            })
+            .await?;
+
+        Ok(Response::new(super::PutBlobResponse {
+            digest: digest.into(),
+        }))
+    }
+}
diff --git a/tvix/castore/src/proto/grpc_directoryservice_wrapper.rs b/tvix/castore/src/proto/grpc_directoryservice_wrapper.rs
new file mode 100644
index 0000000000..7d741a3f07
--- /dev/null
+++ b/tvix/castore/src/proto/grpc_directoryservice_wrapper.rs
@@ -0,0 +1,114 @@
+use crate::directoryservice::ClosureValidator;
+use crate::proto;
+use crate::{directoryservice::DirectoryService, B3Digest};
+use futures::StreamExt;
+use std::ops::Deref;
+use tokio::sync::mpsc::channel;
+use tokio_stream::wrappers::ReceiverStream;
+use tonic::{async_trait, Request, Response, Status, Streaming};
+use tracing::{debug, instrument, warn};
+
+pub struct GRPCDirectoryServiceWrapper<T> {
+    directory_service: T,
+}
+
+impl<T> GRPCDirectoryServiceWrapper<T> {
+    pub fn new(directory_service: T) -> Self {
+        Self { directory_service }
+    }
+}
+
+#[async_trait]
+impl<T> proto::directory_service_server::DirectoryService for GRPCDirectoryServiceWrapper<T>
+where
+    T: Deref<Target = dyn DirectoryService> + Send + Sync + 'static,
+{
+    type GetStream = ReceiverStream<tonic::Result<proto::Directory, Status>>;
+
+    #[instrument(skip_all)]
+    async fn get(
+        &self,
+        request: Request<proto::GetDirectoryRequest>,
+    ) -> Result<Response<Self::GetStream>, Status> {
+        let (tx, rx) = channel(5);
+
+        let req_inner = request.into_inner();
+
+        // look at the digest in the request and put it in the top of the queue.
+        match &req_inner.by_what {
+            None => return Err(Status::invalid_argument("by_what needs to be specified")),
+            Some(proto::get_directory_request::ByWhat::Digest(ref digest)) => {
+                let digest: B3Digest = digest
+                    .clone()
+                    .try_into()
+                    .map_err(|_e| Status::invalid_argument("invalid digest length"))?;
+
+                if !req_inner.recursive {
+                    let e: Result<proto::Directory, Status> = match self
+                        .directory_service
+                        .get(&digest)
+                        .await
+                    {
+                        Ok(Some(directory)) => Ok(directory),
+                        Ok(None) => {
+                            Err(Status::not_found(format!("directory {} not found", digest)))
+                        }
+                        Err(e) => {
+                            warn!(err = %e, directory.digest=%digest, "failed to get directory");
+                            Err(e.into())
+                        }
+                    };
+
+                    if tx.send(e).await.is_err() {
+                        debug!("receiver dropped");
+                    }
+                } else {
+                    // If recursive was requested, traverse via get_recursive.
+                    let mut directories_it = self.directory_service.get_recursive(&digest);
+
+                    while let Some(e) = directories_it.next().await {
+                        // map err in res from Error to Status
+                        let res = e.map_err(|e| Status::internal(e.to_string()));
+                        if tx.send(res).await.is_err() {
+                            debug!("receiver dropped");
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+
+        let receiver_stream = ReceiverStream::new(rx);
+        Ok(Response::new(receiver_stream))
+    }
+
+    #[instrument(skip_all)]
+    async fn put(
+        &self,
+        request: Request<Streaming<proto::Directory>>,
+    ) -> Result<Response<proto::PutDirectoryResponse>, Status> {
+        let mut req_inner = request.into_inner();
+
+        // We put all Directory messages we receive into ClosureValidator first.
+        let mut validator = ClosureValidator::default();
+        while let Some(directory) = req_inner.message().await? {
+            validator.add(directory)?;
+        }
+
+        // drain, which validates connectivity too.
+        let directories = validator.finalize()?;
+
+        let mut directory_putter = self.directory_service.put_multiple_start();
+        for directory in directories {
+            directory_putter.put(directory).await?;
+        }
+
+        // Properly close the directory putter. Peek at last_directory_digest
+        // and return it, or propagate errors.
+        let last_directory_dgst = directory_putter.close().await?;
+
+        Ok(Response::new(proto::PutDirectoryResponse {
+            root_digest: last_directory_dgst.into(),
+        }))
+    }
+}
diff --git a/tvix/castore/src/proto/mod.rs b/tvix/castore/src/proto/mod.rs
new file mode 100644
index 0000000000..5374e3ae5a
--- /dev/null
+++ b/tvix/castore/src/proto/mod.rs
@@ -0,0 +1,471 @@
+#![allow(non_snake_case)]
+// https://github.com/hyperium/tonic/issues/1056
+use bstr::ByteSlice;
+use std::{collections::HashSet, iter::Peekable, str};
+
+use prost::Message;
+
+mod grpc_blobservice_wrapper;
+mod grpc_directoryservice_wrapper;
+
+pub use grpc_blobservice_wrapper::GRPCBlobServiceWrapper;
+pub use grpc_directoryservice_wrapper::GRPCDirectoryServiceWrapper;
+
+use crate::{B3Digest, B3_LEN};
+
+tonic::include_proto!("tvix.castore.v1");
+
+#[cfg(feature = "tonic-reflection")]
+/// Compiled file descriptors for implementing [gRPC
+/// reflection](https://github.com/grpc/grpc/blob/master/doc/server-reflection.md) with e.g.
+/// [`tonic_reflection`](https://docs.rs/tonic-reflection).
+pub const FILE_DESCRIPTOR_SET: &[u8] = tonic::include_file_descriptor_set!("tvix.castore.v1");
+
+#[cfg(test)]
+mod tests;
+
+/// Errors that can occur during the validation of [Directory] messages.
+#[derive(Debug, PartialEq, Eq, thiserror::Error)]
+pub enum ValidateDirectoryError {
+    /// Elements are not in sorted order
+    #[error("{:?} is not sorted", .0.as_bstr())]
+    WrongSorting(Vec<u8>),
+    /// Multiple elements with the same name encountered
+    #[error("{:?} is a duplicate name", .0.as_bstr())]
+    DuplicateName(Vec<u8>),
+    /// Invalid node
+    #[error("invalid node with name {:?}: {:?}", .0.as_bstr(), .1.to_string())]
+    InvalidNode(Vec<u8>, ValidateNodeError),
+    #[error("Total size exceeds u32::MAX")]
+    SizeOverflow,
+}
+
+/// Errors that occur during Node validation
+#[derive(Debug, PartialEq, Eq, thiserror::Error)]
+pub enum ValidateNodeError {
+    #[error("No node set")]
+    NoNodeSet,
+    /// Invalid digest length encountered
+    #[error("Invalid Digest length: {0}")]
+    InvalidDigestLen(usize),
+    /// Invalid name encountered
+    #[error("Invalid name: {}", .0.as_bstr())]
+    InvalidName(Vec<u8>),
+    /// Invalid symlink target
+    #[error("Invalid symlink target: {}", .0.as_bstr())]
+    InvalidSymlinkTarget(Vec<u8>),
+}
+
+/// Errors that occur during StatBlobResponse validation
+#[derive(Debug, PartialEq, Eq, thiserror::Error)]
+pub enum ValidateStatBlobResponseError {
+    /// Invalid digest length encountered
+    #[error("Invalid digest length {0} for chunk #{1}")]
+    InvalidDigestLen(usize, usize),
+}
+
+/// Checks a Node name for validity as an intermediate node.
+/// We disallow slashes, null bytes, '.', '..' and the empty string.
+pub(crate) fn validate_node_name(name: &[u8]) -> Result<(), ValidateNodeError> {
+    if name.is_empty()
+        || name == b".."
+        || name == b"."
+        || name.contains(&0x00)
+        || name.contains(&b'/')
+    {
+        Err(ValidateNodeError::InvalidName(name.to_owned()))
+    } else {
+        Ok(())
+    }
+}
+
+/// NamedNode is implemented for [FileNode], [DirectoryNode] and [SymlinkNode]
+/// and [node::Node], so we can ask all of them for the name easily.
+pub trait NamedNode {
+    fn get_name(&self) -> &[u8];
+}
+
+impl NamedNode for &FileNode {
+    fn get_name(&self) -> &[u8] {
+        &self.name
+    }
+}
+
+impl NamedNode for &DirectoryNode {
+    fn get_name(&self) -> &[u8] {
+        &self.name
+    }
+}
+
+impl NamedNode for &SymlinkNode {
+    fn get_name(&self) -> &[u8] {
+        &self.name
+    }
+}
+
+impl NamedNode for node::Node {
+    fn get_name(&self) -> &[u8] {
+        match self {
+            node::Node::File(node_file) => &node_file.name,
+            node::Node::Directory(node_directory) => &node_directory.name,
+            node::Node::Symlink(node_symlink) => &node_symlink.name,
+        }
+    }
+}
+
+impl Node {
+    /// Ensures the node has a valid enum kind (is Some), and passes its
+    // per-enum validation.
+    pub fn validate(&self) -> Result<(), ValidateNodeError> {
+        if let Some(node) = self.node.as_ref() {
+            node.validate()
+        } else {
+            Err(ValidateNodeError::NoNodeSet)
+        }
+    }
+}
+
+impl node::Node {
+    /// Returns the node with a new name.
+    pub fn rename(self, name: bytes::Bytes) -> Self {
+        match self {
+            node::Node::Directory(n) => node::Node::Directory(DirectoryNode { name, ..n }),
+            node::Node::File(n) => node::Node::File(FileNode { name, ..n }),
+            node::Node::Symlink(n) => node::Node::Symlink(SymlinkNode { name, ..n }),
+        }
+    }
+
+    /// Ensures the node has a valid name, and checks the type-specific fields too.
+    pub fn validate(&self) -> Result<(), ValidateNodeError> {
+        match self {
+            // for a directory root node, ensure the digest has the appropriate size.
+            node::Node::Directory(directory_node) => {
+                if directory_node.digest.len() != B3_LEN {
+                    Err(ValidateNodeError::InvalidDigestLen(
+                        directory_node.digest.len(),
+                    ))?;
+                }
+                validate_node_name(&directory_node.name)
+            }
+            // for a file root node, ensure the digest has the appropriate size.
+            node::Node::File(file_node) => {
+                if file_node.digest.len() != B3_LEN {
+                    Err(ValidateNodeError::InvalidDigestLen(file_node.digest.len()))?;
+                }
+                validate_node_name(&file_node.name)
+            }
+            // ensure the symlink target is not empty and doesn't contain null bytes.
+            node::Node::Symlink(symlink_node) => {
+                if symlink_node.target.is_empty() || symlink_node.target.contains(&b'\0') {
+                    Err(ValidateNodeError::InvalidSymlinkTarget(
+                        symlink_node.target.to_vec(),
+                    ))?;
+                }
+                validate_node_name(&symlink_node.name)
+            }
+        }
+    }
+}
+
+impl PartialOrd for node::Node {
+    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+        Some(self.cmp(other))
+    }
+}
+
+impl Ord for node::Node {
+    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+        self.get_name().cmp(other.get_name())
+    }
+}
+
+impl PartialOrd for FileNode {
+    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+        Some(self.cmp(other))
+    }
+}
+
+impl Ord for FileNode {
+    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+        self.get_name().cmp(other.get_name())
+    }
+}
+
+impl PartialOrd for SymlinkNode {
+    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+        Some(self.cmp(other))
+    }
+}
+
+impl Ord for SymlinkNode {
+    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+        self.get_name().cmp(other.get_name())
+    }
+}
+
+impl PartialOrd for DirectoryNode {
+    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+        Some(self.cmp(other))
+    }
+}
+
+impl Ord for DirectoryNode {
+    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+        self.get_name().cmp(other.get_name())
+    }
+}
+
+/// Accepts a name, and a mutable reference to the previous name.
+/// If the passed name is larger than the previous one, the reference is updated.
+/// If it's not, an error is returned.
+fn update_if_lt_prev<'n>(
+    prev_name: &mut &'n [u8],
+    name: &'n [u8],
+) -> Result<(), ValidateDirectoryError> {
+    if *name < **prev_name {
+        return Err(ValidateDirectoryError::WrongSorting(name.to_vec()));
+    }
+    *prev_name = name;
+    Ok(())
+}
+
+/// Inserts the given name into a HashSet if it's not already in there.
+/// If it is, an error is returned.
+fn insert_once<'n>(
+    seen_names: &mut HashSet<&'n [u8]>,
+    name: &'n [u8],
+) -> Result<(), ValidateDirectoryError> {
+    if seen_names.get(name).is_some() {
+        return Err(ValidateDirectoryError::DuplicateName(name.to_vec()));
+    }
+    seen_names.insert(name);
+    Ok(())
+}
+
+fn checked_sum(iter: impl IntoIterator<Item = u64>) -> Option<u64> {
+    iter.into_iter().try_fold(0u64, |acc, i| acc.checked_add(i))
+}
+
+impl Directory {
+    /// The size of a directory is the number of all regular and symlink elements,
+    /// the number of directory elements, and their size fields.
+    pub fn size(&self) -> u64 {
+        if cfg!(debug_assertions) {
+            self.size_checked()
+                .expect("Directory::size exceeds u64::MAX")
+        } else {
+            self.size_checked().unwrap_or(u64::MAX)
+        }
+    }
+
+    fn size_checked(&self) -> Option<u64> {
+        checked_sum([
+            self.files.len().try_into().ok()?,
+            self.symlinks.len().try_into().ok()?,
+            self.directories.len().try_into().ok()?,
+            checked_sum(self.directories.iter().map(|e| e.size))?,
+        ])
+    }
+
+    /// Calculates the digest of a Directory, which is the blake3 hash of a
+    /// Directory protobuf message, serialized in protobuf canonical form.
+    pub fn digest(&self) -> B3Digest {
+        let mut hasher = blake3::Hasher::new();
+
+        hasher
+            .update(&self.encode_to_vec())
+            .finalize()
+            .as_bytes()
+            .into()
+    }
+
+    /// validate checks the directory for invalid data, such as:
+    /// - violations of name restrictions
+    /// - invalid digest lengths
+    /// - not properly sorted lists
+    /// - duplicate names in the three lists
+    pub fn validate(&self) -> Result<(), ValidateDirectoryError> {
+        let mut seen_names: HashSet<&[u8]> = HashSet::new();
+
+        let mut last_directory_name: &[u8] = b"";
+        let mut last_file_name: &[u8] = b"";
+        let mut last_symlink_name: &[u8] = b"";
+
+        // check directories
+        for directory_node in &self.directories {
+            node::Node::Directory(directory_node.clone())
+                .validate()
+                .map_err(|e| {
+                    ValidateDirectoryError::InvalidNode(directory_node.name.to_vec(), e)
+                })?;
+
+            update_if_lt_prev(&mut last_directory_name, &directory_node.name)?;
+            insert_once(&mut seen_names, &directory_node.name)?;
+        }
+
+        // check files
+        for file_node in &self.files {
+            node::Node::File(file_node.clone())
+                .validate()
+                .map_err(|e| ValidateDirectoryError::InvalidNode(file_node.name.to_vec(), e))?;
+
+            update_if_lt_prev(&mut last_file_name, &file_node.name)?;
+            insert_once(&mut seen_names, &file_node.name)?;
+        }
+
+        // check symlinks
+        for symlink_node in &self.symlinks {
+            node::Node::Symlink(symlink_node.clone())
+                .validate()
+                .map_err(|e| ValidateDirectoryError::InvalidNode(symlink_node.name.to_vec(), e))?;
+
+            update_if_lt_prev(&mut last_symlink_name, &symlink_node.name)?;
+            insert_once(&mut seen_names, &symlink_node.name)?;
+        }
+
+        self.size_checked()
+            .ok_or(ValidateDirectoryError::SizeOverflow)?;
+
+        Ok(())
+    }
+
+    /// Allows iterating over all three nodes ([DirectoryNode], [FileNode],
+    /// [SymlinkNode]) in an ordered fashion, as long as the individual lists
+    /// are sorted (which can be checked by the [Directory::validate]).
+    pub fn nodes(&self) -> DirectoryNodesIterator {
+        return DirectoryNodesIterator {
+            i_directories: self.directories.iter().peekable(),
+            i_files: self.files.iter().peekable(),
+            i_symlinks: self.symlinks.iter().peekable(),
+        };
+    }
+
+    /// Adds the specified [node::Node] to the [Directory], preserving sorted entries.
+    /// This assumes the [Directory] to be sorted prior to adding the node.
+    ///
+    /// Inserting an element that already exists with the same name in the directory is not
+    /// supported.
+    pub fn add(&mut self, node: node::Node) {
+        debug_assert!(
+            !self.files.iter().any(|x| x.get_name() == node.get_name()),
+            "name already exists in files"
+        );
+        debug_assert!(
+            !self
+                .directories
+                .iter()
+                .any(|x| x.get_name() == node.get_name()),
+            "name already exists in directories"
+        );
+        debug_assert!(
+            !self
+                .symlinks
+                .iter()
+                .any(|x| x.get_name() == node.get_name()),
+            "name already exists in symlinks"
+        );
+
+        match node {
+            node::Node::File(node) => {
+                let pos = self
+                    .files
+                    .binary_search(&node)
+                    .expect_err("Tvix bug: dir entry with name already exists");
+                self.files.insert(pos, node);
+            }
+            node::Node::Directory(node) => {
+                let pos = self
+                    .directories
+                    .binary_search(&node)
+                    .expect_err("Tvix bug: dir entry with name already exists");
+                self.directories.insert(pos, node);
+            }
+            node::Node::Symlink(node) => {
+                let pos = self
+                    .symlinks
+                    .binary_search(&node)
+                    .expect_err("Tvix bug: dir entry with name already exists");
+                self.symlinks.insert(pos, node);
+            }
+        }
+    }
+}
+
+impl StatBlobResponse {
+    /// Validates a StatBlobResponse. All chunks must have valid blake3 digests.
+    /// It is allowed to send an empty list, if no more granular chunking is
+    /// available.
+    pub fn validate(&self) -> Result<(), ValidateStatBlobResponseError> {
+        for (i, chunk) in self.chunks.iter().enumerate() {
+            if chunk.digest.len() != blake3::KEY_LEN {
+                return Err(ValidateStatBlobResponseError::InvalidDigestLen(
+                    chunk.digest.len(),
+                    i,
+                ));
+            }
+        }
+        Ok(())
+    }
+}
+
+/// Struct to hold the state of an iterator over all nodes of a Directory.
+///
+/// Internally, this keeps peekable Iterators over all three lists of a
+/// Directory message.
+pub struct DirectoryNodesIterator<'a> {
+    // directory: &Directory,
+    i_directories: Peekable<std::slice::Iter<'a, DirectoryNode>>,
+    i_files: Peekable<std::slice::Iter<'a, FileNode>>,
+    i_symlinks: Peekable<std::slice::Iter<'a, SymlinkNode>>,
+}
+
+/// looks at two elements implementing NamedNode, and returns true if "left
+/// is smaller / comes first".
+///
+/// Some(_) is preferred over None.
+fn left_name_lt_right<A: NamedNode, B: NamedNode>(left: Option<&A>, right: Option<&B>) -> bool {
+    match left {
+        // if left is None, right always wins
+        None => false,
+        Some(left_inner) => {
+            // left is Some.
+            match right {
+                // left is Some, right is None - left wins.
+                None => true,
+                Some(right_inner) => {
+                    // both are Some - compare the name.
+                    return left_inner.get_name() < right_inner.get_name();
+                }
+            }
+        }
+    }
+}
+
+impl Iterator for DirectoryNodesIterator<'_> {
+    type Item = node::Node;
+
+    // next returns the next node in the Directory.
+    // we peek at all three internal iterators, and pick the one with the
+    // smallest name, to ensure lexicographical ordering.
+    // The individual lists are already known to be sorted.
+    fn next(&mut self) -> Option<Self::Item> {
+        if left_name_lt_right(self.i_directories.peek(), self.i_files.peek()) {
+            // i_directories is still in the game, compare with symlinks
+            if left_name_lt_right(self.i_directories.peek(), self.i_symlinks.peek()) {
+                self.i_directories
+                    .next()
+                    .cloned()
+                    .map(node::Node::Directory)
+            } else {
+                self.i_symlinks.next().cloned().map(node::Node::Symlink)
+            }
+        } else {
+            // i_files is still in the game, compare with symlinks
+            if left_name_lt_right(self.i_files.peek(), self.i_symlinks.peek()) {
+                self.i_files.next().cloned().map(node::Node::File)
+            } else {
+                self.i_symlinks.next().cloned().map(node::Node::Symlink)
+            }
+        }
+    }
+}
diff --git a/tvix/castore/src/proto/tests/directory.rs b/tvix/castore/src/proto/tests/directory.rs
new file mode 100644
index 0000000000..81b73a048d
--- /dev/null
+++ b/tvix/castore/src/proto/tests/directory.rs
@@ -0,0 +1,452 @@
+use crate::proto::{
+    node, Directory, DirectoryNode, FileNode, SymlinkNode, ValidateDirectoryError,
+    ValidateNodeError,
+};
+
+use hex_literal::hex;
+
+const DUMMY_DIGEST: [u8; 32] = [0; 32];
+
+#[test]
+fn size() {
+    {
+        let d = Directory::default();
+        assert_eq!(d.size(), 0);
+    }
+    {
+        let d = Directory {
+            directories: vec![DirectoryNode {
+                name: "foo".into(),
+                digest: DUMMY_DIGEST.to_vec().into(),
+                size: 0,
+            }],
+            ..Default::default()
+        };
+        assert_eq!(d.size(), 1);
+    }
+    {
+        let d = Directory {
+            directories: vec![DirectoryNode {
+                name: "foo".into(),
+                digest: DUMMY_DIGEST.to_vec().into(),
+                size: 4,
+            }],
+            ..Default::default()
+        };
+        assert_eq!(d.size(), 5);
+    }
+    {
+        let d = Directory {
+            files: vec![FileNode {
+                name: "foo".into(),
+                digest: DUMMY_DIGEST.to_vec().into(),
+                size: 42,
+                executable: false,
+            }],
+            ..Default::default()
+        };
+        assert_eq!(d.size(), 1);
+    }
+    {
+        let d = Directory {
+            symlinks: vec![SymlinkNode {
+                name: "foo".into(),
+                target: "bar".into(),
+            }],
+            ..Default::default()
+        };
+        assert_eq!(d.size(), 1);
+    }
+}
+
+#[test]
+#[cfg_attr(not(debug_assertions), ignore)]
+#[should_panic = "Directory::size exceeds u64::MAX"]
+fn size_unchecked_panic() {
+    let d = Directory {
+        directories: vec![DirectoryNode {
+            name: "foo".into(),
+            digest: DUMMY_DIGEST.to_vec().into(),
+            size: u64::MAX,
+        }],
+        ..Default::default()
+    };
+
+    d.size();
+}
+
+#[test]
+#[cfg_attr(debug_assertions, ignore)]
+fn size_unchecked_saturate() {
+    let d = Directory {
+        directories: vec![DirectoryNode {
+            name: "foo".into(),
+            digest: DUMMY_DIGEST.to_vec().into(),
+            size: u64::MAX,
+        }],
+        ..Default::default()
+    };
+
+    assert_eq!(d.size(), u64::MAX);
+}
+
+#[test]
+fn size_checked() {
+    // We don't test the overflow cases that rely purely on immediate
+    // child count, since that would take an absurd amount of memory.
+    {
+        let d = Directory {
+            directories: vec![DirectoryNode {
+                name: "foo".into(),
+                digest: DUMMY_DIGEST.to_vec().into(),
+                size: u64::MAX - 1,
+            }],
+            ..Default::default()
+        };
+        assert_eq!(d.size_checked(), Some(u64::MAX));
+    }
+    {
+        let d = Directory {
+            directories: vec![DirectoryNode {
+                name: "foo".into(),
+                digest: DUMMY_DIGEST.to_vec().into(),
+                size: u64::MAX,
+            }],
+            ..Default::default()
+        };
+        assert_eq!(d.size_checked(), None);
+    }
+    {
+        let d = Directory {
+            directories: vec![
+                DirectoryNode {
+                    name: "foo".into(),
+                    digest: DUMMY_DIGEST.to_vec().into(),
+                    size: u64::MAX / 2,
+                },
+                DirectoryNode {
+                    name: "foo".into(),
+                    digest: DUMMY_DIGEST.to_vec().into(),
+                    size: u64::MAX / 2,
+                },
+            ],
+            ..Default::default()
+        };
+        assert_eq!(d.size_checked(), None);
+    }
+}
+
+#[test]
+fn digest() {
+    let d = Directory::default();
+
+    assert_eq!(
+        d.digest(),
+        (&hex!("af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262")).into()
+    )
+}
+
+#[test]
+fn validate_empty() {
+    let d = Directory::default();
+    assert_eq!(d.validate(), Ok(()));
+}
+
+#[test]
+fn validate_invalid_names() {
+    {
+        let d = Directory {
+            directories: vec![DirectoryNode {
+                name: "".into(),
+                digest: DUMMY_DIGEST.to_vec().into(),
+                size: 42,
+            }],
+            ..Default::default()
+        };
+        match d.validate().expect_err("must fail") {
+            ValidateDirectoryError::InvalidNode(n, ValidateNodeError::InvalidName(_)) => {
+                assert_eq!(n, b"")
+            }
+            _ => panic!("unexpected error"),
+        };
+    }
+
+    {
+        let d = Directory {
+            directories: vec![DirectoryNode {
+                name: ".".into(),
+                digest: DUMMY_DIGEST.to_vec().into(),
+                size: 42,
+            }],
+            ..Default::default()
+        };
+        match d.validate().expect_err("must fail") {
+            ValidateDirectoryError::InvalidNode(n, ValidateNodeError::InvalidName(_)) => {
+                assert_eq!(n, b".")
+            }
+            _ => panic!("unexpected error"),
+        };
+    }
+
+    {
+        let d = Directory {
+            files: vec![FileNode {
+                name: "..".into(),
+                digest: DUMMY_DIGEST.to_vec().into(),
+                size: 42,
+                executable: false,
+            }],
+            ..Default::default()
+        };
+        match d.validate().expect_err("must fail") {
+            ValidateDirectoryError::InvalidNode(n, ValidateNodeError::InvalidName(_)) => {
+                assert_eq!(n, b"..")
+            }
+            _ => panic!("unexpected error"),
+        };
+    }
+
+    {
+        let d = Directory {
+            symlinks: vec![SymlinkNode {
+                name: "\x00".into(),
+                target: "foo".into(),
+            }],
+            ..Default::default()
+        };
+        match d.validate().expect_err("must fail") {
+            ValidateDirectoryError::InvalidNode(n, ValidateNodeError::InvalidName(_)) => {
+                assert_eq!(n, b"\x00")
+            }
+            _ => panic!("unexpected error"),
+        };
+    }
+
+    {
+        let d = Directory {
+            symlinks: vec![SymlinkNode {
+                name: "foo/bar".into(),
+                target: "foo".into(),
+            }],
+            ..Default::default()
+        };
+        match d.validate().expect_err("must fail") {
+            ValidateDirectoryError::InvalidNode(n, ValidateNodeError::InvalidName(_)) => {
+                assert_eq!(n, b"foo/bar")
+            }
+            _ => panic!("unexpected error"),
+        };
+    }
+}
+
+#[test]
+fn validate_invalid_digest() {
+    let d = Directory {
+        directories: vec![DirectoryNode {
+            name: "foo".into(),
+            digest: vec![0x00, 0x42].into(), // invalid length
+            size: 42,
+        }],
+        ..Default::default()
+    };
+    match d.validate().expect_err("must fail") {
+        ValidateDirectoryError::InvalidNode(_, ValidateNodeError::InvalidDigestLen(n)) => {
+            assert_eq!(n, 2)
+        }
+        _ => panic!("unexpected error"),
+    }
+}
+
+#[test]
+fn validate_sorting() {
+    // "b" comes before "a", bad.
+    {
+        let d = Directory {
+            directories: vec![
+                DirectoryNode {
+                    name: "b".into(),
+                    digest: DUMMY_DIGEST.to_vec().into(),
+                    size: 42,
+                },
+                DirectoryNode {
+                    name: "a".into(),
+                    digest: DUMMY_DIGEST.to_vec().into(),
+                    size: 42,
+                },
+            ],
+            ..Default::default()
+        };
+        match d.validate().expect_err("must fail") {
+            ValidateDirectoryError::WrongSorting(s) => {
+                assert_eq!(s, b"a");
+            }
+            _ => panic!("unexpected error"),
+        }
+    }
+
+    // "a" exists twice, bad.
+    {
+        let d = Directory {
+            directories: vec![
+                DirectoryNode {
+                    name: "a".into(),
+                    digest: DUMMY_DIGEST.to_vec().into(),
+                    size: 42,
+                },
+                DirectoryNode {
+                    name: "a".into(),
+                    digest: DUMMY_DIGEST.to_vec().into(),
+                    size: 42,
+                },
+            ],
+            ..Default::default()
+        };
+        match d.validate().expect_err("must fail") {
+            ValidateDirectoryError::DuplicateName(s) => {
+                assert_eq!(s, b"a");
+            }
+            _ => panic!("unexpected error"),
+        }
+    }
+
+    // "a" comes before "b", all good.
+    {
+        let d = Directory {
+            directories: vec![
+                DirectoryNode {
+                    name: "a".into(),
+                    digest: DUMMY_DIGEST.to_vec().into(),
+                    size: 42,
+                },
+                DirectoryNode {
+                    name: "b".into(),
+                    digest: DUMMY_DIGEST.to_vec().into(),
+                    size: 42,
+                },
+            ],
+            ..Default::default()
+        };
+
+        d.validate().expect("validate shouldn't error");
+    }
+
+    // [b, c] and [a] are both properly sorted.
+    {
+        let d = Directory {
+            directories: vec![
+                DirectoryNode {
+                    name: "b".into(),
+                    digest: DUMMY_DIGEST.to_vec().into(),
+                    size: 42,
+                },
+                DirectoryNode {
+                    name: "c".into(),
+                    digest: DUMMY_DIGEST.to_vec().into(),
+                    size: 42,
+                },
+            ],
+            symlinks: vec![SymlinkNode {
+                name: "a".into(),
+                target: "foo".into(),
+            }],
+            ..Default::default()
+        };
+
+        d.validate().expect("validate shouldn't error");
+    }
+}
+
+#[test]
+fn validate_overflow() {
+    let d = Directory {
+        directories: vec![DirectoryNode {
+            name: "foo".into(),
+            digest: DUMMY_DIGEST.to_vec().into(),
+            size: u64::MAX,
+        }],
+        ..Default::default()
+    };
+
+    match d.validate().expect_err("must fail") {
+        ValidateDirectoryError::SizeOverflow => {}
+        _ => panic!("unexpected error"),
+    }
+}
+
+#[test]
+fn add_nodes_to_directory() {
+    let mut d = Directory {
+        ..Default::default()
+    };
+
+    d.add(node::Node::Directory(DirectoryNode {
+        name: "b".into(),
+        digest: DUMMY_DIGEST.to_vec().into(),
+        size: 1,
+    }));
+    d.add(node::Node::Directory(DirectoryNode {
+        name: "a".into(),
+        digest: DUMMY_DIGEST.to_vec().into(),
+        size: 1,
+    }));
+    d.add(node::Node::Directory(DirectoryNode {
+        name: "z".into(),
+        digest: DUMMY_DIGEST.to_vec().into(),
+        size: 1,
+    }));
+
+    d.add(node::Node::File(FileNode {
+        name: "f".into(),
+        digest: DUMMY_DIGEST.to_vec().into(),
+        size: 1,
+        executable: true,
+    }));
+    d.add(node::Node::File(FileNode {
+        name: "c".into(),
+        digest: DUMMY_DIGEST.to_vec().into(),
+        size: 1,
+        executable: true,
+    }));
+    d.add(node::Node::File(FileNode {
+        name: "g".into(),
+        digest: DUMMY_DIGEST.to_vec().into(),
+        size: 1,
+        executable: true,
+    }));
+
+    d.add(node::Node::Symlink(SymlinkNode {
+        name: "t".into(),
+        target: "a".into(),
+    }));
+    d.add(node::Node::Symlink(SymlinkNode {
+        name: "o".into(),
+        target: "a".into(),
+    }));
+    d.add(node::Node::Symlink(SymlinkNode {
+        name: "e".into(),
+        target: "a".into(),
+    }));
+
+    d.validate().expect("directory should be valid");
+}
+
+#[test]
+#[cfg_attr(not(debug_assertions), ignore)]
+#[should_panic = "name already exists in directories"]
+fn add_duplicate_node_to_directory_panics() {
+    let mut d = Directory {
+        ..Default::default()
+    };
+
+    d.add(node::Node::Directory(DirectoryNode {
+        name: "a".into(),
+        digest: DUMMY_DIGEST.to_vec().into(),
+        size: 1,
+    }));
+    d.add(node::Node::File(FileNode {
+        name: "a".into(),
+        digest: DUMMY_DIGEST.to_vec().into(),
+        size: 1,
+        executable: true,
+    }));
+}
diff --git a/tvix/castore/src/proto/tests/directory_nodes_iterator.rs b/tvix/castore/src/proto/tests/directory_nodes_iterator.rs
new file mode 100644
index 0000000000..68f147a332
--- /dev/null
+++ b/tvix/castore/src/proto/tests/directory_nodes_iterator.rs
@@ -0,0 +1,78 @@
+use crate::proto::Directory;
+use crate::proto::DirectoryNode;
+use crate::proto::FileNode;
+use crate::proto::NamedNode;
+use crate::proto::SymlinkNode;
+
+#[test]
+fn iterator() {
+    let d = Directory {
+        directories: vec![
+            DirectoryNode {
+                name: "c".into(),
+                ..DirectoryNode::default()
+            },
+            DirectoryNode {
+                name: "d".into(),
+                ..DirectoryNode::default()
+            },
+            DirectoryNode {
+                name: "h".into(),
+                ..DirectoryNode::default()
+            },
+            DirectoryNode {
+                name: "l".into(),
+                ..DirectoryNode::default()
+            },
+        ],
+        files: vec![
+            FileNode {
+                name: "b".into(),
+                ..FileNode::default()
+            },
+            FileNode {
+                name: "e".into(),
+                ..FileNode::default()
+            },
+            FileNode {
+                name: "g".into(),
+                ..FileNode::default()
+            },
+            FileNode {
+                name: "j".into(),
+                ..FileNode::default()
+            },
+        ],
+        symlinks: vec![
+            SymlinkNode {
+                name: "a".into(),
+                ..SymlinkNode::default()
+            },
+            SymlinkNode {
+                name: "f".into(),
+                ..SymlinkNode::default()
+            },
+            SymlinkNode {
+                name: "i".into(),
+                ..SymlinkNode::default()
+            },
+            SymlinkNode {
+                name: "k".into(),
+                ..SymlinkNode::default()
+            },
+        ],
+    };
+
+    // We keep this strings here and convert to string to make the comparison
+    // less messy.
+    let mut node_names: Vec<String> = vec![];
+
+    for node in d.nodes() {
+        node_names.push(String::from_utf8(node.get_name().to_vec()).unwrap());
+    }
+
+    assert_eq!(
+        vec!["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l"],
+        node_names
+    );
+}
diff --git a/tvix/castore/src/proto/tests/mod.rs b/tvix/castore/src/proto/tests/mod.rs
new file mode 100644
index 0000000000..8d903bacb6
--- /dev/null
+++ b/tvix/castore/src/proto/tests/mod.rs
@@ -0,0 +1,2 @@
+mod directory;
+mod directory_nodes_iterator;
diff --git a/tvix/castore/src/tests/import.rs b/tvix/castore/src/tests/import.rs
new file mode 100644
index 0000000000..8b3bd5ce0f
--- /dev/null
+++ b/tvix/castore/src/tests/import.rs
@@ -0,0 +1,129 @@
+use crate::blobservice::{self, BlobService};
+use crate::directoryservice;
+use crate::fixtures::*;
+use crate::import::fs::ingest_path;
+use crate::proto;
+
+use std::sync::Arc;
+use tempfile::TempDir;
+
+#[cfg(target_family = "unix")]
+use std::os::unix::ffi::OsStrExt;
+
+#[cfg(target_family = "unix")]
+#[tokio::test]
+async fn symlink() {
+    let blob_service = blobservice::from_addr("memory://").await.unwrap();
+    let directory_service = directoryservice::from_addr("memory://").await.unwrap();
+
+    let tmpdir = TempDir::new().unwrap();
+
+    std::fs::create_dir_all(&tmpdir).unwrap();
+    std::os::unix::fs::symlink(
+        "/nix/store/somewhereelse",
+        tmpdir.path().join("doesntmatter"),
+    )
+    .unwrap();
+
+    let root_node = ingest_path(
+        Arc::from(blob_service),
+        directory_service,
+        tmpdir.path().join("doesntmatter"),
+    )
+    .await
+    .expect("must succeed");
+
+    assert_eq!(
+        proto::node::Node::Symlink(proto::SymlinkNode {
+            name: "doesntmatter".into(),
+            target: "/nix/store/somewhereelse".into(),
+        }),
+        root_node,
+    )
+}
+
+#[tokio::test]
+async fn single_file() {
+    let blob_service =
+        Arc::from(blobservice::from_addr("memory://").await.unwrap()) as Arc<dyn BlobService>;
+    let directory_service = directoryservice::from_addr("memory://").await.unwrap();
+
+    let tmpdir = TempDir::new().unwrap();
+
+    std::fs::write(tmpdir.path().join("root"), HELLOWORLD_BLOB_CONTENTS).unwrap();
+
+    let root_node = ingest_path(
+        blob_service.clone(),
+        directory_service,
+        tmpdir.path().join("root"),
+    )
+    .await
+    .expect("must succeed");
+
+    assert_eq!(
+        proto::node::Node::File(proto::FileNode {
+            name: "root".into(),
+            digest: HELLOWORLD_BLOB_DIGEST.clone().into(),
+            size: HELLOWORLD_BLOB_CONTENTS.len() as u64,
+            executable: false,
+        }),
+        root_node,
+    );
+
+    // ensure the blob has been uploaded
+    assert!(blob_service.has(&HELLOWORLD_BLOB_DIGEST).await.unwrap());
+}
+
+#[cfg(target_family = "unix")]
+#[tokio::test]
+async fn complicated() {
+    let blob_service =
+        Arc::from(blobservice::from_addr("memory://").await.unwrap()) as Arc<dyn BlobService>;
+    let directory_service = directoryservice::from_addr("memory://").await.unwrap();
+
+    let tmpdir = TempDir::new().unwrap();
+
+    // File ``.keep`
+    std::fs::write(tmpdir.path().join(".keep"), vec![]).unwrap();
+    // Symlink `aa`
+    std::os::unix::fs::symlink("/nix/store/somewhereelse", tmpdir.path().join("aa")).unwrap();
+    // Directory `keep`
+    std::fs::create_dir(tmpdir.path().join("keep")).unwrap();
+    // File ``keep/.keep`
+    std::fs::write(tmpdir.path().join("keep").join(".keep"), vec![]).unwrap();
+
+    let root_node = ingest_path(blob_service.clone(), &directory_service, tmpdir.path())
+        .await
+        .expect("must succeed");
+
+    // ensure root_node matched expectations
+    assert_eq!(
+        proto::node::Node::Directory(proto::DirectoryNode {
+            name: tmpdir
+                .path()
+                .file_name()
+                .unwrap()
+                .as_bytes()
+                .to_owned()
+                .into(),
+            digest: DIRECTORY_COMPLICATED.digest().into(),
+            size: DIRECTORY_COMPLICATED.size(),
+        }),
+        root_node,
+    );
+
+    // ensure DIRECTORY_WITH_KEEP and DIRECTORY_COMPLICATED have been uploaded
+    assert!(directory_service
+        .get(&DIRECTORY_WITH_KEEP.digest())
+        .await
+        .unwrap()
+        .is_some());
+    assert!(directory_service
+        .get(&DIRECTORY_COMPLICATED.digest())
+        .await
+        .unwrap()
+        .is_some());
+
+    // ensure EMPTY_BLOB_CONTENTS has been uploaded
+    assert!(blob_service.has(&EMPTY_BLOB_DIGEST).await.unwrap());
+}
diff --git a/tvix/castore/src/tests/mod.rs b/tvix/castore/src/tests/mod.rs
new file mode 100644
index 0000000000..d016f3e0aa
--- /dev/null
+++ b/tvix/castore/src/tests/mod.rs
@@ -0,0 +1 @@
+mod import;
diff --git a/tvix/castore/src/tonic.rs b/tvix/castore/src/tonic.rs
new file mode 100644
index 0000000000..4b65d6b028
--- /dev/null
+++ b/tvix/castore/src/tonic.rs
@@ -0,0 +1,122 @@
+use tokio::net::UnixStream;
+use tonic::transport::{Channel, Endpoint};
+
+fn url_wants_wait_connect(url: &url::Url) -> bool {
+    url.query_pairs()
+        .filter(|(k, v)| k == "wait-connect" && v == "1")
+        .count()
+        > 0
+}
+
+/// Turn a [url::Url] to a [Channel] if it can be parsed successfully.
+/// It supports the following schemes (and URLs):
+///  - `grpc+http://[::1]:8000`, connecting over unencrypted HTTP/2 (h2c)
+///  - `grpc+https://[::1]:8000`, connecting over encrypted HTTP/2
+///  - `grpc+unix:/path/to/socket`, connecting to a unix domain socket
+///
+/// All URLs support adding `wait-connect=1` as a URL parameter, in which case
+/// the connection is established lazily.
+pub async fn channel_from_url(url: &url::Url) -> Result<Channel, self::Error> {
+    match url.scheme() {
+        "grpc+unix" => {
+            if url.host_str().is_some() {
+                return Err(Error::HostSetForUnixSocket());
+            }
+
+            let connector = tower::service_fn({
+                let url = url.clone();
+                move |_: tonic::transport::Uri| UnixStream::connect(url.path().to_string().clone())
+            });
+
+            // the URL doesn't matter
+            let endpoint = Endpoint::from_static("http://[::]:50051");
+            if url_wants_wait_connect(url) {
+                Ok(endpoint.connect_with_connector(connector).await?)
+            } else {
+                Ok(endpoint.connect_with_connector_lazy(connector))
+            }
+        }
+        _ => {
+            // ensure path is empty, not supported with gRPC.
+            if !url.path().is_empty() {
+                return Err(Error::PathMayNotBeSet());
+            }
+
+            // Stringify the URL and remove the grpc+ prefix.
+            // We can't use `url.set_scheme(rest)`, as it disallows
+            // setting something http(s) that previously wasn't.
+            let unprefixed_url_str = match url.to_string().strip_prefix("grpc+") {
+                None => return Err(Error::MissingGRPCPrefix()),
+                Some(url_str) => url_str.to_owned(),
+            };
+
+            // Use the regular tonic transport::Endpoint logic, but unprefixed_url_str,
+            // as tonic doesn't know about grpc+http[s].
+            let endpoint = Endpoint::try_from(unprefixed_url_str)?;
+            if url_wants_wait_connect(url) {
+                Ok(endpoint.connect().await?)
+            } else {
+                Ok(endpoint.connect_lazy())
+            }
+        }
+    }
+}
+
+/// Errors occuring when trying to connect to a backend
+#[derive(Debug, thiserror::Error)]
+pub enum Error {
+    #[error("grpc+ prefix is missing from URL")]
+    MissingGRPCPrefix(),
+
+    #[error("host may not be set for unix domain sockets")]
+    HostSetForUnixSocket(),
+
+    #[error("path may not be set")]
+    PathMayNotBeSet(),
+
+    #[error("transport error: {0}")]
+    TransportError(tonic::transport::Error),
+}
+
+impl From<tonic::transport::Error> for Error {
+    fn from(value: tonic::transport::Error) -> Self {
+        Self::TransportError(value)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::channel_from_url;
+    use rstest::rstest;
+    use url::Url;
+
+    #[rstest]
+    /// Correct scheme to connect to a unix socket.
+    #[case::valid_unix_socket("grpc+unix:///path/to/somewhere", true)]
+    /// Connecting with wait-connect set to 0 succeeds, as that's the default.
+    #[case::valid_unix_socket_wait_connect_0("grpc+unix:///path/to/somewhere?wait-connect=0", true)]
+    /// Connecting with wait-connect set to 1 fails, as the path doesn't exist.
+    #[case::valid_unix_socket_wait_connect_1(
+        "grpc+unix:///path/to/somewhere?wait-connect=1",
+        false
+    )]
+    /// Correct scheme for unix socket, but setting a host too, which is invalid.
+    #[case::invalid_unix_socket_and_host("grpc+unix://host.example/path/to/somewhere", false)]
+    /// Correct scheme to connect to localhost, with port 12345
+    #[case::valid_ipv6_localhost_port_12345("grpc+http://[::1]:12345", true)]
+    /// Correct scheme to connect to localhost over http, without specifying a port.
+    #[case::valid_http_host_without_port("grpc+http://localhost", true)]
+    /// Correct scheme to connect to localhost over http, without specifying a port.
+    #[case::valid_https_host_without_port("grpc+https://localhost", true)]
+    /// Correct scheme to connect to localhost over http, but with additional path, which is invalid.
+    #[case::invalid_host_and_path("grpc+http://localhost/some-path", false)]
+    /// Connecting with wait-connect set to 0 succeeds, as that's the default.
+    #[case::valid_host_wait_connect_0("grpc+http://localhost?wait-connect=0", true)]
+    /// Connecting with wait-connect set to 1 fails, as the host doesn't exist.
+    #[case::valid_host_wait_connect_1_fails("grpc+http://nonexist.invalid?wait-connect=1", false)]
+    #[tokio::test]
+    async fn test_from_addr_tokio(#[case] uri_str: &str, #[case] is_ok: bool) {
+        let url = Url::parse(uri_str).expect("must parse");
+        assert_eq!(channel_from_url(&url).await.is_ok(), is_ok)
+    }
+}
diff --git a/tvix/cli/Cargo.toml b/tvix/cli/Cargo.toml
new file mode 100644
index 0000000000..ce8d361771
--- /dev/null
+++ b/tvix/cli/Cargo.toml
@@ -0,0 +1,27 @@
+[package]
+name = "tvix-cli"
+version = "0.1.0"
+edition = "2021"
+
+[[bin]]
+name = "tvix"
+path = "src/main.rs"
+
+[dependencies]
+nix-compat = { path = "../nix-compat" }
+tvix-build = { path = "../build" }
+tvix-castore = { path = "../castore" }
+tvix-store = { path = "../store", default-features = false, features = []}
+tvix-eval = { path = "../eval" }
+tvix-glue = { path = "../glue" }
+bytes = "1.4.0"
+clap = { version = "4.0", features = ["derive", "env"] }
+dirs = "4.0.0"
+rustyline = "10.0.0"
+thiserror = "1.0.38"
+tokio = "1.28.0"
+tracing = { version = "0.1.37", features = ["max_level_trace", "release_max_level_info"] }
+tracing-subscriber = { version = "0.3.16", features = ["json"] }
+
+[dependencies.wu-manber]
+git = "https://github.com/tvlfyi/wu-manber.git"
diff --git a/tvix/cli/default.nix b/tvix/cli/default.nix
new file mode 100644
index 0000000000..62e93cc213
--- /dev/null
+++ b/tvix/cli/default.nix
@@ -0,0 +1,95 @@
+{ depot, pkgs, lib, ... }:
+
+(depot.tvix.crates.workspaceMembers.tvix-cli.build.override {
+  runTests = true;
+}).overrideAttrs (finalAttrs: previousAttrs:
+
+let
+  tvix-cli = finalAttrs.finalPackage;
+
+  benchmark-gnutime-format-string =
+    description:
+    "Benchmark: " +
+    (builtins.toJSON {
+      "${description}" = {
+        kbytes = "%M";
+        system = "%S";
+        user = "%U";
+      };
+    });
+
+  # You can run the benchmark with a simple `nix run`, like:
+  #
+  #  nix-build -A tvix.cli.meta.ci.extraSteps.benchmark-nixpkgs-cross-hello-outpath
+  #
+  # TODO(amjoseph): store these results someplace more durable, like git trailers
+  #
+  mkExprBenchmark = { expr, description }:
+    let name = "tvix-cli-benchmark-${description}"; in
+    (pkgs.runCommand name { } ''
+      export SSL_CERT_FILE=${pkgs.cacert.out}/etc/ssl/certs/ca-bundle.crt
+      ${lib.escapeShellArgs [
+        "${pkgs.time}/bin/time"
+        "--format" "${benchmark-gnutime-format-string description}"
+        "${tvix-cli}/bin/tvix"
+        "--no-warnings"
+        "-E" expr
+      ]}
+      touch $out
+    '');
+
+  mkNixpkgsBenchmark = attrpath:
+    mkExprBenchmark {
+      description = builtins.replaceStrings [ ".drv" ] [ "-drv" ] attrpath;
+      expr = "(import ${pkgs.path} {}).${attrpath}";
+    };
+
+  # Constructs a Derivation invoking tvix-cli inside a build, ensures the
+  # calculated tvix output path matches what's passed in externally.
+  mkNixpkgsEvalTest = attrpath: expectedPath:
+    let
+      name = "tvix-eval-test-${builtins.replaceStrings [".drv"] ["-drv"] attrpath}";
+    in
+    (pkgs.runCommand name { } ''
+      export SSL_CERT_FILE=${pkgs.cacert.out}/etc/ssl/certs/ca-bundle.crt
+      TVIX_OUTPUT=$(${tvix-cli}/bin/tvix -E '(import ${pkgs.path} {}).${attrpath}')
+      EXPECTED='${/* the verbatim expected Tvix output: */ "=> \"${builtins.unsafeDiscardStringContext expectedPath}\" :: string"}'
+
+      echo "Tvix output: ''${TVIX_OUTPUT}"
+      if [ "$TVIX_OUTPUT" != "$EXPECTED" ]; then
+        echo "Correct would have been ''${EXPECTED}"
+        exit 1
+      fi
+
+      echo "Output was correct."
+      touch $out
+    '');
+
+
+  benchmarks = {
+    benchmark-hello = (mkNixpkgsBenchmark "hello.outPath");
+    benchmark-cross-hello = (mkNixpkgsBenchmark "pkgsCross.aarch64-multiplatform.hello.outPath");
+    benchmark-firefox = (mkNixpkgsBenchmark "firefox.outPath");
+    benchmark-cross-firefox = (mkNixpkgsBenchmark "pkgsCross.aarch64-multiplatform.firefox.outPath");
+    # Example used for benchmarking LightSpan::Delayed in commit bf286a54bc2ac5eeb78c3d5c5ae66e9af24d74d4
+    benchmark-nixpkgs-attrnames = (mkExprBenchmark { expr = "builtins.length (builtins.attrNames (import ${pkgs.path} {}))"; description = "nixpkgs-attrnames"; });
+  };
+
+  evalTests = {
+    eval-nixpkgs-stdenv-drvpath = (mkNixpkgsEvalTest "stdenv.drvPath" pkgs.stdenv.drvPath);
+    eval-nixpkgs-stdenv-outpath = (mkNixpkgsEvalTest "stdenv.outPath" pkgs.stdenv.outPath);
+    eval-nixpkgs-hello-outpath = (mkNixpkgsEvalTest "hello.outPath" pkgs.hello.outPath);
+    eval-nixpkgs-firefox-outpath = (mkNixpkgsEvalTest "firefox.outPath" pkgs.firefox.outPath);
+    eval-nixpkgs-firefox-drvpath = (mkNixpkgsEvalTest "firefox.drvPath" pkgs.firefox.drvPath);
+    eval-nixpkgs-cross-stdenv-outpath = (mkNixpkgsEvalTest "pkgsCross.aarch64-multiplatform.stdenv.outPath" pkgs.pkgsCross.aarch64-multiplatform.stdenv.outPath);
+    eval-nixpkgs-cross-hello-outpath = (mkNixpkgsEvalTest "pkgsCross.aarch64-multiplatform.hello.outPath" pkgs.pkgsCross.aarch64-multiplatform.hello.outPath);
+  };
+in
+{
+  meta = {
+    ci.targets = (builtins.attrNames benchmarks) ++ (builtins.attrNames evalTests);
+  };
+
+  # Expose benchmarks and evalTests as standard CI targets.
+  passthru = benchmarks // evalTests;
+})
diff --git a/tvix/cli/src/main.rs b/tvix/cli/src/main.rs
new file mode 100644
index 0000000000..5635f446b9
--- /dev/null
+++ b/tvix/cli/src/main.rs
@@ -0,0 +1,337 @@
+use clap::Parser;
+use rustyline::{error::ReadlineError, Editor};
+use std::rc::Rc;
+use std::{fs, path::PathBuf};
+use tracing::Level;
+use tracing_subscriber::fmt::writer::MakeWriterExt;
+use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
+use tracing_subscriber::{EnvFilter, Layer};
+use tvix_build::buildservice;
+use tvix_eval::builtins::impure_builtins;
+use tvix_eval::observer::{DisassemblingObserver, TracingObserver};
+use tvix_eval::{EvalIO, Value};
+use tvix_glue::builtins::add_fetcher_builtins;
+use tvix_glue::builtins::add_import_builtins;
+use tvix_glue::tvix_io::TvixIO;
+use tvix_glue::tvix_store_io::TvixStoreIO;
+use tvix_glue::{builtins::add_derivation_builtins, configure_nix_path};
+
+#[derive(Parser)]
+struct Args {
+    #[arg(long)]
+    log_level: Option<Level>,
+
+    /// Path to a script to evaluate
+    script: Option<PathBuf>,
+
+    #[clap(long, short = 'E')]
+    expr: Option<String>,
+
+    /// Dump the raw AST to stdout before interpreting
+    #[clap(long, env = "TVIX_DISPLAY_AST")]
+    display_ast: bool,
+
+    /// Dump the bytecode to stdout before evaluating
+    #[clap(long, env = "TVIX_DUMP_BYTECODE")]
+    dump_bytecode: bool,
+
+    /// Trace the runtime of the VM
+    #[clap(long, env = "TVIX_TRACE_RUNTIME")]
+    trace_runtime: bool,
+
+    /// Capture the time (relative to the start time of evaluation) of all events traced with
+    /// `--trace-runtime`
+    #[clap(long, env = "TVIX_TRACE_RUNTIME_TIMING", requires("trace_runtime"))]
+    trace_runtime_timing: bool,
+
+    /// Only compile, but do not execute code. This will make Tvix act
+    /// sort of like a linter.
+    #[clap(long)]
+    compile_only: bool,
+
+    /// Don't print warnings.
+    #[clap(long)]
+    no_warnings: bool,
+
+    /// A colon-separated list of directories to use to resolve `<...>`-style paths
+    #[clap(long, short = 'I', env = "NIX_PATH")]
+    nix_search_path: Option<String>,
+
+    /// Print "raw" (unquoted) output.
+    #[clap(long)]
+    raw: bool,
+
+    /// Strictly evaluate values, traversing them and forcing e.g.
+    /// elements of lists and attribute sets before printing the
+    /// return value.
+    #[clap(long)]
+    strict: bool,
+
+    #[arg(long, env, default_value = "memory://")]
+    blob_service_addr: String,
+
+    #[arg(long, env, default_value = "memory://")]
+    directory_service_addr: String,
+
+    #[arg(long, env, default_value = "memory://")]
+    path_info_service_addr: String,
+
+    #[arg(long, env, default_value = "dummy://")]
+    build_service_addr: String,
+}
+
+/// Interprets the given code snippet, printing out warnings, errors
+/// and the result itself. The return value indicates whether
+/// evaluation succeeded.
+fn interpret(code: &str, path: Option<PathBuf>, args: &Args, explain: bool) -> bool {
+    let tokio_runtime = tokio::runtime::Runtime::new().expect("failed to setup tokio runtime");
+
+    let (blob_service, directory_service, path_info_service) = tokio_runtime
+        .block_on({
+            let blob_service_addr = args.blob_service_addr.clone();
+            let directory_service_addr = args.directory_service_addr.clone();
+            let path_info_service_addr = args.path_info_service_addr.clone();
+            async move {
+                tvix_store::utils::construct_services(
+                    blob_service_addr,
+                    directory_service_addr,
+                    path_info_service_addr,
+                )
+                .await
+            }
+        })
+        .expect("unable to setup {blob|directory|pathinfo}service before interpreter setup");
+
+    let build_service = tokio_runtime
+        .block_on({
+            let blob_service = blob_service.clone();
+            let directory_service = directory_service.clone();
+            async move {
+                buildservice::from_addr(
+                    &args.build_service_addr,
+                    blob_service.clone(),
+                    directory_service.clone(),
+                )
+                .await
+            }
+        })
+        .expect("unable to setup buildservice before interpreter setup");
+
+    let tvix_store_io = Rc::new(TvixStoreIO::new(
+        blob_service.clone(),
+        directory_service.clone(),
+        path_info_service.into(),
+        build_service.into(),
+        tokio_runtime.handle().clone(),
+    ));
+
+    let mut eval = tvix_eval::Evaluation::new(
+        Box::new(TvixIO::new(tvix_store_io.clone() as Rc<dyn EvalIO>)) as Box<dyn EvalIO>,
+        true,
+    );
+    eval.strict = args.strict;
+    eval.builtins.extend(impure_builtins());
+    add_derivation_builtins(&mut eval, Rc::clone(&tvix_store_io));
+    add_fetcher_builtins(&mut eval, Rc::clone(&tvix_store_io));
+    add_import_builtins(&mut eval, tvix_store_io);
+    configure_nix_path(&mut eval, &args.nix_search_path);
+
+    let source_map = eval.source_map();
+    let result = {
+        let mut compiler_observer =
+            DisassemblingObserver::new(source_map.clone(), std::io::stderr());
+        if args.dump_bytecode {
+            eval.compiler_observer = Some(&mut compiler_observer);
+        }
+
+        let mut runtime_observer = TracingObserver::new(std::io::stderr());
+        if args.trace_runtime {
+            if args.trace_runtime_timing {
+                runtime_observer.enable_timing()
+            }
+            eval.runtime_observer = Some(&mut runtime_observer);
+        }
+
+        eval.evaluate(code, path)
+    };
+
+    if args.display_ast {
+        if let Some(ref expr) = result.expr {
+            eprintln!("AST: {}", tvix_eval::pretty_print_expr(expr));
+        }
+    }
+
+    for error in &result.errors {
+        error.fancy_format_stderr();
+    }
+
+    if !args.no_warnings {
+        for warning in &result.warnings {
+            warning.fancy_format_stderr(&source_map);
+        }
+    }
+
+    if let Some(value) = result.value.as_ref() {
+        if explain {
+            println!("=> {}", value.explain());
+        } else {
+            println_result(value, args.raw);
+        }
+    }
+
+    // inform the caller about any errors
+    result.errors.is_empty()
+}
+
+/// Interpret the given code snippet, but only run the Tvix compiler
+/// on it and return errors and warnings.
+fn lint(code: &str, path: Option<PathBuf>, args: &Args) -> bool {
+    let mut eval = tvix_eval::Evaluation::new_impure();
+    eval.strict = args.strict;
+
+    let source_map = eval.source_map();
+
+    let mut compiler_observer = DisassemblingObserver::new(source_map.clone(), std::io::stderr());
+
+    if args.dump_bytecode {
+        eval.compiler_observer = Some(&mut compiler_observer);
+    }
+
+    if args.trace_runtime {
+        eprintln!("warning: --trace-runtime has no effect with --compile-only!");
+    }
+
+    let result = eval.compile_only(code, path);
+
+    if args.display_ast {
+        if let Some(ref expr) = result.expr {
+            eprintln!("AST: {}", tvix_eval::pretty_print_expr(expr));
+        }
+    }
+
+    for error in &result.errors {
+        error.fancy_format_stderr();
+    }
+
+    for warning in &result.warnings {
+        warning.fancy_format_stderr(&source_map);
+    }
+
+    // inform the caller about any errors
+    result.errors.is_empty()
+}
+
+fn main() {
+    let args = Args::parse();
+
+    // configure log settings
+    let level = args.log_level.unwrap_or(Level::INFO);
+
+    let subscriber = tracing_subscriber::registry().with(
+        tracing_subscriber::fmt::Layer::new()
+            .with_writer(std::io::stderr.with_max_level(level))
+            .compact()
+            .with_filter(
+                EnvFilter::builder()
+                    .with_default_directive(level.into())
+                    .from_env()
+                    .expect("invalid RUST_LOG"),
+            ),
+    );
+    subscriber
+        .try_init()
+        .expect("unable to set up tracing subscriber");
+
+    if let Some(file) = &args.script {
+        run_file(file.clone(), &args)
+    } else if let Some(expr) = &args.expr {
+        if !interpret(expr, None, &args, false) {
+            std::process::exit(1);
+        }
+    } else {
+        run_prompt(&args)
+    }
+}
+
+fn run_file(mut path: PathBuf, args: &Args) {
+    if path.is_dir() {
+        path.push("default.nix");
+    }
+    let contents = fs::read_to_string(&path).expect("failed to read the input file");
+
+    let success = if args.compile_only {
+        lint(&contents, Some(path), args)
+    } else {
+        interpret(&contents, Some(path), args, false)
+    };
+
+    if !success {
+        std::process::exit(1);
+    }
+}
+
+fn println_result(result: &Value, raw: bool) {
+    if raw {
+        println!("{}", result.to_contextful_str().unwrap())
+    } else {
+        println!("=> {} :: {}", result, result.type_of())
+    }
+}
+
+fn state_dir() -> Option<PathBuf> {
+    let mut path = dirs::data_dir();
+    if let Some(p) = path.as_mut() {
+        p.push("tvix")
+    }
+    path
+}
+
+fn run_prompt(args: &Args) {
+    let mut rl = Editor::<()>::new().expect("should be able to launch rustyline");
+
+    if args.compile_only {
+        eprintln!("warning: `--compile-only` has no effect on REPL usage!");
+    }
+
+    let history_path = match state_dir() {
+        // Attempt to set up these paths, but do not hard fail if it
+        // doesn't work.
+        Some(mut path) => {
+            let _ = std::fs::create_dir_all(&path);
+            path.push("history.txt");
+            let _ = rl.load_history(&path);
+            Some(path)
+        }
+
+        None => None,
+    };
+
+    loop {
+        let readline = rl.readline("tvix-repl> ");
+        match readline {
+            Ok(line) => {
+                if line.is_empty() {
+                    continue;
+                }
+
+                rl.add_history_entry(&line);
+
+                if let Some(without_prefix) = line.strip_prefix(":d ") {
+                    interpret(without_prefix, None, args, true);
+                } else {
+                    interpret(&line, None, args, false);
+                }
+            }
+            Err(ReadlineError::Interrupted) | Err(ReadlineError::Eof) => break,
+
+            Err(err) => {
+                eprintln!("error: {}", err);
+                break;
+            }
+        }
+    }
+
+    if let Some(path) = history_path {
+        rl.save_history(&path).unwrap();
+    }
+}
diff --git a/tvix/clippy.toml b/tvix/clippy.toml
new file mode 100644
index 0000000000..be7709684c
--- /dev/null
+++ b/tvix/clippy.toml
@@ -0,0 +1,6 @@
+# prevents a false-positive lint on our types containing bytes::Bytes
+# https://rust-lang.github.io/rust-clippy/master/index.html#/mutable_key_type
+ignore-interior-mutability = [
+  "bytes::Bytes",
+  "tvix_castore::digests::B3Digest"
+]
diff --git a/tvix/crate-hashes.json b/tvix/crate-hashes.json
new file mode 100644
index 0000000000..ca45e43176
--- /dev/null
+++ b/tvix/crate-hashes.json
@@ -0,0 +1,4 @@
+{
+  "bigtable_rs 0.2.9 (git+https://github.com/flokli/bigtable_rs?rev=0af404741dfc40eb9fa99cf4d4140a09c5c20df7#0af404741dfc40eb9fa99cf4d4140a09c5c20df7)": "1njjam1lx2xlnm7a41lga8601vmjgqz0fvc77x24gd04pc7avxll",
+  "wu-manber 0.1.0 (git+https://github.com/tvlfyi/wu-manber.git#0d5b22bea136659f7de60b102a7030e0daaa503d)": "1zhk83lbq99xzyjwphv2qrb8f8qgfqwa5bbbvyzm0z0bljsjv0pd"
+}
\ No newline at end of file
diff --git a/tvix/default.nix b/tvix/default.nix
new file mode 100644
index 0000000000..f562cf37de
--- /dev/null
+++ b/tvix/default.nix
@@ -0,0 +1,238 @@
+# Nix helpers for projects under //tvix
+{ pkgs, lib, depot, ... }:
+
+let
+  # crate override for crates that need protobuf
+  protobufDep = prev: (prev.nativeBuildInputs or [ ]) ++ [ pkgs.buildPackages.protobuf ];
+  iconvDarwinDep = lib.optional pkgs.stdenv.isDarwin pkgs.libiconv;
+
+  # On Darwin, some crates producing binaries need to be able to link against security.
+  darwinDeps = lib.optionals pkgs.stdenv.isDarwin (with pkgs.buildPackages.darwin.apple_sdk.frameworks; [
+    Security
+    SystemConfiguration
+  ]);
+
+  # Load the crate2nix crate tree.
+  crates = import ./Cargo.nix {
+    inherit pkgs;
+    nixpkgs = pkgs.path;
+
+    # Hack to fix Darwin build
+    # See https://github.com/NixOS/nixpkgs/issues/218712
+    buildRustCrateForPkgs = pkgs:
+      if pkgs.stdenv.isDarwin then
+        let
+          buildRustCrate = pkgs.buildRustCrate;
+          buildRustCrate_ = args: buildRustCrate args // { dontStrip = true; };
+          override = o: args: buildRustCrate.override o (args // { dontStrip = true; });
+        in
+        pkgs.makeOverridable override { }
+      else pkgs.buildRustCrate;
+
+    defaultCrateOverrides = pkgs.defaultCrateOverrides // {
+      zstd-sys = prev: {
+        nativeBuildInputs = prev.nativeBuildInputs or [ ];
+        buildInputs = prev.buildInputs or [ ] ++ iconvDarwinDep;
+      };
+
+      opentelemetry-proto = prev: {
+        nativeBuildInputs = protobufDep prev;
+      };
+
+      prost-build = prev: {
+        nativeBuildInputs = protobufDep prev;
+      };
+
+      prost-wkt-types = prev: {
+        nativeBuildInputs = protobufDep prev;
+      };
+
+      tonic-reflection = prev: {
+        nativeBuildInputs = protobufDep prev;
+      };
+
+      tvix-build = prev: {
+        PROTO_ROOT = depot.tvix.build.protos.protos;
+        nativeBuildInputs = protobufDep prev;
+        buildInputs = darwinDeps;
+      };
+
+      tvix-castore = prev: {
+        PROTO_ROOT = depot.tvix.castore.protos.protos;
+        nativeBuildInputs = protobufDep prev;
+      };
+
+      tvix-cli = prev: {
+        buildInputs = prev.buildInputs or [ ] ++ darwinDeps;
+      };
+
+      tvix-store = prev: {
+        PROTO_ROOT = depot.tvix.store.protos.protos;
+        nativeBuildInputs = protobufDep prev;
+        # fuse-backend-rs uses DiskArbitration framework to handle mount/unmount on Darwin
+        buildInputs = prev.buildInputs or [ ]
+          ++ darwinDeps
+          ++ lib.optional pkgs.stdenv.isDarwin pkgs.buildPackages.darwin.apple_sdk.frameworks.DiskArbitration;
+      };
+    };
+  };
+
+  # Cargo dependencies to be used with nixpkgs rustPlatform functions.
+  cargoDeps = pkgs.rustPlatform.importCargoLock {
+    lockFile = ./Cargo.lock;
+    # Extract the hashes from `crates` / Cargo.nix, we already get them from cargo2nix.
+    # This returns an attribute set containing "${crateName}-${version}" as key,
+    # and the outputHash as value.
+    outputHashes = builtins.listToAttrs
+      (map
+        (crateName:
+          (lib.nameValuePair "${crateName}-${crates.internal.crates.${crateName}.version}" crates.internal.crates.${crateName}.src.outputHash)
+        ) [
+        "bigtable_rs"
+        "wu-manber"
+      ]);
+  };
+
+  # The cleaned sources.
+  src = depot.third_party.gitignoreSource ./.;
+
+  # Target containing *all* tvix proto files.
+  # Useful for workspace-wide cargo invocations (doc, clippy)
+  protos = pkgs.symlinkJoin {
+    name = "tvix-all-protos";
+    paths = [
+      depot.tvix.build.protos.protos
+      depot.tvix.castore.protos.protos
+      depot.tvix.store.protos.protos
+    ];
+  };
+
+in
+{
+  inherit crates protos;
+
+  # Run crate2nix generate, ensure the output doesn't differ afterwards
+  # (and doesn't fail).
+  #
+  # Currently this re-downloads every crate every time
+  # crate2nix-check (but not crate2nix) is built.
+  # TODO(amjoseph): be less wasteful with bandwidth.
+  #
+  crate2nix-check =
+    let
+      outputHashAlgo = "sha256";
+    in
+    pkgs.stdenv.mkDerivation {
+      inherit src;
+
+      # Important: we include the hash of the Cargo.lock file and
+      # Cargo.nix file in the derivation name.  This forces the FOD
+      # to be rebuilt/reverified whenever either of them changes.
+      name = "tvix-crate2nix-check-" +
+        (builtins.substring 0 8 (builtins.hashFile "sha256" ./Cargo.lock)) +
+        "-" +
+        (builtins.substring 0 8 (builtins.hashFile "sha256" ./Cargo.nix));
+
+      nativeBuildInputs = with pkgs; [ git cacert cargo ];
+      buildPhase = ''
+        export CARGO_HOME=$(mktemp -d)
+
+        # The following command can be omitted, in which case
+        # crate2nix-generate will run it automatically, but won't show the
+        # output, which makes it look like the build is somehow "stuck" for a
+        # minute or two.
+        cargo metadata > /dev/null
+
+        # running this command counteracts depotfmt brokenness
+        git init
+
+        ${depot.tools.crate2nix-generate}/bin/crate2nix-generate
+
+        # technically unnecessary, but provides more-helpful output in case of error
+        diff -ur Cargo.nix ${src}/Cargo.nix
+
+        # the FOD hash will check that the (re-)generated Cargo.nix matches the committed Cargo.nix
+        cp Cargo.nix $out
+      '';
+
+      # This is an FOD in order to allow `cargo` to perform network access.
+      outputHashMode = "flat";
+      inherit outputHashAlgo;
+      outputHash = builtins.hashFile outputHashAlgo ./Cargo.nix;
+      env.SSL_CERT_FILE = "${pkgs.cacert.out}/etc/ssl/certs/ca-bundle.crt";
+    };
+
+  # Provide the Tvix logo in both .webp and .png format.
+  logo = pkgs.runCommand "logo"
+    {
+      nativeBuildInputs = [ pkgs.imagemagick ];
+    } ''
+    mkdir -p $out
+    cp ${./logo.webp} $out/logo.webp
+    convert $out/logo.webp $out/logo.png
+  '';
+
+  # Provide a shell for the combined dependencies of all Tvix Rust
+  # projects. Note that as this is manually maintained it may be
+  # lacking something, but it is required for some people's workflows.
+  #
+  # This shell can be entered with e.g. `mg shell //tvix:shell`.
+  # This is a separate file, so it can be used individually in the tvix josh
+  # workspace too.
+  shell = (import ./shell.nix { inherit pkgs; });
+
+  # Build the Rust documentation for publishing on docs.tvix.dev.
+  rust-docs = pkgs.stdenv.mkDerivation {
+    inherit cargoDeps src;
+    name = "tvix-rust-docs";
+    PROTO_ROOT = protos;
+
+    nativeBuildInputs = with pkgs; [
+      cargo
+      pkg-config
+      protobuf
+      rustc
+      rustPlatform.cargoSetupHook
+    ];
+
+    buildInputs = [
+      pkgs.fuse
+    ] ++ iconvDarwinDep;
+
+    buildPhase = ''
+      cargo doc --document-private-items
+      mv target/doc $out
+    '';
+  };
+
+  # Run cargo clippy. We run it with -Dwarnings, so warnings cause a nonzero
+  # exit code.
+  clippy = pkgs.stdenv.mkDerivation {
+    inherit cargoDeps src;
+    name = "tvix-clippy";
+    PROTO_ROOT = protos;
+
+    buildInputs = [
+      pkgs.fuse
+    ];
+    nativeBuildInputs = with pkgs; [
+      cargo
+      clippy
+      pkg-config
+      protobuf
+      rustc
+      rustPlatform.cargoSetupHook
+    ];
+
+    # Allow blocks_in_conditions due to false positives with #[tracing::instrument(…)]:
+    # https://github.com/rust-lang/rust-clippy/issues/12281
+    buildPhase = "cargo clippy --tests --all-features --benches --examples -- -Dwarnings -A clippy::blocks_in_conditions | tee $out";
+  };
+
+  meta.ci.targets = [
+    "clippy"
+    "crate2nix-check"
+    "shell"
+    "rust-docs"
+  ];
+}
diff --git a/tvix/docs/.gitignore b/tvix/docs/.gitignore
index 77699ee8a3..8117055463 100644
--- a/tvix/docs/.gitignore
+++ b/tvix/docs/.gitignore
@@ -1,2 +1,2 @@
-*.svg
-*.html
+book
+.mdbook-plantuml-cache/
diff --git a/tvix/docs/Makefile b/tvix/docs/Makefile
deleted file mode 100644
index ba9e2bdef6..0000000000
--- a/tvix/docs/Makefile
+++ /dev/null
@@ -1,12 +0,0 @@
-all: build
-
-puml:
-	plantuml *.puml -tsvg
-
-html:
-	pandoc *.md -f markdown --self-contained -t html -s -o tvix.html --csl=${CSL}
-
-build: puml html
-
-clean:
-	rm -f *.tex *.pdf *.png *.svg
diff --git a/tvix/docs/book.toml b/tvix/docs/book.toml
new file mode 100644
index 0000000000..7318a90233
--- /dev/null
+++ b/tvix/docs/book.toml
@@ -0,0 +1,11 @@
+[book]
+authors = ["The Tvix Authors"]
+language = "en"
+multilingual = false
+src = "src"
+title = "Tvix Docs"
+
+[preprocessor.plantuml]
+# override the /usr/bin/plantuml default
+plantuml-cmd = "plantuml"
+use-data-uris = true
diff --git a/tvix/docs/components.md b/tvix/docs/components.md
deleted file mode 100644
index 19e7baa3ec..0000000000
--- a/tvix/docs/components.md
+++ /dev/null
@@ -1,114 +0,0 @@
----
-title: "Tvix - Architecture & data flow"
-numbersections: true
-author:
-- adisbladis
-- flokli
-- tazjin
-email:
-- adis@blad.is
-- mail@tazj.in
-lang: en-GB
-classoption:
-- twocolumn
-header-includes:
-- \usepackage{caption, graphicx, tikz, aeguill, pdflscape}
----
-
-# Background
-
-We intend for Tvix tooling to be more decoupled than the existing,
-monolithic Nix implementation. In practice, we expect to gain several
-benefits from this, such as:
-
-- Ability to use different builders
-- Ability to use different store implementations
-- No monopolisation of the implementation, allowing users to replace
-  components that they are unhappy with (up to and including the
-  language evaluator)
-- Less hidden intra-dependencies between tools due to explicit RPC/IPC
-  boundaries
-
-Communication between different components of the system will use
-gRPC. The rest of this document outlines the components.
-
-# Components
-
-## Coordinator
-
-*Purpose:* The coordinator (in the simplest case, the Tvix CLI tool)
-oversees the flow of a build process and delegates tasks to the right
-subcomponents. For example, if a user runs the equivalent of
-`nix-build` in a folder containing a `default.nix` file, the
-coordinator will invoke the evaluator, pass the resulting derivations
-to the builder and coordinate any necessary store interactions (for
-substitution and other purposes).
-
-While many users are likely to use the CLI tool as their primary
-method of interacting with Tvix, it is not unlikely that alternative
-coordinators (e.g. for a distributed, "Nix-native" CI system) would be
-implemented. To facilitate this, we are considering implementing the
-coordinator on top of a state-machine model that would make it
-possible to reuse the FSM logic without tying it to any particular
-kind of application.
-
-## Evaluator
-
-*Purpose:* Eval takes care of evaluating Nix code. In a typical build
-flow it would be responsible for producing derivations. It can also be
-used as a standalone tool, for example, in use-cases where Nix is used
-to generate configuration without any build or store involvement.
-
-*Requirements:* For now, it will run on the machine invoking the build
-command itself. We give it filesystem access to handle things like
-imports or `builtins.readFile`.
-
-In the future, we might abstract away raw filesystem access by
-allowing the evaluator to request files from the coordinator (which
-will query the store for it). This might get messy, and the benefits
-are questionable. We might be okay with running the evaluator with
-filesystem access for now and can extend the interface if the need
-arises.
-
-## Builder
-
-*Purpose:* A builder receives derivations from the coordinator and
-builds them.
-
-By making builder a standardised interface it's possible to make the
-sandboxing mechanism used by the build process pluggable.
-
-Nix is currently using a hard-coded
-[libseccomp](https://github.com/seccomp/libseccomp) based sandboxing
-mechanism and another one based on
-[sandboxd](https://www.unix.com/man-page/mojave/8/sandboxd/) on macOS.
-These are only separated by [compiler preprocessor
-macros](https://gcc.gnu.org/onlinedocs/cpp/Ifdef.html) within the same
-source files despite having very little in common with each other.
-
-This makes experimentation with alternative backends difficult and
-porting Nix to other platforms harder than it has to be. We want to
-write a new Linux builder which uses
-[OCI](https://github.com/opencontainers/runtime-spec), the current
-dominant Linux containerisation technology, by default.
-
-With a well-defined builder abstraction, it's also easy to imagine
-other backends such as a Kubernetes-based one in the future.
-
-## Store
-
-*Purpose:* Store takes care of storing build results. It provides a
-unified interface to get file paths and upload new ones.
-
-Most likely, we will end up with multiple implementations of store, a
-few possible ones that come to mind are:
-
-- Local
-- SSH
-- GCP
-- S3
-- Ceph
-
-# Figures
-
-![component flow](./component-flow.svg)
diff --git a/tvix/docs/default.nix b/tvix/docs/default.nix
index 016d641df5..9fc2f76576 100644
--- a/tvix/docs/default.nix
+++ b/tvix/docs/default.nix
@@ -1,47 +1,23 @@
 { pkgs, lib, ... }:
 
-let
-
-  tl = pkgs.texlive.combine {
-    inherit (pkgs.texlive) scheme-medium wrapfig ulem capt-of
-      titlesec preprint enumitem paralist ctex environ svg
-      beamer trimspaces zhnumber changepage framed pdfpages
-      fvextra minted upquote ifplatform xstring;
-  };
-
-  csl = pkgs.fetchurl {
-    name = "numeric.csl";
-    url = "https://gist.githubusercontent.com/bwiernik/8c6f39cf51ceb3a03107/raw/1d75c2d62113ffbba6ed03a47ad99bde86934f2b/APA%2520Numeric";
-    sha256 = "1yfhhnhbzvhrv93baz98frmgsx5y442nzhb0l956l4j35fb0cc3h";
-  };
-
-in
 pkgs.stdenv.mkDerivation {
-  pname = "tvix-doc";
+  pname = "tvix-docs";
   version = "0.1";
 
-  outputs = [ "out" "svg" ];
+  outputs = [ "out" ];
 
   src = lib.cleanSource ./.;
 
-  CSL = csl;
-
   nativeBuildInputs = [
-    pkgs.pandoc
+    pkgs.mdbook
+    pkgs.mdbook-plantuml
     pkgs.plantuml
-    tl
   ];
 
-  installPhase = ''
-    runHook preInstall
-
-    mkdir -p $out
-    cp -v *.html $out/
-
-    mkdir -p $svg
-    cp -v *.svg $svg/
-
-    runHook postSubmit
+  # plantuml wants to create ./.mdbook-plantuml-cache, which fails as $src is r/o.
+  # copy all sources elsewhere to workaround.
+  buildCommand = ''
+    cp -R $src/. .
+    mdbook build -d $out
   '';
-
 }
diff --git a/tvix/docs/src/SUMMARY.md b/tvix/docs/src/SUMMARY.md
new file mode 100644
index 0000000000..b0e47a0011
--- /dev/null
+++ b/tvix/docs/src/SUMMARY.md
@@ -0,0 +1,10 @@
+# Summary
+
+# Tvix
+- [Architecture & data flow](./architecture.md)
+- [TODOs](./TODO.md)
+
+# Nix
+- [Specification of the Nix Language](./language-spec.md)
+- [Nix language version history](./lang-version.md)
+- [Value Pointer Equality](./value-pointer-equality.md)
diff --git a/tvix/docs/src/TODO.md b/tvix/docs/src/TODO.md
new file mode 100644
index 0000000000..6644bb6bac
--- /dev/null
+++ b/tvix/docs/src/TODO.md
@@ -0,0 +1,129 @@
+# TODO
+
+This contains a rough collection of ideas on the TODO list, trying to keep track
+of it somewhere.
+
+Of course, there's no guarantee these things will get addressed, but it helps
+dumping the backlog somewhere.
+
+Feel free to add new ideas. Before picking something, ask in `#tvix-dev` to make
+sure noone is working on this, or has some specific design in mind already.
+
+## Cleanups
+ - move some of the rstest cases in `tvix-glue` to the `.nix`/`.exp` mechanism.
+   - Parts requiring test fixtures need some special convention.
+     Some of these also cannot be checked into the repo, like the import tests
+     adding special files to test filtering.
+ - add `nix_oracle` mechanism from `tvix-eval` to `tvix-glue`.
+
+## Fixes towards correctness
+ - `builtins.toXML` is missing string context. See b/398.
+ - `builtins.toXML` self-closing tags need to be configurable in a more granular
+   fashion, requires third-party crate support. See b/399.
+ - `rnix` only supports string source files, but `NixString` uses bytes (and Nix
+   source code might be no valid UTF-8).
+
+## Documentation
+Extend the other pages in here. Some ideas on what should be tackled:
+ - Document what Tvix is, and what it is not yet. What it is now, what it is not
+   (yet), explaining some of the architectural choices (castore, more hermetic
+   `Build` repr), while still being compatible. Explain how it's possible to
+   plug in other frontends, and use `tvix-{[ca]store,build}` without Nixlang even.
+   And how `nix-compat` is a useful crate for all sorts of formats and data
+   types of Nix.
+ - Update the Architecture diagram to model the current state of things.
+   There's no gRPC between Coordinator and Evaluator.
+ - Add a dedicated section/page explaining the separation between tvix-glue and
+   tvix-eval, and how more annoying builtins get injected into tvix-eval through
+   tvix-glue.
+   Maybe restructure to only explain the component structure potentially
+   crossing process boundaries (those with gRPC), and make the rest more crate
+   and trait-focused?
+ - Restructure docs on castore vs store, this seems to be duplicated a bit and
+   is probably still not too clear.
+ - Describe store composition(s) in more detail. There's some notes on granular
+   fetching which probably can be repurposed.
+ - Absorb the rest of //tvix/website into this.
+
+## Features
+
+### CLI
+ - `nix repl` can set variables and effectively mutates a global scope. We
+  should update the existing / add another repl that allows the same. We don't
+  want to mutate the evaluator, but should construct a new one, passing in the
+  root scope returned from the previous evaluation.
+
+### Fetchers
+Some more fetcher-related builtins need work:
+ - `fetchGit`
+ - `fetchTree` (hairy, seems there's no proper spec and the URL syntax seems
+   subject to change/underdocumented)
+
+### Convert builtins:fetchurl to Fetches
+We need to convert `builtins:fetchurl`-style calls to `builtins.derivation` to
+fetches, not Derivations (tracked in `KnownPaths`).
+
+### Derivation -> Build
+While we have some support for `structuredAttrs` and `fetchClosure` (at least
+enough to calculate output hashes, aka produce identical ATerm), the code
+populating the `Build` struct doesn't exist it yet.
+
+Similarly, we also don't properly populate the build environment for
+`fetchClosure` yet. (Note there already is `ExportedPathInfo`, so once
+`structuredAttrs` is there this should be easy.
+
+### Builders
+Once builds are proven to work with real-world builds, and the corner cases
+there are ruled out, adding other types of builders might be interesting.
+
+ - bwrap
+ - gVisor
+ - Cloud Hypervisor (using similar technique as `//tvix//boot`).
+
+Long-term, we want to extend traits and gRPC protocol to expose more telemetry,
+logs etc, but this is something requiring a lot of designing.
+
+### Store composition
+ - Combinators: list-by-priority, first-come-first-serve, cache
+ - How do describe hierarchies. URL format too one-dimensional, but we might get
+   quite far with a similar "substituters" concept that Nix uses, to construct
+   the composed stores.
+### Store Config
+   There's already serde for some store options (bigtable uses `serde_qs`).
+   We might also have common options global over all backends, like chunking
+   parameters for chunking blobservices. Think where this would fit in.
+ - Rework the URL syntax for object_store. We should support the default s3/gcs
+   URLs at least.
+
+### BlobService
+ - On the trait side, currently there's no way to distinguish reading a
+   known-chunk vs blob, so we might be calling `.chunks()` unnecessarily often.
+   At least for the `object_store` backend, this might be a problem.
+ - While `object_store` recently got support for `Content-Type`
+   (https://github.com/apache/arrow-rs/pull/5650), there's no support on the
+   local filesystem yet. We'd need to add support to this (through xattrs).
+
+### DirectoryService
+ - Add an `object_store` variant, storing a Directory *closure* keyed by the
+   root `Directory` digest. This won't allow indexing intermediate Directory
+   nodes, but once we have `DirectoryService` composition, it shouldn't be an
+   issue.
+ - [redb](https://www.redb.org/) backend
+
+### PathInfoService
+ - [redb](https://www.redb.org/) backend
+ - sqlite backend (different schema than the Nix one, we need the root nodes data!)
+
+### Nix-compat
+- Async NAR reader (@edef?)
+
+### Nix Daemon protocol
+- Some work ongoing on the worker operation parsing. Partially blocked on the async NAR reader.
+
+### O11Y
+ - gRPC trace propagation (cl/10532)
+ - `tracing-tracy` (cl/10952)
+ - `[tracing-]indicatif` for progress/log reporting (floklis stash)
+ - unification into `tvix-tracing` crate, currently a lot of boilerplate
+   in `tvix-store` CLI entrypoint, and half of the boilerplate copied over to
+   `tvix-cli`.
diff --git a/tvix/docs/src/architecture.md b/tvix/docs/src/architecture.md
new file mode 100644
index 0000000000..5e0aa95f1a
--- /dev/null
+++ b/tvix/docs/src/architecture.md
@@ -0,0 +1,147 @@
+# Tvix - Architecture & data flow
+
+## Background
+
+We intend for Tvix tooling to be more decoupled than the existing,
+monolithic Nix implementation. In practice, we expect to gain several
+benefits from this, such as:
+
+- Ability to use different builders
+- Ability to use different store implementations
+- No monopolisation of the implementation, allowing users to replace
+  components that they are unhappy with (up to and including the
+  language evaluator)
+- Less hidden intra-dependencies between tools due to explicit RPC/IPC
+  boundaries
+
+Communication between different components of the system will use
+gRPC. The rest of this document outlines the components.
+
+## Components
+
+### Coordinator
+
+*Purpose:* The coordinator (in the simplest case, the Tvix CLI tool)
+oversees the flow of a build process and delegates tasks to the right
+subcomponents. For example, if a user runs the equivalent of
+`nix-build` in a folder containing a `default.nix` file, the
+coordinator will invoke the evaluator, pass the resulting derivations
+to the builder and coordinate any necessary store interactions (for
+substitution and other purposes).
+
+While many users are likely to use the CLI tool as their primary
+method of interacting with Tvix, it is not unlikely that alternative
+coordinators (e.g. for a distributed, "Nix-native" CI system) would be
+implemented. To facilitate this, we are considering implementing the
+coordinator on top of a state-machine model that would make it
+possible to reuse the FSM logic without tying it to any particular
+kind of application.
+
+### Evaluator
+
+*Purpose:* Eval takes care of evaluating Nix code. In a typical build
+flow it would be responsible for producing derivations. It can also be
+used as a standalone tool, for example, in use-cases where Nix is used
+to generate configuration without any build or store involvement.
+
+*Requirements:* For now, it will run on the machine invoking the build
+command itself. We give it filesystem access to handle things like
+imports or `builtins.readFile`.
+
+To support IFD, the Evaluator also needs access to store paths. This
+could be implemented by having the coordinator provide an interface to retrieve
+files from a store path, or by ensuring a "realized version of the store" is
+accessible by the evaluator (this could be a FUSE filesystem, or the "real"
+/nix/store on disk.
+
+We might be okay with running the evaluator with filesystem access for now and
+can extend the interface if the need arises.
+
+### Builder
+
+*Purpose:* A builder receives derivations from the coordinator and
+builds them.
+
+By making builder a standardised interface it's possible to make the
+sandboxing mechanism used by the build process pluggable.
+
+Nix is currently using a hard-coded
+[libseccomp](https://github.com/seccomp/libseccomp) based sandboxing
+mechanism and another one based on
+[sandboxd](https://www.unix.com/man-page/mojave/8/sandboxd/) on macOS.
+These are only separated by [compiler preprocessor
+macros](https://gcc.gnu.org/onlinedocs/cpp/Ifdef.html) within the same
+source files despite having very little in common with each other.
+
+This makes experimentation with alternative backends difficult and
+porting Nix to other platforms harder than it has to be. We want to
+write a new Linux builder which uses
+[OCI](https://github.com/opencontainers/runtime-spec), the current
+dominant Linux containerisation technology, by default.
+
+With a well-defined builder abstraction, it's also easy to imagine
+other backends such as a Kubernetes-based one in the future.
+
+The environment in which builds happen is currently very Nix-specific. We might
+want to avoid having to maintain all the intricacies of a Nix-specific
+sandboxing environment in every builder, and instead only provide a more
+generic interface, receiving build requests (and have the coordinator translate
+derivations to that format). [^1]
+
+To build, the builder needs to be able to mount all build inputs into the build
+environment. For this, it needs the store to expose a filesystem interface.
+
+### Store
+
+*Purpose:* Store takes care of storing build results. It provides a
+unified interface to get store paths and upload new ones, as well as querying
+for the existence of a store path and its metadata (references, signatures, …).
+
+Tvix natively uses an improved store protocol. Instead of transferring around
+NAR files, which don't provide an index and don't allow seekable access, a
+concept similar to git tree hashing is used.
+
+This allows more granular substitution, chunk reusage and parallel download of
+individual files, reducing bandwidth usage.
+As these chunks are content-addressed, it opens up the potential for
+peer-to-peer trustless substitution of most of the data, as long as we sign the
+root of the index.
+
+Tvix still keeps the old-style signatures, NAR hashes and NAR size around. In
+the case of NAR hash / NAR size, this data is strictly required in some cases.
+The old-style signatures are valuable for communication with existing
+implementations.
+
+Old-style binary caches (like cache.nixos.org) can still be exposed via the new
+interface, by doing on-the-fly (re)chunking/ingestion.
+
+Most likely, there will be multiple implementations of store, some storing
+things locally, some exposing a "remote view".
+
+A few possible ones that come to mind are:
+
+- Local store
+- SFTP/ GCP / S3 / HTTP
+- NAR/NARInfo protocol: HTTP, S3
+
+A remote Tvix store can be connected by simply connecting to its gRPC
+interface, possibly using SSH tunneling, but there doesn't need to be an
+additional "wire format" like the Nix `ssh(+ng)://` protocol.
+
+Settling on one interface allows composition of stores, meaning it becomes
+possible to express substitution from remote caches as a proxy layer.
+
+It'd also be possible to write a FUSE implementation on top of the RPC
+interface, exposing a lazily-substituting /nix/store mountpoint. Using this in
+remote build context dramatically reduces the amount of data transferred to a
+builder, as only the files really accessed during the build are substituted.
+
+## Figures
+
+```plantuml,format=svg
+{{#include figures/component-flow.puml}}
+```
+
+[^1]: There have already been some discussions in the Nix community, to switch
+  to REAPI:
+  https://discourse.nixos.org/t/a-proposal-for-replacing-the-nix-worker-protocol/20926/22
diff --git a/tvix/docs/component-flow.puml b/tvix/docs/src/figures/component-flow.puml
index 3bcddbe746..5b6d79b823 100644
--- a/tvix/docs/component-flow.puml
+++ b/tvix/docs/src/figures/component-flow.puml
@@ -28,7 +28,7 @@ note right
     Immediately starts streaming derivations as they are instantiated across
     the dependency graph so they can be built while the evaluation is still running.
 
-    There are two types of build requests: One for regular "fire and forget" builds
+    There are two types of build requests: One for regular "fire and forget" builds,
     and another for IFD (import from derivation).
 
     These are distinct because IFD needs to be fed back into the evaluator for
@@ -42,27 +42,13 @@ loop while has more derivations
         Coord<--Store: Success response
     else Store does not have path
         Coord-->Build: Request derivation to be built
-        note left
-            The build request optionally includes a desired store.
-            If a builder is aware of how to push to the store it will do so
-            directly when the build is finished.
-
-            If the store is not known by the builder results will be streamed
-            back to the coordinator for store addition.
-        end note
 
         alt Build failure
             Coord<--Build: Fail response
             note left: It's up to the coordinator whether to exit on build failure
         else Build success
-            alt Known store
-                Build-->Store: Push outputs to store
-                Build<--Coord: Send success & pushed response
-            else Unknown store
-                Build<--Coord: Send success & not pushed response
-                Coord<--Build: Stream build outputs
-                Coord-->Store: Push outputs to store
-            end
+            Build-->Store: Push outputs to store
+            Build<--Coord: Send success & pushed response
         end
 
     end
diff --git a/tvix/docs/src/lang-version.md b/tvix/docs/src/lang-version.md
new file mode 100644
index 0000000000..c288274c91
--- /dev/null
+++ b/tvix/docs/src/lang-version.md
@@ -0,0 +1,62 @@
+# Nix language version history
+
+The Nix language (“Nix”) has its own versioning mechanism independent from its
+most popular implementation (“C++ Nix”): `builtins.langVersion`. It has been
+increased whenever the language has changed syntactically or semantically in a
+way that would not be introspectable otherwise. In particular, this does not
+include addition (or removal) of `builtins`, as this can be introspected using
+standard attribute set operations.
+
+Changes to `builtins.langVersion` are best found by viewing the git history of
+C++ Nix using `git log -G 'mkInt\\(v, [0-9]\\)'` for `builtins.langVersion` < 7.
+After that point `git log -G 'v\\.mkInt\\([0-9]+\\)'` should work. To reduce the
+amount of false positives, specify the version number you are interested in
+explicitly.
+
+## 1
+
+The first version of the Nix language is its state at the point when
+`builtins.langVersion` was added in [8b8ee53] which was first released
+as part of C++ Nix 1.2.
+
+## 2
+
+Nix version 2 changed the behavior of `builtins.storePath`: It would now [try to
+substitute the given path if missing][storePath-substitute], instead of creating
+an evaluation failure. `builtins.langVersion` was increased in [e36229d].
+
+## 3
+
+Nix version 3 changed the behavior of the `==` behavior. Strings would now be
+considered [equal even if they had differing string context][equal-no-ctx].
+
+## 4
+
+Nix version 4 [added the float type][float] to the language.
+
+## 5
+
+The [increase of `builtins.langVersion` to 5][langVersion-5] did not signify a
+language change, but added support for structured attributes to the Nix daemon.
+Eelco Dolstra writes as to what changed:
+
+> The structured attributes support. Unfortunately that's not so much a language
+> change as a build.cc (i.e. daemon) change, but we don't really have a way to
+> express that...
+
+Maybe `builtins.nixVersion` (which was added in version 1) should have been
+used instead. In any case, the [only `langVersion` check][nixpkgs-langVersion-5]
+in nixpkgs verifies a lower bound of 5.
+
+## 6
+
+Nix version 6 added support for [comparing two lists][list-comparison].
+
+[8b8ee53]: https://github.com/nixos/nix/commit/8b8ee53bc73769bb25d967ba259dabc9b23e2e6f
+[storePath-substitute]: https://github.com/nixos/nix/commit/22d665019a3770148929b7504c73bcdbe025ec12
+[e36229d]: https://github.com/nixos/nix/commit/e36229d27f9ab508e0abf1892f3e8c263d2f8c58
+[equal-no-ctx]: https://github.com/nixos/nix/commit/ee7fe64c0ac00f2be11604a2a6509eb86dc19f0a
+[float]: https://github.com/nixos/nix/commit/14ebde52893263930cdcde1406cc91cc5c42556f
+[langVersion-5]: https://github.com/nixos/nix/commit/8191992c83bf4387b03c5fdaba818dc2b520462d
+[list-comparison]: https://github.com/nixos/nix/commit/09471d2680292af48b2788108de56a8da755d661
+[nixpkgs-langVersion-5]: https://github.com/NixOS/nixpkgs/blob/d7ac3423d321b8b145ccdd1aed9dfdb280f5e391/pkgs/build-support/closure-info.nix#L11
diff --git a/tvix/docs/language-spec.md b/tvix/docs/src/language-spec.md
index a714374933..0ff1dc491e 100644
--- a/tvix/docs/language-spec.md
+++ b/tvix/docs/src/language-spec.md
@@ -1,15 +1,4 @@
----
-title: "Specification of the Nix language"
-numbersections: true
-author:
-- tazjin
-email:
-- tazjin@tvl.su
-lang: en-GB
----
-
-The Nix Language
-================
+# Specification of the Nix Language
 
 WARNING: This document is a work in progress. Please keep an eye on
 [`topic:nix-spec`](https://cl.tvl.fyi/q/topic:nix-spec) for ongoing
diff --git a/tvix/docs/src/value-pointer-equality.md b/tvix/docs/src/value-pointer-equality.md
new file mode 100644
index 0000000000..d84efcb50c
--- /dev/null
+++ b/tvix/docs/src/value-pointer-equality.md
@@ -0,0 +1,338 @@
+# Value Pointer Equality in Nix
+
+## Introduction
+
+It is a piece of semi-obscure Nix trivia that while functions are generally not
+comparable, they can be compared in certain situations. This is actually quite an
+important fact, as it is essential for the evaluation of nixpkgs: The attribute sets
+used to represent platforms in nixpkgs, like `stdenv.buildPlatform`, contain functions,
+such as `stdenv.buildPlatform.canExecute`. When writing cross logic, one invariably
+ends up writing expressions that compare these sets, e.g. `stdenv.buildPlatform !=
+stdenv.hostPlatform`. Since attribute set equality is the equality of their attribute
+names and values, we also end up comparing the functions within them.  We can summarize
+the relevant part of this behavior for platform comparisons in the following (true)
+Nix expressions:
+
+* `stdenv.hostPlatform.canExecute != stdenv.hostPlatform.canExecute`
+* `stdenv.hostPlatform == stdenv.hostPlatform`
+
+This fact is commonly referred to as pointer equality of functions (or function pointer
+equality) which is not an entirely accurate name, as we'll see. This account of the
+behavior states that, while functions are incomparable in general, they are comparable
+insofar, as they occupy the same spot in an attribute set.
+
+However, [a maybe lesser known trick][puck-issue] is to write a function such as the
+following to allow comparing functions:
+
+```nix
+let
+  pointerEqual = lhs: rhs: { x = lhs; } == { x = rhs; };
+
+  f = name: "Hello, my name is ${name}";
+  g = name: "Hello, my name is ${name}";
+in
+[
+  (pointerEqual f f) # => true
+  (pointerEqual f g) # => false
+]
+```
+
+Here, clearly, the function is not contained at the same position in one and the same
+attribute set, but at the same position in two entirely different attribute sets. We can
+also see that we are not comparing the functions themselves (e.g. their AST), but
+rather if they are the same individual value (i.e. pointer equal).
+
+To figure out the _actual_ semantics, we'll first have a look at how value (pointer) equality
+works in C++ Nix, the only production ready Nix implementation currently available.
+
+## Nix (Pointer) Equality in C++ Nix
+
+TIP: The summary presented here is up-to-date as of 2023-06-27 and was tested
+with Nix 2.3, 2.11 and 2.15.
+
+### `EvalState::eqValues` and `ExprOpEq::eval`
+
+The function implementing equality in C++ Nix is `EvalState::eqValues` which starts with
+[the following bit of code][eqValues-pointer-eq]:
+
+```cpp
+bool EvalState::eqValues(Value & v1, Value & v2)
+{
+    forceValue(v1);
+    forceValue(v2);
+
+    /* !!! Hack to support some old broken code that relies on pointer
+       equality tests between sets.  (Specifically, builderDefs calls
+       uniqList on a list of sets.)  Will remove this eventually. */
+    if (&v1 == &v2) return true;
+```
+
+So this immediately looks more like pointer equality of arbitrary *values* instead of functions. In fact
+there is [no special code facilitating function equality][eqValues-function-eq]:
+
+```cpp
+        /* Functions are incomparable. */
+        case nFunction:
+            return false;
+```
+
+So one takeaway of this is that pointer equality is neither dependent on functions nor attribute sets.
+In fact, we can also write our `pointerEqual` function as:
+
+```nix
+lhs: rhs: [ lhs ] == [ rhs ]
+```
+
+It's interesting that `EvalState::eqValues` forces the left and right-hand value before trying pointer
+equality. It explains that `let x = throw ""; in x == x` does not evaluate successfully, but it is puzzling why
+`let f = x: x; in f == f` does not return `true`. In fact, why do we need to wrap the values in a list or
+attribute set at all for our `pointerEqual` function to work?
+
+The answer lies in [the code that evaluates `ExprOpEq`][ExprOpEq],
+i.e. an expression involving the `==` operator:
+
+```cpp
+void ExprOpEq::eval(EvalState & state, Env & env, Value & v)
+{
+    Value v1; e1->eval(state, env, v1);
+    Value v2; e2->eval(state, env, v2);
+    v.mkBool(state.eqValues(v1, v2));
+}
+```
+
+As you can see, two _distinct_ `Value` structs are created, so they can never be pointer equal even
+if the `union` inside points to the same bit of memory. We can thus understand what actually happens
+when we check the equality of an attribute set (or list), by looking at the following expression:
+
+```nix
+let
+  x = { name = throw "nameless"; };
+in
+
+x == x # => causes an evaluation error
+```
+
+Because `x` can't be pointer equal, as it'll end up in the distinct structs `v1` and `v2`, it needs to be compared
+by value. For this reason, the `name` attribute will be forced and an evaluation error caused.
+If we rewrite the expression to use…
+
+```nix
+{ inherit x; } == { inherit x; } # => true
+```
+
+…, it'll work: The two attribute sets are compared by value, but their `x` attribute turns out to be pointer
+equal _after_ forcing it. This does not throw, since forcing an attribute set does not force its attributes'
+values (as forcing a list doesn't force its elements).
+
+As we have seen, pointer equality can not only be used to compare function values, but also other
+otherwise incomparable values, such as lists and attribute sets that would cause an evaluation
+error if they were forced recursively. We can even switch out the `throw` for an `abort`. The limitation is
+of course that we need to use a value that behaves differently depending on whether it is forced
+“normally” (think `builtins.seq`) or recursively (think `builtins.deepSeq`), so thunks will generally be
+evaluated before pointer equality can kick into effect.
+
+### Other Comparisons
+
+The `!=` operator uses `EvalState::eqValues` internally as well, so it behaves exactly as `!(a == b)`.
+
+The `>`, `<`, `>=` and `<=` operators all desugar to [CompareValues][]
+eventually which generally looks at the value type before comparing. It does,
+however, rely on `EvalState::eqValues` for list comparisons
+([introduced in Nix 2.5][nix-2.5-changelog]), so it is possible to compare lists
+with e.g. functions in them, as long as they are equal by pointer:
+
+```nix
+let
+  f = x: x + 42;
+in
+
+[
+  ([ f 2 ] > [ f 1 ]) # => true
+  ([ f 2 ] > [ (x: x) 1]) # => error: cannot compare a function with a function
+  ([ f ] > [ f ]) # => false
+]
+```
+
+Finally, since `builtins.elem` relies on `EvalState::eqValues`, you can check for
+a function by pointer equality:
+
+```nix
+let
+  f = x: f x;
+in
+builtins.elem f [ f 2 3 ] # => true
+```
+
+### Pointer Equality Preserving Nix Operations
+
+We have seen that pointer equality is established by comparing the memory
+location of two C++ `Value` structs. But how does this _representation_ relate
+to Nix values _themselves_ (in the sense of a platonic ideal if you will)? In
+Nix, values have no identity (ignoring `unsafeGetAttrPos`) or memory location.
+
+Since Nix is purely functional, values can't be mutated, so they need to be
+copied frequently. With Nix being garbage collected, there is no strong
+expectation when a copy is made, we probably just hope it is done as seldomly as
+possible to save on memory. With pointer equality leaking the memory location of
+the `Value` structs to an extent, it is now suddenly our business to know
+exactly _when_ a copy of a value is made.
+
+Evaluation in C++ Nix mainly proceeds along the following [two
+functions][eval-maybeThunk].
+
+```cpp
+struct Expr
+{
+    /* … */
+    virtual void eval(EvalState & state, Env & env, Value & v);
+    virtual Value * maybeThunk(EvalState & state, Env & env);
+    /* … */
+};
+```
+
+As you can see, `Expr::eval` always takes a reference to a struct _allocated by
+the caller_ to place the evaluation result in. Anything that is processed using
+`Expr::eval` will be a copy of the `Value` struct even if the value before and
+after are the same.
+
+`Expr::maybeThunk`, on the other hand, returns a pointer to a `Value` which may
+already exist or be newly allocated. So, if evaluation passes through `maybeThunk`,
+Nix values _can_ retain their pointer equality. Since Nix is lazy, a lot of
+evaluation needs to be thunked and pass through `maybeThunk`—knowing under what
+circumstances `maybeThunk` will return a pointer to an already existing `Value`
+struct thus means knowing the circumstances under which pointer equality of a
+Nix value will be preserved in C++ Nix.
+
+The [default case][maybeThunk-default] of `Expr::maybeThunk` allocates a new
+`Value` which holds the delayed computation of the `Expr` as a thunk:
+
+```cpp
+
+Value * Expr::maybeThunk(EvalState & state, Env & env)
+{
+    Value * v = state.allocValue();
+    mkThunk(*v, env, this);
+    return v;
+}
+```
+
+Consequently, only special cased expressions could preserve pointer equality.
+These are `ExprInt`, `ExprFloat`, `ExprString`, `ExprPath`—all of which relate
+to creating new values—and [finally, `ExprVar`][maybeThunk-ExprVar]:
+
+```cpp
+Value * ExprVar::maybeThunk(EvalState & state, Env & env)
+{
+    Value * v = state.lookupVar(&env, *this, true);
+    /* The value might not be initialised in the environment yet.
+       In that case, ignore it. */
+    if (v) { state.nrAvoided++; return v; }
+    return Expr::maybeThunk(state, env);
+}
+```
+
+Here we may actually return an already existing `Value` struct. Consequently,
+accessing a value from the scope is the only thing you can do with a value in
+C++ Nix that preserves its pointer equality, as the following example shows:
+For example, using the select operator to get a value from an attribute set
+or even passing a value trough the identity function invalidates its pointer
+equality to itself (or rather, its former self).
+
+```nix
+let
+  pointerEqual = a: b: [ a ] == [ b ];
+  id = x: x;
+
+  f = _: null;
+  x = { inherit f; };
+  y = { inherit f; };
+in
+
+[
+  (pointerEqual f f)      # => true
+
+  (pointerEqual f (id f)) # => false
+
+  (pointerEqual x.f y.f)  # => false
+  (pointerEqual x.f x.f)  # => false
+
+  (pointerEqual x x)      # => true
+  (pointerEqual x y)      # => true
+]
+```
+
+In the last two cases, the example also shows that there is another way to
+preserve pointer equality: Storing a value in an attribute set (or list)
+preserves its pointer equality even if the structure holding it is modified in
+some way (as long as the value we care about is left untouched). The catch is,
+of course, that there is no way to get the value out of the structure while
+preserving pointer equality (which requires using the select operator or a call
+to `builtins.elemAt`).
+
+We initially illustrated the issue of pointer equality using the following
+true expressions:
+
+* `stdenv.hostPlatform.canExecute != stdenv.hostPlatform.canExecute`
+* `stdenv.hostPlatform == stdenv.hostPlatform`
+
+We can now add a third one, illustrating that pointer equality is invalidated
+by select operations:
+
+* `[ stdenv.hostPlatform.canExecute ] != [ stdenv.hostPlatform.canExecute ]`
+
+To summarize, pointer equality is established on the memory location of the
+`Value` struct in C++ Nix. Except for simple values (`int`, `bool`, …),
+the `Value` struct only consists of a pointer to the actual representation
+of the value (attribute set, list, function, …) and is thus cheap to copy.
+In practice, this happens when a value passes through the evaluation of
+almost any Nix expression. Only in the select cases described above
+a value preserves its pointer equality despite being unchanged by an
+expression. We can call this behavior *exterior pointer equality*.
+
+## Summary
+
+When comparing two Nix values, we must force both of them (non-recursively!), but are
+allowed to short-circuit the comparison based on pointer equality, i.e. if they are at
+the same exact value in memory, they are deemed equal immediately. This is completely
+independent of what type of value they are. If they are not pointer equal, they are
+(recursively) compared by value as expected.
+
+However, when evaluating the Nix expression `a == b`, we *must* invoke our implementation's
+value equality function in a way that `a` and `b` themselves can never be deemed pointer equal.
+Any values we encounter while recursing during the equality check must be compared by
+pointer as described above, though.
+
+## Stability of the Feature
+
+Keen readers will have noticed the following comment in the C++ Nix source code,
+indicating that pointer comparison may be removed in the future.
+
+```cpp
+    /* !!! Hack to support some old broken code that relies on pointer
+       equality tests between sets.  (Specifically, builderDefs calls
+       uniqList on a list of sets.)  Will remove this eventually. */
+```
+
+Now, I can't speak for the upstream C++ Nix developers, but sure can speculate.
+As already pointed out, this feature is currently needed for evaluating nixpkgs.
+While its use could realistically be eliminated (only bothersome spot is probably
+the `emulator` function, but that should also be doable), removing the feature
+would seriously compromise C++ Nix's ability to evaluate historical nixpkgs
+revision which is arguably a strength of the system.
+
+Another indication that it is likely here to stay is that it has already
+[outlived builderDefs][], even though
+it was (apparently) reintroduced just for this use case. More research into
+the history of this feature would still be prudent, especially the reason for
+its original introduction (maybe performance?).
+
+[puck-issue]: https://github.com/NixOS/nix/issues/3371
+[eqValues-pointer-eq]: https://github.com/NixOS/nix/blob/3c618c43c6044eda184df235c193877529e951cb/src/libexpr/eval.cc#L2401-L2404
+[eqValues-function-eq]: https://github.com/NixOS/nix/blob/3c618c43c6044eda184df235c193877529e951cb/src/libexpr/eval.cc#L2458-L2460
+[ExprOpEq]: https://github.com/NixOS/nix/blob/3c618c43c6044eda184df235c193877529e951cb/src/libexpr/eval.cc#L1822-L1827
+[outlived builderDefs]: https://github.com/NixOS/nixpkgs/issues/4210
+[CompareValues]: https://github.com/NixOS/nix/blob/3c618c43c6044eda184df235c193877529e951cb/src/libexpr/primops.cc#L569-L610
+[nix-2.5-changelog]: https://nixos.org/manual/nix/stable/release-notes/rl-2.5.html
+[eval-maybeThunk]: https://github.com/NixOS/nix/blob/3c618c43c6044eda184df235c193877529e951cb/src/libexpr/nixexpr.hh#L161-L162
+[maybeThunk-default]: https://github.com/NixOS/nix/blob/8e770dac9f68162cfbb368e53f928df491babff3/src/libexpr/eval.cc#L1076-L1081
+[maybeThunk-ExprVar]: https://github.com/NixOS/nix/blob/8e770dac9f68162cfbb368e53f928df491babff3/src/libexpr/eval.cc#L1084-L1091
diff --git a/tvix/docs/theme/highlight.js b/tvix/docs/theme/highlight.js
new file mode 100644
index 0000000000..86fb5af97a
--- /dev/null
+++ b/tvix/docs/theme/highlight.js
@@ -0,0 +1,590 @@
+/*!
+  Highlight.js v11.9.0 (git: b7ec4bfafc)
+  (c) 2006-2023 undefined and other contributors
+  License: BSD-3-Clause
+ */
+var hljs=function(){"use strict";function e(t){
+return t instanceof Map?t.clear=t.delete=t.set=()=>{
+throw Error("map is read-only")}:t instanceof Set&&(t.add=t.clear=t.delete=()=>{
+throw Error("set is read-only")
+}),Object.freeze(t),Object.getOwnPropertyNames(t).forEach((n=>{
+const i=t[n],s=typeof i;"object"!==s&&"function"!==s||Object.isFrozen(i)||e(i)
+})),t}class t{constructor(e){
+void 0===e.data&&(e.data={}),this.data=e.data,this.isMatchIgnored=!1}
+ignoreMatch(){this.isMatchIgnored=!0}}function n(e){
+return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#x27;")
+}function i(e,...t){const n=Object.create(null);for(const t in e)n[t]=e[t]
+;return t.forEach((e=>{for(const t in e)n[t]=e[t]})),n}const s=e=>!!e.scope
+;class o{constructor(e,t){
+this.buffer="",this.classPrefix=t.classPrefix,e.walk(this)}addText(e){
+this.buffer+=n(e)}openNode(e){if(!s(e))return;const t=((e,{prefix:t})=>{
+if(e.startsWith("language:"))return e.replace("language:","language-")
+;if(e.includes(".")){const n=e.split(".")
+;return[`${t}${n.shift()}`,...n.map(((e,t)=>`${e}${"_".repeat(t+1)}`))].join(" ")
+}return`${t}${e}`})(e.scope,{prefix:this.classPrefix});this.span(t)}
+closeNode(e){s(e)&&(this.buffer+="</span>")}value(){return this.buffer}span(e){
+this.buffer+=`<span class="${e}">`}}const r=(e={})=>{const t={children:[]}
+;return Object.assign(t,e),t};class a{constructor(){
+this.rootNode=r(),this.stack=[this.rootNode]}get top(){
+return this.stack[this.stack.length-1]}get root(){return this.rootNode}add(e){
+this.top.children.push(e)}openNode(e){const t=r({scope:e})
+;this.add(t),this.stack.push(t)}closeNode(){
+if(this.stack.length>1)return this.stack.pop()}closeAllNodes(){
+for(;this.closeNode(););}toJSON(){return JSON.stringify(this.rootNode,null,4)}
+walk(e){return this.constructor._walk(e,this.rootNode)}static _walk(e,t){
+return"string"==typeof t?e.addText(t):t.children&&(e.openNode(t),
+t.children.forEach((t=>this._walk(e,t))),e.closeNode(t)),e}static _collapse(e){
+"string"!=typeof e&&e.children&&(e.children.every((e=>"string"==typeof e))?e.children=[e.children.join("")]:e.children.forEach((e=>{
+a._collapse(e)})))}}class c extends a{constructor(e){super(),this.options=e}
+addText(e){""!==e&&this.add(e)}startScope(e){this.openNode(e)}endScope(){
+this.closeNode()}__addSublanguage(e,t){const n=e.root
+;t&&(n.scope="language:"+t),this.add(n)}toHTML(){
+return new o(this,this.options).value()}finalize(){
+return this.closeAllNodes(),!0}}function l(e){
+return e?"string"==typeof e?e:e.source:null}function g(e){return h("(?=",e,")")}
+function u(e){return h("(?:",e,")*")}function d(e){return h("(?:",e,")?")}
+function h(...e){return e.map((e=>l(e))).join("")}function f(...e){const t=(e=>{
+const t=e[e.length-1]
+;return"object"==typeof t&&t.constructor===Object?(e.splice(e.length-1,1),t):{}
+})(e);return"("+(t.capture?"":"?:")+e.map((e=>l(e))).join("|")+")"}
+function p(e){return RegExp(e.toString()+"|").exec("").length-1}
+const b=/\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./
+;function m(e,{joinWith:t}){let n=0;return e.map((e=>{n+=1;const t=n
+;let i=l(e),s="";for(;i.length>0;){const e=b.exec(i);if(!e){s+=i;break}
+s+=i.substring(0,e.index),
+i=i.substring(e.index+e[0].length),"\\"===e[0][0]&&e[1]?s+="\\"+(Number(e[1])+t):(s+=e[0],
+"("===e[0]&&n++)}return s})).map((e=>`(${e})`)).join(t)}
+const E="[a-zA-Z]\\w*",x="[a-zA-Z_]\\w*",w="\\b\\d+(\\.\\d+)?",y="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",_="\\b(0b[01]+)",O={
+begin:"\\\\[\\s\\S]",relevance:0},v={scope:"string",begin:"'",end:"'",
+illegal:"\\n",contains:[O]},k={scope:"string",begin:'"',end:'"',illegal:"\\n",
+contains:[O]},N=(e,t,n={})=>{const s=i({scope:"comment",begin:e,end:t,
+contains:[]},n);s.contains.push({scope:"doctag",
+begin:"[ ]*(?=(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):)",
+end:/(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):/,excludeBegin:!0,relevance:0})
+;const o=f("I","a","is","so","us","to","at","if","in","it","on",/[A-Za-z]+['](d|ve|re|ll|t|s|n)/,/[A-Za-z]+[-][a-z]+/,/[A-Za-z][a-z]{2,}/)
+;return s.contains.push({begin:h(/[ ]+/,"(",o,/[.]?[:]?([.][ ]|[ ])/,"){3}")}),s
+},S=N("//","$"),M=N("/\\*","\\*/"),R=N("#","$");var j=Object.freeze({
+__proto__:null,APOS_STRING_MODE:v,BACKSLASH_ESCAPE:O,BINARY_NUMBER_MODE:{
+scope:"number",begin:_,relevance:0},BINARY_NUMBER_RE:_,COMMENT:N,
+C_BLOCK_COMMENT_MODE:M,C_LINE_COMMENT_MODE:S,C_NUMBER_MODE:{scope:"number",
+begin:y,relevance:0},C_NUMBER_RE:y,END_SAME_AS_BEGIN:e=>Object.assign(e,{
+"on:begin":(e,t)=>{t.data._beginMatch=e[1]},"on:end":(e,t)=>{
+t.data._beginMatch!==e[1]&&t.ignoreMatch()}}),HASH_COMMENT_MODE:R,IDENT_RE:E,
+MATCH_NOTHING_RE:/\b\B/,METHOD_GUARD:{begin:"\\.\\s*"+x,relevance:0},
+NUMBER_MODE:{scope:"number",begin:w,relevance:0},NUMBER_RE:w,
+PHRASAL_WORDS_MODE:{
+begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/
+},QUOTE_STRING_MODE:k,REGEXP_MODE:{scope:"regexp",begin:/\/(?=[^/\n]*\/)/,
+end:/\/[gimuy]*/,contains:[O,{begin:/\[/,end:/\]/,relevance:0,contains:[O]}]},
+RE_STARTERS_RE:"!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",
+SHEBANG:(e={})=>{const t=/^#![ ]*\//
+;return e.binary&&(e.begin=h(t,/.*\b/,e.binary,/\b.*/)),i({scope:"meta",begin:t,
+end:/$/,relevance:0,"on:begin":(e,t)=>{0!==e.index&&t.ignoreMatch()}},e)},
+TITLE_MODE:{scope:"title",begin:E,relevance:0},UNDERSCORE_IDENT_RE:x,
+UNDERSCORE_TITLE_MODE:{scope:"title",begin:x,relevance:0}});function A(e,t){
+"."===e.input[e.index-1]&&t.ignoreMatch()}function I(e,t){
+void 0!==e.className&&(e.scope=e.className,delete e.className)}function T(e,t){
+t&&e.beginKeywords&&(e.begin="\\b("+e.beginKeywords.split(" ").join("|")+")(?!\\.)(?=\\b|\\s)",
+e.__beforeBegin=A,e.keywords=e.keywords||e.beginKeywords,delete e.beginKeywords,
+void 0===e.relevance&&(e.relevance=0))}function L(e,t){
+Array.isArray(e.illegal)&&(e.illegal=f(...e.illegal))}function B(e,t){
+if(e.match){
+if(e.begin||e.end)throw Error("begin & end are not supported with match")
+;e.begin=e.match,delete e.match}}function P(e,t){
+void 0===e.relevance&&(e.relevance=1)}const D=(e,t)=>{if(!e.beforeMatch)return
+;if(e.starts)throw Error("beforeMatch cannot be used with starts")
+;const n=Object.assign({},e);Object.keys(e).forEach((t=>{delete e[t]
+})),e.keywords=n.keywords,e.begin=h(n.beforeMatch,g(n.begin)),e.starts={
+relevance:0,contains:[Object.assign(n,{endsParent:!0})]
+},e.relevance=0,delete n.beforeMatch
+},H=["of","and","for","in","not","or","if","then","parent","list","value"],C="keyword"
+;function $(e,t,n=C){const i=Object.create(null)
+;return"string"==typeof e?s(n,e.split(" ")):Array.isArray(e)?s(n,e):Object.keys(e).forEach((n=>{
+Object.assign(i,$(e[n],t,n))})),i;function s(e,n){
+t&&(n=n.map((e=>e.toLowerCase()))),n.forEach((t=>{const n=t.split("|")
+;i[n[0]]=[e,U(n[0],n[1])]}))}}function U(e,t){
+return t?Number(t):(e=>H.includes(e.toLowerCase()))(e)?0:1}const z={},W=e=>{
+console.error(e)},X=(e,...t)=>{console.log("WARN: "+e,...t)},G=(e,t)=>{
+z[`${e}/${t}`]||(console.log(`Deprecated as of ${e}. ${t}`),z[`${e}/${t}`]=!0)
+},K=Error();function F(e,t,{key:n}){let i=0;const s=e[n],o={},r={}
+;for(let e=1;e<=t.length;e++)r[e+i]=s[e],o[e+i]=!0,i+=p(t[e-1])
+;e[n]=r,e[n]._emit=o,e[n]._multi=!0}function Z(e){(e=>{
+e.scope&&"object"==typeof e.scope&&null!==e.scope&&(e.beginScope=e.scope,
+delete e.scope)})(e),"string"==typeof e.beginScope&&(e.beginScope={
+_wrap:e.beginScope}),"string"==typeof e.endScope&&(e.endScope={_wrap:e.endScope
+}),(e=>{if(Array.isArray(e.begin)){
+if(e.skip||e.excludeBegin||e.returnBegin)throw W("skip, excludeBegin, returnBegin not compatible with beginScope: {}"),
+K
+;if("object"!=typeof e.beginScope||null===e.beginScope)throw W("beginScope must be object"),
+K;F(e,e.begin,{key:"beginScope"}),e.begin=m(e.begin,{joinWith:""})}})(e),(e=>{
+if(Array.isArray(e.end)){
+if(e.skip||e.excludeEnd||e.returnEnd)throw W("skip, excludeEnd, returnEnd not compatible with endScope: {}"),
+K
+;if("object"!=typeof e.endScope||null===e.endScope)throw W("endScope must be object"),
+K;F(e,e.end,{key:"endScope"}),e.end=m(e.end,{joinWith:""})}})(e)}function V(e){
+function t(t,n){
+return RegExp(l(t),"m"+(e.case_insensitive?"i":"")+(e.unicodeRegex?"u":"")+(n?"g":""))
+}class n{constructor(){
+this.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0}
+addRule(e,t){
+t.position=this.position++,this.matchIndexes[this.matchAt]=t,this.regexes.push([t,e]),
+this.matchAt+=p(e)+1}compile(){0===this.regexes.length&&(this.exec=()=>null)
+;const e=this.regexes.map((e=>e[1]));this.matcherRe=t(m(e,{joinWith:"|"
+}),!0),this.lastIndex=0}exec(e){this.matcherRe.lastIndex=this.lastIndex
+;const t=this.matcherRe.exec(e);if(!t)return null
+;const n=t.findIndex(((e,t)=>t>0&&void 0!==e)),i=this.matchIndexes[n]
+;return t.splice(0,n),Object.assign(t,i)}}class s{constructor(){
+this.rules=[],this.multiRegexes=[],
+this.count=0,this.lastIndex=0,this.regexIndex=0}getMatcher(e){
+if(this.multiRegexes[e])return this.multiRegexes[e];const t=new n
+;return this.rules.slice(e).forEach((([e,n])=>t.addRule(e,n))),
+t.compile(),this.multiRegexes[e]=t,t}resumingScanAtSamePosition(){
+return 0!==this.regexIndex}considerAll(){this.regexIndex=0}addRule(e,t){
+this.rules.push([e,t]),"begin"===t.type&&this.count++}exec(e){
+const t=this.getMatcher(this.regexIndex);t.lastIndex=this.lastIndex
+;let n=t.exec(e)
+;if(this.resumingScanAtSamePosition())if(n&&n.index===this.lastIndex);else{
+const t=this.getMatcher(0);t.lastIndex=this.lastIndex+1,n=t.exec(e)}
+return n&&(this.regexIndex+=n.position+1,
+this.regexIndex===this.count&&this.considerAll()),n}}
+if(e.compilerExtensions||(e.compilerExtensions=[]),
+e.contains&&e.contains.includes("self"))throw Error("ERR: contains `self` is not supported at the top-level of a language.  See documentation.")
+;return e.classNameAliases=i(e.classNameAliases||{}),function n(o,r){const a=o
+;if(o.isCompiled)return a
+;[I,B,Z,D].forEach((e=>e(o,r))),e.compilerExtensions.forEach((e=>e(o,r))),
+o.__beforeBegin=null,[T,L,P].forEach((e=>e(o,r))),o.isCompiled=!0;let c=null
+;return"object"==typeof o.keywords&&o.keywords.$pattern&&(o.keywords=Object.assign({},o.keywords),
+c=o.keywords.$pattern,
+delete o.keywords.$pattern),c=c||/\w+/,o.keywords&&(o.keywords=$(o.keywords,e.case_insensitive)),
+a.keywordPatternRe=t(c,!0),
+r&&(o.begin||(o.begin=/\B|\b/),a.beginRe=t(a.begin),o.end||o.endsWithParent||(o.end=/\B|\b/),
+o.end&&(a.endRe=t(a.end)),
+a.terminatorEnd=l(a.end)||"",o.endsWithParent&&r.terminatorEnd&&(a.terminatorEnd+=(o.end?"|":"")+r.terminatorEnd)),
+o.illegal&&(a.illegalRe=t(o.illegal)),
+o.contains||(o.contains=[]),o.contains=[].concat(...o.contains.map((e=>(e=>(e.variants&&!e.cachedVariants&&(e.cachedVariants=e.variants.map((t=>i(e,{
+variants:null},t)))),e.cachedVariants?e.cachedVariants:q(e)?i(e,{
+starts:e.starts?i(e.starts):null
+}):Object.isFrozen(e)?i(e):e))("self"===e?o:e)))),o.contains.forEach((e=>{n(e,a)
+})),o.starts&&n(o.starts,r),a.matcher=(e=>{const t=new s
+;return e.contains.forEach((e=>t.addRule(e.begin,{rule:e,type:"begin"
+}))),e.terminatorEnd&&t.addRule(e.terminatorEnd,{type:"end"
+}),e.illegal&&t.addRule(e.illegal,{type:"illegal"}),t})(a),a}(e)}function q(e){
+return!!e&&(e.endsWithParent||q(e.starts))}class J extends Error{
+constructor(e,t){super(e),this.name="HTMLInjectionError",this.html=t}}
+const Y=n,Q=i,ee=Symbol("nomatch"),te=n=>{
+const i=Object.create(null),s=Object.create(null),o=[];let r=!0
+;const a="Could not find the language '{}', did you forget to load/include a language module?",l={
+disableAutodetect:!0,name:"Plain text",contains:[]};let p={
+ignoreUnescapedHTML:!1,throwUnescapedHTML:!1,noHighlightRe:/^(no-?highlight)$/i,
+languageDetectRe:/\blang(?:uage)?-([\w-]+)\b/i,classPrefix:"hljs-",
+cssSelector:"pre code",languages:null,__emitter:c};function b(e){
+return p.noHighlightRe.test(e)}function m(e,t,n){let i="",s=""
+;"object"==typeof t?(i=e,
+n=t.ignoreIllegals,s=t.language):(G("10.7.0","highlight(lang, code, ...args) has been deprecated."),
+G("10.7.0","Please use highlight(code, options) instead.\nhttps://github.com/highlightjs/highlight.js/issues/2277"),
+s=e,i=t),void 0===n&&(n=!0);const o={code:i,language:s};N("before:highlight",o)
+;const r=o.result?o.result:E(o.language,o.code,n)
+;return r.code=o.code,N("after:highlight",r),r}function E(e,n,s,o){
+const c=Object.create(null);function l(){if(!N.keywords)return void M.addText(R)
+;let e=0;N.keywordPatternRe.lastIndex=0;let t=N.keywordPatternRe.exec(R),n=""
+;for(;t;){n+=R.substring(e,t.index)
+;const s=_.case_insensitive?t[0].toLowerCase():t[0],o=(i=s,N.keywords[i]);if(o){
+const[e,i]=o
+;if(M.addText(n),n="",c[s]=(c[s]||0)+1,c[s]<=7&&(j+=i),e.startsWith("_"))n+=t[0];else{
+const n=_.classNameAliases[e]||e;u(t[0],n)}}else n+=t[0]
+;e=N.keywordPatternRe.lastIndex,t=N.keywordPatternRe.exec(R)}var i
+;n+=R.substring(e),M.addText(n)}function g(){null!=N.subLanguage?(()=>{
+if(""===R)return;let e=null;if("string"==typeof N.subLanguage){
+if(!i[N.subLanguage])return void M.addText(R)
+;e=E(N.subLanguage,R,!0,S[N.subLanguage]),S[N.subLanguage]=e._top
+}else e=x(R,N.subLanguage.length?N.subLanguage:null)
+;N.relevance>0&&(j+=e.relevance),M.__addSublanguage(e._emitter,e.language)
+})():l(),R=""}function u(e,t){
+""!==e&&(M.startScope(t),M.addText(e),M.endScope())}function d(e,t){let n=1
+;const i=t.length-1;for(;n<=i;){if(!e._emit[n]){n++;continue}
+const i=_.classNameAliases[e[n]]||e[n],s=t[n];i?u(s,i):(R=s,l(),R=""),n++}}
+function h(e,t){
+return e.scope&&"string"==typeof e.scope&&M.openNode(_.classNameAliases[e.scope]||e.scope),
+e.beginScope&&(e.beginScope._wrap?(u(R,_.classNameAliases[e.beginScope._wrap]||e.beginScope._wrap),
+R=""):e.beginScope._multi&&(d(e.beginScope,t),R="")),N=Object.create(e,{parent:{
+value:N}}),N}function f(e,n,i){let s=((e,t)=>{const n=e&&e.exec(t)
+;return n&&0===n.index})(e.endRe,i);if(s){if(e["on:end"]){const i=new t(e)
+;e["on:end"](n,i),i.isMatchIgnored&&(s=!1)}if(s){
+for(;e.endsParent&&e.parent;)e=e.parent;return e}}
+if(e.endsWithParent)return f(e.parent,n,i)}function b(e){
+return 0===N.matcher.regexIndex?(R+=e[0],1):(T=!0,0)}function m(e){
+const t=e[0],i=n.substring(e.index),s=f(N,e,i);if(!s)return ee;const o=N
+;N.endScope&&N.endScope._wrap?(g(),
+u(t,N.endScope._wrap)):N.endScope&&N.endScope._multi?(g(),
+d(N.endScope,e)):o.skip?R+=t:(o.returnEnd||o.excludeEnd||(R+=t),
+g(),o.excludeEnd&&(R=t));do{
+N.scope&&M.closeNode(),N.skip||N.subLanguage||(j+=N.relevance),N=N.parent
+}while(N!==s.parent);return s.starts&&h(s.starts,e),o.returnEnd?0:t.length}
+let w={};function y(i,o){const a=o&&o[0];if(R+=i,null==a)return g(),0
+;if("begin"===w.type&&"end"===o.type&&w.index===o.index&&""===a){
+if(R+=n.slice(o.index,o.index+1),!r){const t=Error(`0 width match regex (${e})`)
+;throw t.languageName=e,t.badRule=w.rule,t}return 1}
+if(w=o,"begin"===o.type)return(e=>{
+const n=e[0],i=e.rule,s=new t(i),o=[i.__beforeBegin,i["on:begin"]]
+;for(const t of o)if(t&&(t(e,s),s.isMatchIgnored))return b(n)
+;return i.skip?R+=n:(i.excludeBegin&&(R+=n),
+g(),i.returnBegin||i.excludeBegin||(R=n)),h(i,e),i.returnBegin?0:n.length})(o)
+;if("illegal"===o.type&&!s){
+const e=Error('Illegal lexeme "'+a+'" for mode "'+(N.scope||"<unnamed>")+'"')
+;throw e.mode=N,e}if("end"===o.type){const e=m(o);if(e!==ee)return e}
+if("illegal"===o.type&&""===a)return 1
+;if(I>1e5&&I>3*o.index)throw Error("potential infinite loop, way more iterations than matches")
+;return R+=a,a.length}const _=O(e)
+;if(!_)throw W(a.replace("{}",e)),Error('Unknown language: "'+e+'"')
+;const v=V(_);let k="",N=o||v;const S={},M=new p.__emitter(p);(()=>{const e=[]
+;for(let t=N;t!==_;t=t.parent)t.scope&&e.unshift(t.scope)
+;e.forEach((e=>M.openNode(e)))})();let R="",j=0,A=0,I=0,T=!1;try{
+if(_.__emitTokens)_.__emitTokens(n,M);else{for(N.matcher.considerAll();;){
+I++,T?T=!1:N.matcher.considerAll(),N.matcher.lastIndex=A
+;const e=N.matcher.exec(n);if(!e)break;const t=y(n.substring(A,e.index),e)
+;A=e.index+t}y(n.substring(A))}return M.finalize(),k=M.toHTML(),{language:e,
+value:k,relevance:j,illegal:!1,_emitter:M,_top:N}}catch(t){
+if(t.message&&t.message.includes("Illegal"))return{language:e,value:Y(n),
+illegal:!0,relevance:0,_illegalBy:{message:t.message,index:A,
+context:n.slice(A-100,A+100),mode:t.mode,resultSoFar:k},_emitter:M};if(r)return{
+language:e,value:Y(n),illegal:!1,relevance:0,errorRaised:t,_emitter:M,_top:N}
+;throw t}}function x(e,t){t=t||p.languages||Object.keys(i);const n=(e=>{
+const t={value:Y(e),illegal:!1,relevance:0,_top:l,_emitter:new p.__emitter(p)}
+;return t._emitter.addText(e),t})(e),s=t.filter(O).filter(k).map((t=>E(t,e,!1)))
+;s.unshift(n);const o=s.sort(((e,t)=>{
+if(e.relevance!==t.relevance)return t.relevance-e.relevance
+;if(e.language&&t.language){if(O(e.language).supersetOf===t.language)return 1
+;if(O(t.language).supersetOf===e.language)return-1}return 0})),[r,a]=o,c=r
+;return c.secondBest=a,c}function w(e){let t=null;const n=(e=>{
+let t=e.className+" ";t+=e.parentNode?e.parentNode.className:""
+;const n=p.languageDetectRe.exec(t);if(n){const t=O(n[1])
+;return t||(X(a.replace("{}",n[1])),
+X("Falling back to no-highlight mode for this block.",e)),t?n[1]:"no-highlight"}
+return t.split(/\s+/).find((e=>b(e)||O(e)))})(e);if(b(n))return
+;if(N("before:highlightElement",{el:e,language:n
+}),e.dataset.highlighted)return void console.log("Element previously highlighted. To highlight again, first unset `dataset.highlighted`.",e)
+;if(e.children.length>0&&(p.ignoreUnescapedHTML||(console.warn("One of your code blocks includes unescaped HTML. This is a potentially serious security risk."),
+console.warn("https://github.com/highlightjs/highlight.js/wiki/security"),
+console.warn("The element with unescaped HTML:"),
+console.warn(e)),p.throwUnescapedHTML))throw new J("One of your code blocks includes unescaped HTML.",e.innerHTML)
+;t=e;const i=t.textContent,o=n?m(i,{language:n,ignoreIllegals:!0}):x(i)
+;e.innerHTML=o.value,e.dataset.highlighted="yes",((e,t,n)=>{const i=t&&s[t]||n
+;e.classList.add("hljs"),e.classList.add("language-"+i)
+})(e,n,o.language),e.result={language:o.language,re:o.relevance,
+relevance:o.relevance},o.secondBest&&(e.secondBest={
+language:o.secondBest.language,relevance:o.secondBest.relevance
+}),N("after:highlightElement",{el:e,result:o,text:i})}let y=!1;function _(){
+"loading"!==document.readyState?document.querySelectorAll(p.cssSelector).forEach(w):y=!0
+}function O(e){return e=(e||"").toLowerCase(),i[e]||i[s[e]]}
+function v(e,{languageName:t}){"string"==typeof e&&(e=[e]),e.forEach((e=>{
+s[e.toLowerCase()]=t}))}function k(e){const t=O(e)
+;return t&&!t.disableAutodetect}function N(e,t){const n=e;o.forEach((e=>{
+e[n]&&e[n](t)}))}
+"undefined"!=typeof window&&window.addEventListener&&window.addEventListener("DOMContentLoaded",(()=>{
+y&&_()}),!1),Object.assign(n,{highlight:m,highlightAuto:x,highlightAll:_,
+highlightElement:w,
+highlightBlock:e=>(G("10.7.0","highlightBlock will be removed entirely in v12.0"),
+G("10.7.0","Please use highlightElement now."),w(e)),configure:e=>{p=Q(p,e)},
+initHighlighting:()=>{
+_(),G("10.6.0","initHighlighting() deprecated.  Use highlightAll() now.")},
+initHighlightingOnLoad:()=>{
+_(),G("10.6.0","initHighlightingOnLoad() deprecated.  Use highlightAll() now.")
+},registerLanguage:(e,t)=>{let s=null;try{s=t(n)}catch(t){
+if(W("Language definition for '{}' could not be registered.".replace("{}",e)),
+!r)throw t;W(t),s=l}
+s.name||(s.name=e),i[e]=s,s.rawDefinition=t.bind(null,n),s.aliases&&v(s.aliases,{
+languageName:e})},unregisterLanguage:e=>{delete i[e]
+;for(const t of Object.keys(s))s[t]===e&&delete s[t]},
+listLanguages:()=>Object.keys(i),getLanguage:O,registerAliases:v,
+autoDetection:k,inherit:Q,addPlugin:e=>{(e=>{
+e["before:highlightBlock"]&&!e["before:highlightElement"]&&(e["before:highlightElement"]=t=>{
+e["before:highlightBlock"](Object.assign({block:t.el},t))
+}),e["after:highlightBlock"]&&!e["after:highlightElement"]&&(e["after:highlightElement"]=t=>{
+e["after:highlightBlock"](Object.assign({block:t.el},t))})})(e),o.push(e)},
+removePlugin:e=>{const t=o.indexOf(e);-1!==t&&o.splice(t,1)}}),n.debugMode=()=>{
+r=!1},n.safeMode=()=>{r=!0},n.versionString="11.9.0",n.regex={concat:h,
+lookahead:g,either:f,optional:d,anyNumberOfTimes:u}
+;for(const t in j)"object"==typeof j[t]&&e(j[t]);return Object.assign(n,j),n
+},ne=te({});return ne.newInstance=()=>te({}),ne}()
+;"object"==typeof exports&&"undefined"!=typeof module&&(module.exports=hljs);/*! `bash` grammar compiled for Highlight.js 11.9.0 */
+(()=>{var e=(()=>{"use strict";return e=>{const s=e.regex,t={},n={begin:/\$\{/,
+end:/\}/,contains:["self",{begin:/:-/,contains:[t]}]};Object.assign(t,{
+className:"variable",variants:[{
+begin:s.concat(/\$[\w\d#@][\w\d_]*/,"(?![\\w\\d])(?![$])")},n]});const a={
+className:"subst",begin:/\$\(/,end:/\)/,contains:[e.BACKSLASH_ESCAPE]
+},i=e.inherit(e.COMMENT(),{match:[/(^|\s)/,/#.*$/],scope:{2:"comment"}}),c={
+begin:/<<-?\s*(?=\w+)/,starts:{contains:[e.END_SAME_AS_BEGIN({begin:/(\w+)/,
+end:/(\w+)/,className:"string"})]}},o={className:"string",begin:/"/,end:/"/,
+contains:[e.BACKSLASH_ESCAPE,t,a]};a.contains.push(o);const r={begin:/\$?\(\(/,
+end:/\)\)/,contains:[{begin:/\d+#[0-9a-f]+/,className:"number"},e.NUMBER_MODE,t]
+},l=e.SHEBANG({binary:"(fish|bash|zsh|sh|csh|ksh|tcsh|dash|scsh)",relevance:10
+}),m={className:"function",begin:/\w[\w\d_]*\s*\(\s*\)\s*\{/,returnBegin:!0,
+contains:[e.inherit(e.TITLE_MODE,{begin:/\w[\w\d_]*/})],relevance:0};return{
+name:"Bash",aliases:["sh"],keywords:{$pattern:/\b[a-z][a-z0-9._-]+\b/,
+keyword:["if","then","else","elif","fi","for","while","until","in","do","done","case","esac","function","select"],
+literal:["true","false"],
+built_in:["break","cd","continue","eval","exec","exit","export","getopts","hash","pwd","readonly","return","shift","test","times","trap","umask","unset","alias","bind","builtin","caller","command","declare","echo","enable","help","let","local","logout","mapfile","printf","read","readarray","source","type","typeset","ulimit","unalias","set","shopt","autoload","bg","bindkey","bye","cap","chdir","clone","comparguments","compcall","compctl","compdescribe","compfiles","compgroups","compquote","comptags","comptry","compvalues","dirs","disable","disown","echotc","echoti","emulate","fc","fg","float","functions","getcap","getln","history","integer","jobs","kill","limit","log","noglob","popd","print","pushd","pushln","rehash","sched","setcap","setopt","stat","suspend","ttyctl","unfunction","unhash","unlimit","unsetopt","vared","wait","whence","where","which","zcompile","zformat","zftp","zle","zmodload","zparseopts","zprof","zpty","zregexparse","zsocket","zstyle","ztcp","chcon","chgrp","chown","chmod","cp","dd","df","dir","dircolors","ln","ls","mkdir","mkfifo","mknod","mktemp","mv","realpath","rm","rmdir","shred","sync","touch","truncate","vdir","b2sum","base32","base64","cat","cksum","comm","csplit","cut","expand","fmt","fold","head","join","md5sum","nl","numfmt","od","paste","ptx","pr","sha1sum","sha224sum","sha256sum","sha384sum","sha512sum","shuf","sort","split","sum","tac","tail","tr","tsort","unexpand","uniq","wc","arch","basename","chroot","date","dirname","du","echo","env","expr","factor","groups","hostid","id","link","logname","nice","nohup","nproc","pathchk","pinky","printenv","printf","pwd","readlink","runcon","seq","sleep","stat","stdbuf","stty","tee","test","timeout","tty","uname","unlink","uptime","users","who","whoami","yes"]
+},contains:[l,e.SHEBANG(),m,r,i,c,{match:/(\/[a-z._-]+)+/},o,{match:/\\"/},{
+className:"string",begin:/'/,end:/'/},{match:/\\'/},t]}}})()
+;hljs.registerLanguage("bash",e)})();/*! `c` grammar compiled for Highlight.js 11.9.0 */
+(()=>{var e=(()=>{"use strict";return e=>{const n=e.regex,t=e.COMMENT("//","$",{
+contains:[{begin:/\\\n/}]
+}),s="decltype\\(auto\\)",a="[a-zA-Z_]\\w*::",r="("+s+"|"+n.optional(a)+"[a-zA-Z_]\\w*"+n.optional("<[^<>]+>")+")",i={
+className:"type",variants:[{begin:"\\b[a-z\\d_]*_t\\b"},{
+match:/\batomic_[a-z]{3,6}\b/}]},l={className:"string",variants:[{
+begin:'(u8?|U|L)?"',end:'"',illegal:"\\n",contains:[e.BACKSLASH_ESCAPE]},{
+begin:"(u8?|U|L)?'(\\\\(x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4,8}|[0-7]{3}|\\S)|.)",
+end:"'",illegal:"."},e.END_SAME_AS_BEGIN({
+begin:/(?:u8?|U|L)?R"([^()\\ ]{0,16})\(/,end:/\)([^()\\ ]{0,16})"/})]},o={
+className:"number",variants:[{begin:"\\b(0b[01']+)"},{
+begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)((ll|LL|l|L)(u|U)?|(u|U)(ll|LL|l|L)?|f|F|b|B)"
+},{
+begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"
+}],relevance:0},c={className:"meta",begin:/#\s*[a-z]+\b/,end:/$/,keywords:{
+keyword:"if else elif endif define undef warning error line pragma _Pragma ifdef ifndef include"
+},contains:[{begin:/\\\n/,relevance:0},e.inherit(l,{className:"string"}),{
+className:"string",begin:/<.*?>/},t,e.C_BLOCK_COMMENT_MODE]},d={
+className:"title",begin:n.optional(a)+e.IDENT_RE,relevance:0
+},g=n.optional(a)+e.IDENT_RE+"\\s*\\(",u={
+keyword:["asm","auto","break","case","continue","default","do","else","enum","extern","for","fortran","goto","if","inline","register","restrict","return","sizeof","struct","switch","typedef","union","volatile","while","_Alignas","_Alignof","_Atomic","_Generic","_Noreturn","_Static_assert","_Thread_local","alignas","alignof","noreturn","static_assert","thread_local","_Pragma"],
+type:["float","double","signed","unsigned","int","short","long","char","void","_Bool","_Complex","_Imaginary","_Decimal32","_Decimal64","_Decimal128","const","static","complex","bool","imaginary"],
+literal:"true false NULL",
+built_in:"std string wstring cin cout cerr clog stdin stdout stderr stringstream istringstream ostringstream auto_ptr deque list queue stack vector map set pair bitset multiset multimap unordered_set unordered_map unordered_multiset unordered_multimap priority_queue make_pair array shared_ptr abort terminate abs acos asin atan2 atan calloc ceil cosh cos exit exp fabs floor fmod fprintf fputs free frexp fscanf future isalnum isalpha iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit tolower toupper labs ldexp log10 log malloc realloc memchr memcmp memcpy memset modf pow printf putchar puts scanf sinh sin snprintf sprintf sqrt sscanf strcat strchr strcmp strcpy strcspn strlen strncat strncmp strncpy strpbrk strrchr strspn strstr tanh tan vfprintf vprintf vsprintf endl initializer_list unique_ptr"
+},m=[c,i,t,e.C_BLOCK_COMMENT_MODE,o,l],_={variants:[{begin:/=/,end:/;/},{
+begin:/\(/,end:/\)/},{beginKeywords:"new throw return else",end:/;/}],
+keywords:u,contains:m.concat([{begin:/\(/,end:/\)/,keywords:u,
+contains:m.concat(["self"]),relevance:0}]),relevance:0},p={
+begin:"("+r+"[\\*&\\s]+)+"+g,returnBegin:!0,end:/[{;=]/,excludeEnd:!0,
+keywords:u,illegal:/[^\w\s\*&:<>.]/,contains:[{begin:s,keywords:u,relevance:0},{
+begin:g,returnBegin:!0,contains:[e.inherit(d,{className:"title.function"})],
+relevance:0},{relevance:0,match:/,/},{className:"params",begin:/\(/,end:/\)/,
+keywords:u,relevance:0,contains:[t,e.C_BLOCK_COMMENT_MODE,l,o,i,{begin:/\(/,
+end:/\)/,keywords:u,relevance:0,contains:["self",t,e.C_BLOCK_COMMENT_MODE,l,o,i]
+}]},i,t,e.C_BLOCK_COMMENT_MODE,c]};return{name:"C",aliases:["h"],keywords:u,
+disableAutodetect:!0,illegal:"</",contains:[].concat(_,p,m,[c,{
+begin:e.IDENT_RE+"::",keywords:u},{className:"class",
+beginKeywords:"enum class struct union",end:/[{;:<>=]/,contains:[{
+beginKeywords:"final class struct"},e.TITLE_MODE]}]),exports:{preprocessor:c,
+strings:l,keywords:u}}}})();hljs.registerLanguage("c",e)})();/*! `cpp` grammar compiled for Highlight.js 11.9.0 */
+(()=>{var e=(()=>{"use strict";return e=>{const t=e.regex,a=e.COMMENT("//","$",{
+contains:[{begin:/\\\n/}]
+}),n="decltype\\(auto\\)",r="[a-zA-Z_]\\w*::",i="(?!struct)("+n+"|"+t.optional(r)+"[a-zA-Z_]\\w*"+t.optional("<[^<>]+>")+")",s={
+className:"type",begin:"\\b[a-z\\d_]*_t\\b"},c={className:"string",variants:[{
+begin:'(u8?|U|L)?"',end:'"',illegal:"\\n",contains:[e.BACKSLASH_ESCAPE]},{
+begin:"(u8?|U|L)?'(\\\\(x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4,8}|[0-7]{3}|\\S)|.)",
+end:"'",illegal:"."},e.END_SAME_AS_BEGIN({
+begin:/(?:u8?|U|L)?R"([^()\\ ]{0,16})\(/,end:/\)([^()\\ ]{0,16})"/})]},o={
+className:"number",variants:[{
+begin:"[+-]?(?:(?:[0-9](?:'?[0-9])*\\.(?:[0-9](?:'?[0-9])*)?|\\.[0-9](?:'?[0-9])*)(?:[Ee][+-]?[0-9](?:'?[0-9])*)?|[0-9](?:'?[0-9])*[Ee][+-]?[0-9](?:'?[0-9])*|0[Xx](?:[0-9A-Fa-f](?:'?[0-9A-Fa-f])*(?:\\.(?:[0-9A-Fa-f](?:'?[0-9A-Fa-f])*)?)?|\\.[0-9A-Fa-f](?:'?[0-9A-Fa-f])*)[Pp][+-]?[0-9](?:'?[0-9])*)(?:[Ff](?:16|32|64|128)?|(BF|bf)16|[Ll]|)"
+},{
+begin:"[+-]?\\b(?:0[Bb][01](?:'?[01])*|0[Xx][0-9A-Fa-f](?:'?[0-9A-Fa-f])*|0(?:'?[0-7])*|[1-9](?:'?[0-9])*)(?:[Uu](?:LL?|ll?)|[Uu][Zz]?|(?:LL?|ll?)[Uu]?|[Zz][Uu]|)"
+}],relevance:0},l={className:"meta",begin:/#\s*[a-z]+\b/,end:/$/,keywords:{
+keyword:"if else elif endif define undef warning error line pragma _Pragma ifdef ifndef include"
+},contains:[{begin:/\\\n/,relevance:0},e.inherit(c,{className:"string"}),{
+className:"string",begin:/<.*?>/},a,e.C_BLOCK_COMMENT_MODE]},u={
+className:"title",begin:t.optional(r)+e.IDENT_RE,relevance:0
+},d=t.optional(r)+e.IDENT_RE+"\\s*\\(",p={
+type:["bool","char","char16_t","char32_t","char8_t","double","float","int","long","short","void","wchar_t","unsigned","signed","const","static"],
+keyword:["alignas","alignof","and","and_eq","asm","atomic_cancel","atomic_commit","atomic_noexcept","auto","bitand","bitor","break","case","catch","class","co_await","co_return","co_yield","compl","concept","const_cast|10","consteval","constexpr","constinit","continue","decltype","default","delete","do","dynamic_cast|10","else","enum","explicit","export","extern","false","final","for","friend","goto","if","import","inline","module","mutable","namespace","new","noexcept","not","not_eq","nullptr","operator","or","or_eq","override","private","protected","public","reflexpr","register","reinterpret_cast|10","requires","return","sizeof","static_assert","static_cast|10","struct","switch","synchronized","template","this","thread_local","throw","transaction_safe","transaction_safe_dynamic","true","try","typedef","typeid","typename","union","using","virtual","volatile","while","xor","xor_eq"],
+literal:["NULL","false","nullopt","nullptr","true"],built_in:["_Pragma"],
+_type_hints:["any","auto_ptr","barrier","binary_semaphore","bitset","complex","condition_variable","condition_variable_any","counting_semaphore","deque","false_type","future","imaginary","initializer_list","istringstream","jthread","latch","lock_guard","multimap","multiset","mutex","optional","ostringstream","packaged_task","pair","promise","priority_queue","queue","recursive_mutex","recursive_timed_mutex","scoped_lock","set","shared_future","shared_lock","shared_mutex","shared_timed_mutex","shared_ptr","stack","string_view","stringstream","timed_mutex","thread","true_type","tuple","unique_lock","unique_ptr","unordered_map","unordered_multimap","unordered_multiset","unordered_set","variant","vector","weak_ptr","wstring","wstring_view"]
+},_={className:"function.dispatch",relevance:0,keywords:{
+_hint:["abort","abs","acos","apply","as_const","asin","atan","atan2","calloc","ceil","cerr","cin","clog","cos","cosh","cout","declval","endl","exchange","exit","exp","fabs","floor","fmod","forward","fprintf","fputs","free","frexp","fscanf","future","invoke","isalnum","isalpha","iscntrl","isdigit","isgraph","islower","isprint","ispunct","isspace","isupper","isxdigit","labs","launder","ldexp","log","log10","make_pair","make_shared","make_shared_for_overwrite","make_tuple","make_unique","malloc","memchr","memcmp","memcpy","memset","modf","move","pow","printf","putchar","puts","realloc","scanf","sin","sinh","snprintf","sprintf","sqrt","sscanf","std","stderr","stdin","stdout","strcat","strchr","strcmp","strcpy","strcspn","strlen","strncat","strncmp","strncpy","strpbrk","strrchr","strspn","strstr","swap","tan","tanh","terminate","to_underlying","tolower","toupper","vfprintf","visit","vprintf","vsprintf"]
+},
+begin:t.concat(/\b/,/(?!decltype)/,/(?!if)/,/(?!for)/,/(?!switch)/,/(?!while)/,e.IDENT_RE,t.lookahead(/(<[^<>]+>|)\s*\(/))
+},m=[_,l,s,a,e.C_BLOCK_COMMENT_MODE,o,c],f={variants:[{begin:/=/,end:/;/},{
+begin:/\(/,end:/\)/},{beginKeywords:"new throw return else",end:/;/}],
+keywords:p,contains:m.concat([{begin:/\(/,end:/\)/,keywords:p,
+contains:m.concat(["self"]),relevance:0}]),relevance:0},g={className:"function",
+begin:"("+i+"[\\*&\\s]+)+"+d,returnBegin:!0,end:/[{;=]/,excludeEnd:!0,
+keywords:p,illegal:/[^\w\s\*&:<>.]/,contains:[{begin:n,keywords:p,relevance:0},{
+begin:d,returnBegin:!0,contains:[u],relevance:0},{begin:/::/,relevance:0},{
+begin:/:/,endsWithParent:!0,contains:[c,o]},{relevance:0,match:/,/},{
+className:"params",begin:/\(/,end:/\)/,keywords:p,relevance:0,
+contains:[a,e.C_BLOCK_COMMENT_MODE,c,o,s,{begin:/\(/,end:/\)/,keywords:p,
+relevance:0,contains:["self",a,e.C_BLOCK_COMMENT_MODE,c,o,s]}]
+},s,a,e.C_BLOCK_COMMENT_MODE,l]};return{name:"C++",
+aliases:["cc","c++","h++","hpp","hh","hxx","cxx"],keywords:p,illegal:"</",
+classNameAliases:{"function.dispatch":"built_in"},
+contains:[].concat(f,g,_,m,[l,{
+begin:"\\b(deque|list|queue|priority_queue|pair|stack|vector|map|set|bitset|multiset|multimap|unordered_map|unordered_set|unordered_multiset|unordered_multimap|array|tuple|optional|variant|function)\\s*<(?!<)",
+end:">",keywords:p,contains:["self",s]},{begin:e.IDENT_RE+"::",keywords:p},{
+match:[/\b(?:enum(?:\s+(?:class|struct))?|class|struct|union)/,/\s+/,/\w+/],
+className:{1:"keyword",3:"title.class"}}])}}})();hljs.registerLanguage("cpp",e)
+})();/*! `diff` grammar compiled for Highlight.js 11.9.0 */
+(()=>{var e=(()=>{"use strict";return e=>{const a=e.regex;return{name:"Diff",
+aliases:["patch"],contains:[{className:"meta",relevance:10,
+match:a.either(/^@@ +-\d+,\d+ +\+\d+,\d+ +@@/,/^\*\*\* +\d+,\d+ +\*\*\*\*$/,/^--- +\d+,\d+ +----$/)
+},{className:"comment",variants:[{
+begin:a.either(/Index: /,/^index/,/={3,}/,/^-{3}/,/^\*{3} /,/^\+{3}/,/^diff --git/),
+end:/$/},{match:/^\*{15}$/}]},{className:"addition",begin:/^\+/,end:/$/},{
+className:"deletion",begin:/^-/,end:/$/},{className:"addition",begin:/^!/,
+end:/$/}]}}})();hljs.registerLanguage("diff",e)})();/*! `go` grammar compiled for Highlight.js 11.9.0 */
+(()=>{var e=(()=>{"use strict";return e=>{const n={
+keyword:["break","case","chan","const","continue","default","defer","else","fallthrough","for","func","go","goto","if","import","interface","map","package","range","return","select","struct","switch","type","var"],
+type:["bool","byte","complex64","complex128","error","float32","float64","int8","int16","int32","int64","string","uint8","uint16","uint32","uint64","int","uint","uintptr","rune"],
+literal:["true","false","iota","nil"],
+built_in:["append","cap","close","complex","copy","imag","len","make","new","panic","print","println","real","recover","delete"]
+};return{name:"Go",aliases:["golang"],keywords:n,illegal:"</",
+contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{className:"string",
+variants:[e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,{begin:"`",end:"`"}]},{
+className:"number",variants:[{begin:e.C_NUMBER_RE+"[i]",relevance:1
+},e.C_NUMBER_MODE]},{begin:/:=/},{className:"function",beginKeywords:"func",
+end:"\\s*(\\{|$)",excludeEnd:!0,contains:[e.TITLE_MODE,{className:"params",
+begin:/\(/,end:/\)/,endsParent:!0,keywords:n,illegal:/["']/}]}]}}})()
+;hljs.registerLanguage("go",e)})();/*! `ini` grammar compiled for Highlight.js 11.9.0 */
+(()=>{var e=(()=>{"use strict";return e=>{const n=e.regex,a={className:"number",
+relevance:0,variants:[{begin:/([+-]+)?[\d]+_[\d_]+/},{begin:e.NUMBER_RE}]
+},s=e.COMMENT();s.variants=[{begin:/;/,end:/$/},{begin:/#/,end:/$/}];const i={
+className:"variable",variants:[{begin:/\$[\w\d"][\w\d_]*/},{begin:/\$\{(.*?)\}/
+}]},t={className:"literal",begin:/\bon|off|true|false|yes|no\b/},r={
+className:"string",contains:[e.BACKSLASH_ESCAPE],variants:[{begin:"'''",
+end:"'''",relevance:10},{begin:'"""',end:'"""',relevance:10},{begin:'"',end:'"'
+},{begin:"'",end:"'"}]},l={begin:/\[/,end:/\]/,contains:[s,t,i,r,a,"self"],
+relevance:0},c=n.either(/[A-Za-z0-9_-]+/,/"(\\"|[^"])*"/,/'[^']*'/);return{
+name:"TOML, also INI",aliases:["toml"],case_insensitive:!0,illegal:/\S/,
+contains:[s,{className:"section",begin:/\[+/,end:/\]+/},{
+begin:n.concat(c,"(\\s*\\.\\s*",c,")*",n.lookahead(/\s*=\s*[^#\s]/)),
+className:"attr",starts:{end:/$/,contains:[s,l,t,i,r,a]}}]}}})()
+;hljs.registerLanguage("ini",e)})();/*! `json` grammar compiled for Highlight.js 11.9.0 */
+(()=>{var e=(()=>{"use strict";return e=>{const a=["true","false","null"],n={
+scope:"literal",beginKeywords:a.join(" ")};return{name:"JSON",keywords:{
+literal:a},contains:[{className:"attr",begin:/"(\\.|[^\\"\r\n])*"(?=\s*:)/,
+relevance:1.01},{match:/[{}[\],:]/,className:"punctuation",relevance:0
+},e.QUOTE_STRING_MODE,n,e.C_NUMBER_MODE,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE],
+illegal:"\\S"}}})();hljs.registerLanguage("json",e)})();/*! `markdown` grammar compiled for Highlight.js 11.9.0 */
+(()=>{var e=(()=>{"use strict";return e=>{const n={begin:/<\/?[A-Za-z_]/,
+end:">",subLanguage:"xml",relevance:0},a={variants:[{begin:/\[.+?\]\[.*?\]/,
+relevance:0},{
+begin:/\[.+?\]\(((data|javascript|mailto):|(?:http|ftp)s?:\/\/).*?\)/,
+relevance:2},{
+begin:e.regex.concat(/\[.+?\]\(/,/[A-Za-z][A-Za-z0-9+.-]*/,/:\/\/.*?\)/),
+relevance:2},{begin:/\[.+?\]\([./?&#].*?\)/,relevance:1},{
+begin:/\[.*?\]\(.*?\)/,relevance:0}],returnBegin:!0,contains:[{match:/\[(?=\])/
+},{className:"string",relevance:0,begin:"\\[",end:"\\]",excludeBegin:!0,
+returnEnd:!0},{className:"link",relevance:0,begin:"\\]\\(",end:"\\)",
+excludeBegin:!0,excludeEnd:!0},{className:"symbol",relevance:0,begin:"\\]\\[",
+end:"\\]",excludeBegin:!0,excludeEnd:!0}]},i={className:"strong",contains:[],
+variants:[{begin:/_{2}(?!\s)/,end:/_{2}/},{begin:/\*{2}(?!\s)/,end:/\*{2}/}]
+},s={className:"emphasis",contains:[],variants:[{begin:/\*(?![*\s])/,end:/\*/},{
+begin:/_(?![_\s])/,end:/_/,relevance:0}]},c=e.inherit(i,{contains:[]
+}),t=e.inherit(s,{contains:[]});i.contains.push(t),s.contains.push(c)
+;let g=[n,a];return[i,s,c,t].forEach((e=>{e.contains=e.contains.concat(g)
+})),g=g.concat(i,s),{name:"Markdown",aliases:["md","mkdown","mkd"],contains:[{
+className:"section",variants:[{begin:"^#{1,6}",end:"$",contains:g},{
+begin:"(?=^.+?\\n[=-]{2,}$)",contains:[{begin:"^[=-]*$"},{begin:"^",end:"\\n",
+contains:g}]}]},n,{className:"bullet",begin:"^[ \t]*([*+-]|(\\d+\\.))(?=\\s+)",
+end:"\\s+",excludeEnd:!0},i,s,{className:"quote",begin:"^>\\s+",contains:g,
+end:"$"},{className:"code",variants:[{begin:"(`{3,})[^`](.|\\n)*?\\1`*[ ]*"},{
+begin:"(~{3,})[^~](.|\\n)*?\\1~*[ ]*"},{begin:"```",end:"```+[ ]*$"},{
+begin:"~~~",end:"~~~+[ ]*$"},{begin:"`.+?`"},{begin:"(?=^( {4}|\\t))",
+contains:[{begin:"^( {4}|\\t)",end:"(\\n)$"}],relevance:0}]},{
+begin:"^[-\\*]{3,}",end:"$"},a,{begin:/^\[[^\n]+\]:/,returnBegin:!0,contains:[{
+className:"symbol",begin:/\[/,end:/\]/,excludeBegin:!0,excludeEnd:!0},{
+className:"link",begin:/:\s*/,end:/$/,excludeBegin:!0}]}]}}})()
+;hljs.registerLanguage("markdown",e)})();/*! `nix` grammar compiled for Highlight.js 11.9.0 */
+(()=>{var e=(()=>{"use strict";return e=>{const n={
+keyword:["rec","with","let","in","inherit","assert","if","else","then"],
+literal:["true","false","or","and","null"],
+built_in:["import","abort","baseNameOf","dirOf","isNull","builtins","map","removeAttrs","throw","toString","derivation"]
+},s={className:"subst",begin:/\$\{/,end:/\}/,keywords:n},a={className:"string",
+contains:[{className:"char.escape",begin:/''\$/},s],variants:[{begin:"''",
+end:"''"},{begin:'"',end:'"'}]
+},i=[e.NUMBER_MODE,e.HASH_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,a,{
+begin:/[a-zA-Z0-9-_]+(\s*=)/,returnBegin:!0,relevance:0,contains:[{
+className:"attr",begin:/\S+/,relevance:.2}]}];return s.contains=i,{name:"Nix",
+aliases:["nixos"],keywords:n,contains:i}}})();hljs.registerLanguage("nix",e)
+})();/*! `protobuf` grammar compiled for Highlight.js 11.9.0 */
+(()=>{var e=(()=>{"use strict";return e=>{const s={
+match:[/(message|enum|service)\s+/,e.IDENT_RE],scope:{1:"keyword",
+2:"title.class"}};return{name:"Protocol Buffers",aliases:["proto"],keywords:{
+keyword:["package","import","option","optional","required","repeated","group","oneof"],
+type:["double","float","int32","int64","uint32","uint64","sint32","sint64","fixed32","fixed64","sfixed32","sfixed64","bool","string","bytes"],
+literal:["true","false"]},
+contains:[e.QUOTE_STRING_MODE,e.NUMBER_MODE,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,s,{
+className:"function",beginKeywords:"rpc",end:/[{;]/,excludeEnd:!0,
+keywords:"rpc returns"},{begin:/^\s*[A-Z_]+(?=\s*=[^\n]+;$)/}]}}})()
+;hljs.registerLanguage("protobuf",e)})();/*! `rust` grammar compiled for Highlight.js 11.9.0 */
+(()=>{var e=(()=>{"use strict";return e=>{const t=e.regex,n={
+className:"title.function.invoke",relevance:0,
+begin:t.concat(/\b/,/(?!let|for|while|if|else|match\b)/,e.IDENT_RE,t.lookahead(/\s*\(/))
+},a="([ui](8|16|32|64|128|size)|f(32|64))?",i=["drop ","Copy","Send","Sized","Sync","Drop","Fn","FnMut","FnOnce","ToOwned","Clone","Debug","PartialEq","PartialOrd","Eq","Ord","AsRef","AsMut","Into","From","Default","Iterator","Extend","IntoIterator","DoubleEndedIterator","ExactSizeIterator","SliceConcatExt","ToString","assert!","assert_eq!","bitflags!","bytes!","cfg!","col!","concat!","concat_idents!","debug_assert!","debug_assert_eq!","env!","eprintln!","panic!","file!","format!","format_args!","include_bytes!","include_str!","line!","local_data_key!","module_path!","option_env!","print!","println!","select!","stringify!","try!","unimplemented!","unreachable!","vec!","write!","writeln!","macro_rules!","assert_ne!","debug_assert_ne!"],s=["i8","i16","i32","i64","i128","isize","u8","u16","u32","u64","u128","usize","f32","f64","str","char","bool","Box","Option","Result","String","Vec"]
+;return{name:"Rust",aliases:["rs"],keywords:{$pattern:e.IDENT_RE+"!?",type:s,
+keyword:["abstract","as","async","await","become","box","break","const","continue","crate","do","dyn","else","enum","extern","false","final","fn","for","if","impl","in","let","loop","macro","match","mod","move","mut","override","priv","pub","ref","return","self","Self","static","struct","super","trait","true","try","type","typeof","unsafe","unsized","use","virtual","where","while","yield"],
+literal:["true","false","Some","None","Ok","Err"],built_in:i},illegal:"</",
+contains:[e.C_LINE_COMMENT_MODE,e.COMMENT("/\\*","\\*/",{contains:["self"]
+}),e.inherit(e.QUOTE_STRING_MODE,{begin:/b?"/,illegal:null}),{
+className:"string",variants:[{begin:/b?r(#*)"(.|\n)*?"\1(?!#)/},{
+begin:/b?'\\?(x\w{2}|u\w{4}|U\w{8}|.)'/}]},{className:"symbol",
+begin:/'[a-zA-Z_][a-zA-Z0-9_]*/},{className:"number",variants:[{
+begin:"\\b0b([01_]+)"+a},{begin:"\\b0o([0-7_]+)"+a},{
+begin:"\\b0x([A-Fa-f0-9_]+)"+a},{
+begin:"\\b(\\d[\\d_]*(\\.[0-9_]+)?([eE][+-]?[0-9_]+)?)"+a}],relevance:0},{
+begin:[/fn/,/\s+/,e.UNDERSCORE_IDENT_RE],className:{1:"keyword",
+3:"title.function"}},{className:"meta",begin:"#!?\\[",end:"\\]",contains:[{
+className:"string",begin:/"/,end:/"/,contains:[e.BACKSLASH_ESCAPE]}]},{
+begin:[/let/,/\s+/,/(?:mut\s+)?/,e.UNDERSCORE_IDENT_RE],className:{1:"keyword",
+3:"keyword",4:"variable"}},{
+begin:[/for/,/\s+/,e.UNDERSCORE_IDENT_RE,/\s+/,/in/],className:{1:"keyword",
+3:"variable",5:"keyword"}},{begin:[/type/,/\s+/,e.UNDERSCORE_IDENT_RE],
+className:{1:"keyword",3:"title.class"}},{
+begin:[/(?:trait|enum|struct|union|impl|for)/,/\s+/,e.UNDERSCORE_IDENT_RE],
+className:{1:"keyword",3:"title.class"}},{begin:e.IDENT_RE+"::",keywords:{
+keyword:"Self",built_in:i,type:s}},{className:"punctuation",begin:"->"},n]}}})()
+;hljs.registerLanguage("rust",e)})();/*! `shell` grammar compiled for Highlight.js 11.9.0 */
+(()=>{var s=(()=>{"use strict";return s=>({name:"Shell Session",
+aliases:["console","shellsession"],contains:[{className:"meta.prompt",
+begin:/^\s{0,3}[/~\w\d[\]()@-]*[>%$#][ ]?/,starts:{end:/[^\\](?=\s*$)/,
+subLanguage:"bash"}}]})})();hljs.registerLanguage("shell",s)})();/*! `xml` grammar compiled for Highlight.js 11.9.0 */
+(()=>{var e=(()=>{"use strict";return e=>{
+const a=e.regex,n=a.concat(/[\p{L}_]/u,a.optional(/[\p{L}0-9_.-]*:/u),/[\p{L}0-9_.-]*/u),s={
+className:"symbol",begin:/&[a-z]+;|&#[0-9]+;|&#x[a-f0-9]+;/},t={begin:/\s/,
+contains:[{className:"keyword",begin:/#?[a-z_][a-z1-9_-]+/,illegal:/\n/}]
+},i=e.inherit(t,{begin:/\(/,end:/\)/}),c=e.inherit(e.APOS_STRING_MODE,{
+className:"string"}),l=e.inherit(e.QUOTE_STRING_MODE,{className:"string"}),r={
+endsWithParent:!0,illegal:/</,relevance:0,contains:[{className:"attr",
+begin:/[\p{L}0-9._:-]+/u,relevance:0},{begin:/=\s*/,relevance:0,contains:[{
+className:"string",endsParent:!0,variants:[{begin:/"/,end:/"/,contains:[s]},{
+begin:/'/,end:/'/,contains:[s]},{begin:/[^\s"'=<>`]+/}]}]}]};return{
+name:"HTML, XML",
+aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist","wsf","svg"],
+case_insensitive:!0,unicodeRegex:!0,contains:[{className:"meta",begin:/<![a-z]/,
+end:/>/,relevance:10,contains:[t,l,c,i,{begin:/\[/,end:/\]/,contains:[{
+className:"meta",begin:/<![a-z]/,end:/>/,contains:[t,i,l,c]}]}]
+},e.COMMENT(/<!--/,/-->/,{relevance:10}),{begin:/<!\[CDATA\[/,end:/\]\]>/,
+relevance:10},s,{className:"meta",end:/\?>/,variants:[{begin:/<\?xml/,
+relevance:10,contains:[l]},{begin:/<\?[a-z][a-z0-9]+/}]},{className:"tag",
+begin:/<style(?=\s|>)/,end:/>/,keywords:{name:"style"},contains:[r],starts:{
+end:/<\/style>/,returnEnd:!0,subLanguage:["css","xml"]}},{className:"tag",
+begin:/<script(?=\s|>)/,end:/>/,keywords:{name:"script"},contains:[r],starts:{
+end:/<\/script>/,returnEnd:!0,subLanguage:["javascript","handlebars","xml"]}},{
+className:"tag",begin:/<>|<\/>/},{className:"tag",
+begin:a.concat(/</,a.lookahead(a.concat(n,a.either(/\/>/,/>/,/\s/)))),
+end:/\/?>/,contains:[{className:"name",begin:n,relevance:0,starts:r}]},{
+className:"tag",begin:a.concat(/<\//,a.lookahead(a.concat(n,/>/))),contains:[{
+className:"name",begin:n,relevance:0},{begin:/>/,relevance:0,endsParent:!0}]}]}}
+})();hljs.registerLanguage("xml",e)})();/*! `yaml` grammar compiled for Highlight.js 11.9.0 */
+(()=>{var e=(()=>{"use strict";return e=>{
+const n="true false yes no null",a="[\\w#;/?:@&=+$,.~*'()[\\]]+",s={
+className:"string",relevance:0,variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/
+},{begin:/\S+/}],contains:[e.BACKSLASH_ESCAPE,{className:"template-variable",
+variants:[{begin:/\{\{/,end:/\}\}/},{begin:/%\{/,end:/\}/}]}]},i=e.inherit(s,{
+variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/[^\s,{}[\]]+/}]}),l={
+end:",",endsWithParent:!0,excludeEnd:!0,keywords:n,relevance:0},t={begin:/\{/,
+end:/\}/,contains:[l],illegal:"\\n",relevance:0},g={begin:"\\[",end:"\\]",
+contains:[l],illegal:"\\n",relevance:0},b=[{className:"attr",variants:[{
+begin:/\w[\w :()\./-]*:(?=[ \t]|$)/},{begin:/"\w[\w :()\./-]*":(?=[ \t]|$)/},{
+begin:/'\w[\w :()\./-]*':(?=[ \t]|$)/}]},{className:"meta",begin:"^---\\s*$",
+relevance:10},{className:"string",
+begin:"[\\|>]([1-9]?[+-])?[ ]*\\n( +)[^ ][^\\n]*\\n(\\2[^\\n]+\\n?)*"},{
+begin:"<%[%=-]?",end:"[%-]?%>",subLanguage:"ruby",excludeBegin:!0,excludeEnd:!0,
+relevance:0},{className:"type",begin:"!\\w+!"+a},{className:"type",
+begin:"!<"+a+">"},{className:"type",begin:"!"+a},{className:"type",begin:"!!"+a
+},{className:"meta",begin:"&"+e.UNDERSCORE_IDENT_RE+"$"},{className:"meta",
+begin:"\\*"+e.UNDERSCORE_IDENT_RE+"$"},{className:"bullet",begin:"-(?=[ ]|$)",
+relevance:0},e.HASH_COMMENT_MODE,{beginKeywords:n,keywords:{literal:n}},{
+className:"number",
+begin:"\\b[0-9]{4}(-[0-9][0-9]){0,2}([Tt \\t][0-9][0-9]?(:[0-9][0-9]){2})?(\\.[0-9]*)?([ \\t])*(Z|[-+][0-9][0-9]?(:[0-9][0-9])?)?\\b"
+},{className:"number",begin:e.C_NUMBER_RE+"\\b",relevance:0},t,g,s],r=[...b]
+;return r.pop(),r.push(i),l.contains=r,{name:"YAML",case_insensitive:!0,
+aliases:["yml"],contains:b}}})();hljs.registerLanguage("yaml",e)})();
\ No newline at end of file
diff --git a/tvix/eval/.gitignore b/tvix/eval/.gitignore
deleted file mode 100644
index ea8c4bf7f3..0000000000
--- a/tvix/eval/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/target
diff --git a/tvix/eval/.skip-subtree b/tvix/eval/.skip-subtree
new file mode 100644
index 0000000000..05f9fc116f
--- /dev/null
+++ b/tvix/eval/.skip-subtree
@@ -0,0 +1,2 @@
+Do not traverse further, readTree will encounter Nix language tests
+and such and fail.
diff --git a/tvix/eval/Cargo.lock b/tvix/eval/Cargo.lock
deleted file mode 100644
index d772473bc5..0000000000
--- a/tvix/eval/Cargo.lock
+++ /dev/null
@@ -1,106 +0,0 @@
-# This file is automatically @generated by Cargo.
-# It is not intended for manual editing.
-version = 3
-
-[[package]]
-name = "autocfg"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
-
-[[package]]
-name = "cbitset"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "29b6ad25ae296159fb0da12b970b2fe179b234584d7cd294c891e2bbb284466b"
-dependencies = [
- "num-traits",
-]
-
-[[package]]
-name = "countme"
-version = "2.0.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "328b822bdcba4d4e402be8d9adb6eebf269f969f8eadef977a553ff3c4fbcb58"
-
-[[package]]
-name = "hashbrown"
-version = "0.9.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
-
-[[package]]
-name = "memoffset"
-version = "0.6.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
-dependencies = [
- "autocfg",
-]
-
-[[package]]
-name = "num-traits"
-version = "0.2.15"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
-dependencies = [
- "autocfg",
-]
-
-[[package]]
-name = "rnix"
-version = "0.10.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8024a523e8836f1a5d051203dc00d833357fee94e351b51348dfaeca5364daa9"
-dependencies = [
- "cbitset",
- "rowan",
- "smol_str",
-]
-
-[[package]]
-name = "rowan"
-version = "0.12.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1b36e449f3702f3b0c821411db1cbdf30fb451726a9456dce5dabcd44420043"
-dependencies = [
- "countme",
- "hashbrown",
- "memoffset",
- "rustc-hash",
- "text-size",
-]
-
-[[package]]
-name = "rustc-hash"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
-
-[[package]]
-name = "serde"
-version = "1.0.142"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e590c437916fb6b221e1d00df6e3294f3fccd70ca7e92541c475d6ed6ef5fee2"
-
-[[package]]
-name = "smol_str"
-version = "0.1.23"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7475118a28b7e3a2e157ce0131ba8c5526ea96e90ee601d9f6bb2e286a35ab44"
-dependencies = [
- "serde",
-]
-
-[[package]]
-name = "text-size"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "288cb548dbe72b652243ea797201f3d481a0609a967980fcc5b2315ea811560a"
-
-[[package]]
-name = "tvix-eval"
-version = "0.1.0"
-dependencies = [
- "rnix",
-]
diff --git a/tvix/eval/Cargo.toml b/tvix/eval/Cargo.toml
index 0fa90d04d0..677ce6ab85 100644
--- a/tvix/eval/Cargo.toml
+++ b/tvix/eval/Cargo.toml
@@ -3,7 +3,59 @@ name = "tvix-eval"
 version = "0.1.0"
 edition = "2021"
 
-# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+[lib]
+name = "tvix_eval"
 
 [dependencies]
-rnix = "0.10.2"
+builtin-macros = { path = "./builtin-macros", package = "tvix-eval-builtin-macros" }
+bytes = "1.4.0"
+bstr = { version = "1.8.0", features = ["serde"] }
+codemap = "0.1.3"
+codemap-diagnostic = "0.1.1"
+dirs = "4.0.0"
+genawaiter = { version = "0.99.1", default_features = false }
+imbl = { version = "2.0", features = [ "serde" ] }
+itertools = "0.12.0"
+lazy_static = "1.4.0"
+lexical-core = { version = "0.8.5", features = ["format", "parse-floats"] }
+os_str_bytes = { version = "6.3", features = ["conversions"] }
+path-clean = "0.1"
+proptest = { version = "1.3.0", default_features = false, features = ["std", "alloc", "tempfile"], optional = true }
+regex = "1.6.0"
+rnix = "0.11.0"
+rowan = "*" # pinned by rnix
+serde = { version = "1.0", features = [ "rc", "derive" ] }
+serde_json = "1.0"
+smol_str = "0.2.0"
+tabwriter = "1.2"
+test-strategy = { version = "0.2.1", optional = true }
+toml = "0.6.0"
+xml-rs = "0.8.4"
+sha2 = "0.10.8"
+sha1 = "0.10.6"
+md-5 = "0.10.6"
+data-encoding = "2.5.0"
+
+[dev-dependencies]
+criterion = "0.5"
+itertools = "0.12.0"
+pretty_assertions = "1.2.1"
+rstest = "0.19.0"
+tempfile = "3.3.0"
+
+[features]
+default = ["impure", "arbitrary", "nix_tests"]
+
+# Enables running the Nix language test suite from the original C++
+# Nix implementation (at version 2.3) against Tvix.
+nix_tests = []
+
+# Enables operations in the VM which depend on the ability to perform I/O
+impure = []
+
+# Enables Arbitrary impls for internal types (required to run tests)
+arbitrary = ["proptest", "test-strategy", "imbl/proptest"]
+
+[[bench]]
+name = "eval"
+harness = false
diff --git a/tvix/eval/README.md b/tvix/eval/README.md
index 8320aa8ea3..02e2100f59 100644
--- a/tvix/eval/README.md
+++ b/tvix/eval/README.md
@@ -2,22 +2,83 @@ Tvix Evaluator
 ==============
 
 This project implements an interpreter for the Nix programming
-language.
+language. You can experiment with an online version of the evaluator:
+[tvixbolt][].
 
 The interpreter aims to be compatible with `nixpkgs`, on the
 foundation of Nix 2.3.
 
-<!-- TODO(tazjin): Remove this note when appropriate -->
-Work on this project is *extremely in-progress*, and the state of the
-project in the public repository may not necessarily reflect the state
-of the private codebase, as we are slowly working on publishing it.
-
-We expect this to have caught up in a handful of weeks (as of
-2022-08-12).
+**Important note:** The evaluator is not yet feature-complete, and
+while the core mechanisms (compiler, runtime, ...) have stabilised
+somewhat, a lot of components are still changing rapidly.
 
 Please contact [TVL](https://tvl.fyi) with any questions you might
 have.
 
+## Building tvix-eval
+
+Please check the `README.md` one level up for instructions on how to build this.
+
+The evaluator itself can also be built with standard Rust tooling (i.e. `cargo
+build`).
+
+If you would like to clone **only** the evaluator and build it
+directly with Rust tooling, you can do:
+
+```bash
+git clone https://code.tvl.fyi/depot.git:/tvix/eval.git tvix-eval
+
+cd tvix-eval && cargo build
+```
+
+## Tests
+
+Tvix currently has three language test suites for tvix-eval:
+
+* `nix_tests` and `tvix_tests` are based on the same mechanism
+  borrowed from the C++ Nix implementation. They consist of
+  Nix files as well as expected output (if applicable).
+  The test cases are split into four categories:
+  `eval-okay` (evaluates successfully with the expected output),
+  `eval-fail` (fails to evaluate, no expected output),
+  `parse-okay` (expression parses successfully, no expected output)
+  and `parse-fail` (expression fails to parse, no expected output).
+  Tvix currently ignores the last two types of test cases, since
+  it doesn't implement its own parser.
+
+  Both test suites have a `notyetpassing` directory. All test cases
+  in here test behavior that is not yet supported by Tvix. They are
+  considered to be expected failures, so you can't forget to move
+  them into the test suite proper when fixing the incompatibility.
+
+  Additionally, separate targets in the depot pipeline, under
+  `//tvix/verify-lang-tests`, check both test suites (including
+  `notyetpassing` directories) against
+  C++ Nix 2.3 and the default C++ Nix version in nixpkgs.
+  This way we can prevent accidentally introducing test cases
+  for behavior that C++ Nix doesn't exhibit.
+
+  * `nix_tests` has the test cases from C++ Nix's language test
+    suite and is sporadically updated by manually syncing the
+    directories. The `notyetpassing` directory shows how far
+    it is until we pass it completely.
+
+  * `tvix_tests` contains test cases written by the Tvix contributors.
+    Some more or less duplicate test cases contained in `nix_tests`,
+    but many cover relevant behavior that isn't by `nix_tests`.
+    Consequently, it'd be nice to eventually merge the two test
+    suites into a jointly maintained, common Nix language test suite.
+
+    It also has a `notyetpassing` directory for missing behavior
+    that is discovered while working on Tvix and isn't covered by the
+    `nix_tests` suite.
+
+* `nix_oracle` can evaluate Nix expressions in Tvix and compare the
+  result against C++ Nix (2.3) directly. Eventually it should gain
+  the ability to property test generated Nix expressions.
+  An additional feature is that it can evaluate expressions without
+  `--strict`, so thunking behavior can be verified more easily.
+
 ## rnix-parser
 
 Tvix is written in memory of jD91mZM2, the author of [rnix-parser][]
@@ -28,3 +89,4 @@ parser is now maintained by Nix community members.
 
 [rnix-parser]: https://github.com/nix-community/rnix-parser
 [rip]: https://www.redox-os.org/news/open-source-mental-health/
+[tvixbolt]: https://bolt.tvix.dev/
diff --git a/tvix/eval/benches/eval.rs b/tvix/eval/benches/eval.rs
new file mode 100644
index 0000000000..57d4eb71b5
--- /dev/null
+++ b/tvix/eval/benches/eval.rs
@@ -0,0 +1,36 @@
+use criterion::{black_box, criterion_group, criterion_main, Criterion};
+use itertools::Itertools;
+
+fn interpret(code: &str) {
+    tvix_eval::Evaluation::new_pure().evaluate(code, None);
+}
+
+fn eval_literals(c: &mut Criterion) {
+    c.bench_function("int", |b| {
+        b.iter(|| {
+            interpret(black_box("42"));
+        })
+    });
+}
+
+fn eval_merge_attrs(c: &mut Criterion) {
+    c.bench_function("merge small attrs", |b| {
+        b.iter(|| {
+            interpret(black_box("{ a = 1; b = 2; } // { c = 3; }"));
+        })
+    });
+
+    c.bench_function("merge large attrs with small attrs", |b| {
+        let large_attrs = format!(
+            "{{{}}}",
+            (0..10000).map(|n| format!("a{n} = {n};")).join(" ")
+        );
+        let expr = format!("{large_attrs} // {{ c = 3; }}");
+        b.iter(move || {
+            interpret(black_box(&expr));
+        })
+    });
+}
+
+criterion_group!(benches, eval_literals, eval_merge_attrs);
+criterion_main!(benches);
diff --git a/tvix/eval/build.rs b/tvix/eval/build.rs
new file mode 100644
index 0000000000..a9c9a78b06
--- /dev/null
+++ b/tvix/eval/build.rs
@@ -0,0 +1,9 @@
+use std::env;
+
+fn main() {
+    println!(
+        "cargo:rustc-env=TVIX_CURRENT_SYSTEM={}",
+        &env::var("TARGET").unwrap()
+    );
+    println!("cargo:rerun-if-changed-env=TARGET")
+}
diff --git a/tvix/eval/builtin-macros/.gitignore b/tvix/eval/builtin-macros/.gitignore
new file mode 100644
index 0000000000..eb5a316cbd
--- /dev/null
+++ b/tvix/eval/builtin-macros/.gitignore
@@ -0,0 +1 @@
+target
diff --git a/tvix/eval/builtin-macros/Cargo.toml b/tvix/eval/builtin-macros/Cargo.toml
new file mode 100644
index 0000000000..3a35ea12a0
--- /dev/null
+++ b/tvix/eval/builtin-macros/Cargo.toml
@@ -0,0 +1,16 @@
+[package]
+name = "tvix-eval-builtin-macros"
+version = "0.0.1"
+authors = [ "Griffin Smith <root@gws.fyi>" ]
+edition = "2021"
+
+[dependencies]
+syn = { version = "1.0.57", features = ["full", "parsing", "printing", "visit", "visit-mut", "extra-traits"] }
+quote = "1.0.8"
+proc-macro2 = "1"
+
+[lib]
+proc-macro = true
+
+[dev-dependencies]
+tvix-eval = { path = "../" }
diff --git a/tvix/eval/builtin-macros/src/lib.rs b/tvix/eval/builtin-macros/src/lib.rs
new file mode 100644
index 0000000000..5cc9807f54
--- /dev/null
+++ b/tvix/eval/builtin-macros/src/lib.rs
@@ -0,0 +1,357 @@
+extern crate proc_macro;
+
+use proc_macro::TokenStream;
+use proc_macro2::Span;
+use quote::{quote, quote_spanned, ToTokens};
+use syn::parse::Parse;
+use syn::spanned::Spanned;
+use syn::{
+    parse2, parse_macro_input, parse_quote, parse_quote_spanned, Attribute, FnArg, Ident, Item,
+    ItemMod, LitStr, Meta, Pat, PatIdent, PatType, Token, Type,
+};
+
+/// Description of a single argument passed to a builtin
+struct BuiltinArgument {
+    /// The name of the argument, to be used in docstrings and error messages
+    name: Ident,
+
+    /// Type of the argument.
+    ty: Box<Type>,
+
+    /// Whether the argument should be forced before the underlying builtin
+    /// function is called.
+    strict: bool,
+
+    /// Propagate catchable values as values to the function, rather than short-circuit returning
+    /// them if encountered
+    catch: bool,
+
+    /// Span at which the argument was defined.
+    span: Span,
+}
+
+fn extract_docstring(attrs: &[Attribute]) -> Option<String> {
+    // Rust docstrings are transparently written pre-macro expansion into an attribute that looks
+    // like:
+    //
+    // #[doc = "docstring here"]
+    //
+    // Multi-line docstrings yield multiple attributes in order, which we assemble into a single
+    // string below.
+
+    #[allow(dead_code)]
+    #[derive(Debug)]
+    struct Docstring {
+        eq: Token![=],
+        doc: LitStr,
+    }
+
+    impl Parse for Docstring {
+        fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
+            Ok(Self {
+                eq: input.parse()?,
+                doc: input.parse()?,
+            })
+        }
+    }
+
+    attrs
+        .iter()
+        .filter(|attr| attr.path.get_ident().into_iter().any(|id| id == "doc"))
+        .filter_map(|attr| parse2::<Docstring>(attr.tokens.clone()).ok())
+        .map(|docstring| docstring.doc.value())
+        .reduce(|mut fst, snd| {
+            if snd.is_empty() {
+                // An empty string represents a spacing newline that was added in the
+                // original doc comment.
+                fst.push_str("\n\n");
+            } else {
+                fst.push_str(&snd);
+            }
+
+            fst
+        })
+}
+
+/// Parse arguments to the `builtins` macro itself, such as `#[builtins(state = Rc<State>)]`.
+fn parse_module_args(args: TokenStream) -> Option<Type> {
+    if args.is_empty() {
+        return None;
+    }
+
+    let meta: Meta = syn::parse(args).expect("could not parse arguments to `builtins`-attribute");
+    let name_value = match meta {
+        Meta::NameValue(nv) => nv,
+        _ => panic!("arguments to `builtins`-attribute must be of the form `name = value`"),
+    };
+
+    if *name_value.path.get_ident().unwrap() != "state" {
+        return None;
+    }
+
+    if let syn::Lit::Str(type_name) = name_value.lit {
+        let state_type: Type =
+            syn::parse_str(&type_name.value()).expect("failed to parse builtins state type");
+        return Some(state_type);
+    }
+
+    panic!("state attribute must be a quoted Rust type");
+}
+
+/// Mark the annotated module as a module for defining Nix builtins.
+///
+/// An optional type definition may be specified as an argument (e.g. `#[builtins(Rc<State>)]`),
+/// which will add a parameter to the `builtins` function of that type which is passed to each
+/// builtin upon instantiation. Using this, builtins that close over some external state can be
+/// written.
+///
+/// The type of each function is rewritten to receive a `Vec<Value>`, containing each `Value`
+/// argument that the function receives. The body of functions is accordingly rewritten to "unwrap"
+/// values from this vector and bind them to the correct names, so unless a static error occurs this
+/// transformation is mostly invisible to users of the macro.
+///
+/// A function `fn builtins() -> Vec<Builtin>` will be defined within the annotated module,
+/// returning a list of [`tvix_eval::Builtin`] for each function annotated with the `#[builtin]`
+/// attribute within the module. If a `state` type is specified, the `builtins` function will take a
+/// value of that type.
+///
+/// Each invocation of the `#[builtin]` annotation within the module should be passed a string
+/// literal for the name of the builtin.
+///
+/// # Examples
+/// ```ignore
+/// # use tvix_eval;
+/// # use tvix_eval_builtin_macros::builtins;
+///
+/// #[builtins]
+/// mod builtins {
+///     use tvix_eval::{GenCo, ErrorKind, Value};
+///
+///     #[builtin("identity")]
+///     pub async fn builtin_identity(co: GenCo, x: Value) -> Result<Value, ErrorKind> {
+///         Ok(x)
+///     }
+///
+///     // Builtins can request their argument not be forced before being called by annotating the
+///     // argument with the `#[lazy]` attribute
+///
+///     #[builtin("tryEval")]
+///     pub async fn builtin_try_eval(co: GenCo, #[lazy] x: Value) -> Result<Value, ErrorKind> {
+///         todo!()
+///     }
+/// }
+/// ```
+#[proc_macro_attribute]
+pub fn builtins(args: TokenStream, item: TokenStream) -> TokenStream {
+    let mut module = parse_macro_input!(item as ItemMod);
+
+    // parse the optional state type, which users might want to pass to builtins
+    let state_type = parse_module_args(args);
+
+    let (_, items) = match &mut module.content {
+        Some(content) => content,
+        None => {
+            return (quote_spanned!(module.span() =>
+                compile_error!("Builtin modules must be defined in-line")
+            ))
+            .into();
+        }
+    };
+
+    let mut builtins = vec![];
+    for item in items.iter_mut() {
+        if let Item::Fn(f) = item {
+            if let Some(builtin_attr_pos) = f
+                .attrs
+                .iter()
+                .position(|attr| attr.path.get_ident().iter().any(|id| *id == "builtin"))
+            {
+                let builtin_attr = f.attrs.remove(builtin_attr_pos);
+                let name: LitStr = match builtin_attr.parse_args() {
+                    Ok(args) => args,
+                    Err(err) => return err.into_compile_error().into(),
+                };
+
+                if f.sig.inputs.len() <= 1 {
+                    return (quote_spanned!(
+                        f.sig.inputs.span() =>
+                            compile_error!("Builtin functions must take at least two arguments")
+                    ))
+                    .into();
+                }
+
+                // Inspect the first argument to determine if this function is
+                // taking the state parameter.
+                // TODO(tazjin): add a test in //tvix/eval that covers this
+                let mut captures_state = false;
+                if let FnArg::Typed(PatType { pat, .. }) = &f.sig.inputs[0] {
+                    if let Pat::Ident(PatIdent { ident, .. }) = pat.as_ref() {
+                        if *ident == "state" {
+                            if state_type.is_none() {
+                                panic!("builtin captures a `state` argument, but no state type was defined");
+                            }
+
+                            captures_state = true;
+                        }
+                    }
+                }
+
+                let mut rewritten_args = std::mem::take(&mut f.sig.inputs)
+                    .into_iter()
+                    .collect::<Vec<_>>();
+
+                // Split out the value arguments from the static arguments.
+                let split_idx = if captures_state { 2 } else { 1 };
+                let value_args = rewritten_args.split_off(split_idx);
+
+                let builtin_arguments = value_args
+                    .into_iter()
+                    .map(|arg| {
+                        let span = arg.span();
+                        let mut strict = true;
+                        let mut catch = false;
+                        let (name, ty) = match arg {
+                            FnArg::Receiver(_) => {
+                                return Err(quote_spanned!(span => {
+                                    compile_error!("unexpected receiver argument in builtin")
+                                }))
+                            }
+                            FnArg::Typed(PatType {
+                                mut attrs, pat, ty, ..
+                            }) => {
+                                attrs.retain(|attr| {
+                                    attr.path.get_ident().into_iter().any(|id| {
+                                        if id == "lazy" {
+                                            strict = false;
+                                            false
+                                        } else if id == "catch" {
+                                            catch = true;
+                                            false
+                                        } else {
+                                            true
+                                        }
+                                    })
+                                });
+                                match pat.as_ref() {
+                                    Pat::Ident(PatIdent { ident, .. }) => {
+                                        (ident.clone(), ty.clone())
+                                    }
+                                    _ => panic!("ignored value parameters must be named, e.g. `_x` and not just `_`"),
+                                }
+                            }
+                        };
+
+                        if catch && !strict {
+                            return Err(quote_spanned!(span => {
+                                compile_error!("Cannot mix both lazy and catch on the same argument")
+                            }));
+                        }
+
+                        Ok(BuiltinArgument {
+                            strict,
+                            catch,
+                            span,
+                            name,
+                            ty,
+                        })
+                    })
+                    .collect::<Result<Vec<BuiltinArgument>, _>>();
+
+                let builtin_arguments = match builtin_arguments {
+                    Err(err) => return err.into(),
+
+                    // reverse argument order, as they are popped from the stack
+                    // slice in opposite order
+                    Ok(args) => args,
+                };
+
+                // Rewrite the argument to the actual function to take a
+                // `Vec<Value>`, which is then destructured into the
+                // user-defined values in the function header.
+                let sig_span = f.sig.span();
+                rewritten_args.push(parse_quote_spanned!(sig_span=> mut values: Vec<Value>));
+                f.sig.inputs = rewritten_args.into_iter().collect();
+
+                // Rewrite the body of the function to do said argument forcing.
+                //
+                // This is done by creating a new block for each of the
+                // arguments that evaluates it, and wraps the inner block.
+                for arg in &builtin_arguments {
+                    let block = &f.block;
+                    let ty = &arg.ty;
+                    let ident = &arg.name;
+
+                    if arg.strict {
+                        if arg.catch {
+                            f.block = Box::new(parse_quote_spanned! {arg.span=> {
+                                let #ident: #ty = tvix_eval::generators::request_force(&co, values.pop()
+                                  .expect("Tvix bug: builtin called with incorrect number of arguments")).await;
+                                #block
+                            }});
+                        } else {
+                            f.block = Box::new(parse_quote_spanned! {arg.span=> {
+                                let #ident: #ty = tvix_eval::generators::request_force(&co, values.pop()
+                                  .expect("Tvix bug: builtin called with incorrect number of arguments")).await;
+                                if #ident.is_catchable() {
+                                    return Ok(#ident);
+                                }
+                                #block
+                            }});
+                        }
+                    } else {
+                        f.block = Box::new(parse_quote_spanned! {arg.span=> {
+                            let #ident: #ty = values.pop()
+                              .expect("Tvix bug: builtin called with incorrect number of arguments");
+
+                            #block
+                        }})
+                    }
+                }
+
+                let fn_name = f.sig.ident.clone();
+                let arg_count = builtin_arguments.len();
+                let docstring = match extract_docstring(&f.attrs) {
+                    Some(docs) => quote!(Some(#docs)),
+                    None => quote!(None),
+                };
+
+                if captures_state {
+                    builtins.push(quote_spanned! { builtin_attr.span() => {
+                        let inner_state = state.clone();
+                        tvix_eval::Builtin::new(
+                            #name,
+                            #docstring,
+                            #arg_count,
+                            move |values| Gen::new(|co| tvix_eval::generators::pin_generator(#fn_name(inner_state.clone(), co, values))),
+                        )
+                    }});
+                } else {
+                    builtins.push(quote_spanned! { builtin_attr.span() => {
+                        tvix_eval::Builtin::new(
+                            #name,
+                            #docstring,
+                            #arg_count,
+                            |values| Gen::new(|co| tvix_eval::generators::pin_generator(#fn_name(co, values))),
+                        )
+                    }});
+                }
+            }
+        }
+    }
+
+    if let Some(state_type) = state_type {
+        items.push(parse_quote! {
+            pub fn builtins(state: #state_type) -> Vec<(&'static str, Value)> {
+                vec![#(#builtins),*].into_iter().map(|b| (b.name(), Value::Builtin(b))).collect()
+            }
+        });
+    } else {
+        items.push(parse_quote! {
+            pub fn builtins() -> Vec<(&'static str, Value)> {
+                vec![#(#builtins),*].into_iter().map(|b| (b.name(), Value::Builtin(b))).collect()
+            }
+        });
+    }
+
+    module.into_token_stream().into()
+}
diff --git a/tvix/eval/builtin-macros/tests/tests.rs b/tvix/eval/builtin-macros/tests/tests.rs
new file mode 100644
index 0000000000..288b6670e1
--- /dev/null
+++ b/tvix/eval/builtin-macros/tests/tests.rs
@@ -0,0 +1,45 @@
+pub use tvix_eval::{Builtin, Value};
+use tvix_eval_builtin_macros::builtins;
+
+#[builtins]
+mod builtins {
+    use tvix_eval::generators::{Gen, GenCo};
+    use tvix_eval::{ErrorKind, Value};
+
+    /// Test docstring.
+    ///
+    /// It has multiple lines!
+    #[builtin("identity")]
+    pub async fn builtin_identity(co: GenCo, x: Value) -> Result<Value, ErrorKind> {
+        Ok(x)
+    }
+
+    #[builtin("tryEval")]
+    pub async fn builtin_try_eval(_co: GenCo, #[lazy] _x: Value) -> Result<Value, ErrorKind> {
+        unimplemented!("builtin is never called")
+    }
+}
+
+#[test]
+fn builtins() {
+    let builtins = builtins::builtins();
+    assert_eq!(builtins.len(), 2);
+
+    let (_, identity) = builtins
+        .iter()
+        .find(|(name, _)| *name == "identity")
+        .unwrap();
+
+    match identity {
+        Value::Builtin(identity) => assert_eq!(
+            identity.documentation(),
+            Some(
+                r#" Test docstring.
+
+ It has multiple lines!"#
+            )
+        ),
+
+        _ => panic!("builtin was not a builtin"),
+    }
+}
diff --git a/tvix/eval/default.nix b/tvix/eval/default.nix
index e50ac32be4..91661291f7 100644
--- a/tvix/eval/default.nix
+++ b/tvix/eval/default.nix
@@ -1,5 +1,9 @@
-{ depot, ... }:
+# TODO: find a way to build the benchmarks via crate2nix
+{ depot, pkgs, ... }:
 
-depot.third_party.naersk.buildPackage {
-  src = ./.;
+depot.tvix.crates.workspaceMembers.tvix-eval.build.override {
+  runTests = true;
+
+  # Make C++ Nix available, to compare eval results against.
+  testInputs = [ pkgs.nix ];
 }
diff --git a/tvix/eval/docs/abandoned/thread-local-vm.md b/tvix/eval/docs/abandoned/thread-local-vm.md
new file mode 100644
index 0000000000..c6a2d5e07e
--- /dev/null
+++ b/tvix/eval/docs/abandoned/thread-local-vm.md
@@ -0,0 +1,233 @@
+# We can't have nice things because IFD
+
+The thread-local VM work below was ultimately not merged because it
+was decided that it would be harmful for `tvix::eval::Value` to
+implement `Eq`, `Hash`, or any of the other `std` traits.
+
+Implementing `std` traits on `Value` was deemed harmful because IFD
+can cause arbitrary amounts of compilation to occur, including
+network transactions with builders.  Obviously it would be
+unexpected and error-prone to have a `PartialEq::eq()` which does
+something like this.  This problem does not manifest within the
+"nixpkgs compatibility only" scope, or in any undeprecated language
+feature other than IFD.  Although IFD is outside the "nixpkgs
+compatibility scope", it [has been added to the TVL compatibility
+scope](https://cl.tvl.fyi/c/depot/+/7193/comment/3418997b_0dbd0b65/).
+
+This was the sole reason for not merging.
+
+The explanation below may be useful in case future circumstances
+affect the relevance of the reasoning above.
+
+The implementation can be found in these CLs:
+
+- [refactor(tvix/eval): remove lifetime parameter from VM<'o>](https://cl.tvl.fyi/c/depot/+/7194)
+- [feat(tvix/eval): [FOUNDLING] thread-local VM](https://cl.tvl.fyi/c/depot/+/7195)
+- [feat(tvix/eval): [FOUNDLING] VM::vm_xxx convenience methods](https://cl.tvl.fyi/c/depot/+/7196)
+- [refactor(tvix/eval): [FOUNDLING]: drop explicit `&mut vm` parameter](https://cl.tvl.fyi/c/depot/+/7197)
+
+# Thread-local storage for tvix::eval::vm::VM
+
+## The problem
+
+`Value::force()` takes a `&mut VM` argument, since forcing a value
+requires executing opcodes.  This means that `Value::nix_eq()` too
+must take a `&mut VM`, since any sensible definition of equality
+will have to force thunks.
+
+Unfortunately Rust's `PartialEq::eq()` function does not accept any
+additional arguments like this, so `Value` cannot implement
+`PartialEq`.  Worse, structs which *contain* `Value`s can't
+implement `PartialEq` either.  This means `Value`, and anything
+containing it, cannot be the key for a `BTreeMap` or `HashMap`.  We
+can't even insert `Value`s into a `HashSet`!
+
+There are other situations like this that don't involve `PartialEq`,
+but it's the most glaring one.  The main problem is that you need a
+`VM` in order to force thunks, and thunks can be anywhere in a
+`Value`.
+
+## Solving the problem with thread-locals
+
+We could avoid threading the `&mut VM` through the entire codebase
+by making it a thread-local.
+
+To do this without a performance hit, we need to use LLVM
+thread-locals, which are the same cost as references to `static`s
+but load relative to
+[`llvm.threadlocal.address`][threadlocal-intrinsic] instead of
+relative to the data segment.  Unfortunately `#[thread_local]` [is
+unstable][thread-local-unstable] and [unsafe in
+general][thread-local-unsafe] for most of the cases where we would
+want to use it.  There is one [exception][tls-const-init], however:
+if a `!thread_local()` has a `const` initializer, the compiler will
+insert a `#[thread_local]`; this special case is both safe and
+stable.
+
+The difficult decision is what the type of the thread-local should
+be.  Since you can't get a mutable reference to a `thread_local!()`
+it will have to be some interior-mutability-bestowing wrapper around
+our current `struct VM`.  Here are the choices:
+
+### `RefCell<VM>`
+
+This is the obvious first choice, since it lets you borrow a
+`RefMut<Target=VM>`.  The problem here is that we want to keep the
+codebase written such that all the functions in `impl VM` still take
+a `&mut self`.  This means that there will be an active mutable
+borrow for the duration of `VM::call_builtin()`.  So if we implement
+`PartialEq` by having `eq()` attempt a second mutable borrow from
+the thread-local storage, it will fail since there is already an
+active borrow.
+
+The problem here is that you can't "unborrow" a `RefMut` except by
+dropping it.  There's no way around this.
+
+#### Problem: Uglification
+
+The only solution here is to rewrite all the functions in `impl VM`
+so they don't take any kind of `self` argument, and then have them
+do a short-lived `.borrow_mut()` from the thread-local `RefCell`
+*separately, each time* they want to modify one of the fields of
+`VM` (currently `frames`, `stack`, `with_stack`, `warnings`).  This
+means that if you had a code sequence like this:
+
+```
+impl VM {
+  fn foo(&mut self, ...) {
+    ...
+    self.frame().ip += 1;
+    self.some_other_method();
+    self.frame().ip += 1;
+```
+
+You would need to add *two separate `borrow_mut()`s*, one for each
+of the `self.frame().ip+=1` statements.  You can't just do one big
+`borrow_mut()` because `some_other_method()` will call
+`borrow_mut()` and panic.
+
+#### Problem: Performance
+
+The `RefCell<VM>` approach also has a fairly huge performance hit,
+because every single modification to any part of `VM` will require a
+reference count increment/decrement, and a conditional branch based
+on the check (which will never fail) that the `RefCell` isn't
+already mutably borrowed.  It will also impede a lot of rustc's
+optimizations.
+
+### `Cell<VM>`
+
+This is a non-starter because it means that in order to mutate any
+field of `VM`, you have to move the entire `struct VM` out of the
+`Cell`, mutate it, and move it back in.
+
+### `Cell<Box<VM>>`
+
+Now we're getting warmer.  Here, we can move the `Box<VM>` out of
+the cell with a single pointer-sized memory access.
+
+We don't want to do the "uglification" described in the previous
+section.  We are very fortunate that, sometime in mid-2019, the Rust
+dieties [decreed by fiat][fiat-decree] that `&Cell<T>` and `&mut T`
+are bit-for-bit identical, and even gave us mortals safe wrappers
+[`from_mut()`][from_mut] and [`get_mut()`][get_mut] around
+`mem::transmute()`.
+
+So now, when a `VM` method (which takes `&mut self`) calls out to
+some external code (like a builtin), instead of passing the `&mut
+self` to the external code it can call `Cell::from_mut(&mut self)`,
+and then `Cell::swap()` that into the thread-local storage cell for
+the duration of the external code.  After the external code returns,
+it can `Cell::swap()` it back.  This whole dance gets wrapped in a
+lexical block, and the borrow checker sees that the `&Cell<Box<VM>>`
+returned by `Cell::from_mut()` lives only until the end of the
+lexical block, *so we get the `&mut self` back after the close-brace
+for that block*.  NLL FTW.  This sounds like a lot of work, but it
+should compile down to two pointer-sized loads and two pointer-sized
+stores, and it is incurred basically only for `OpBuiltin`.
+
+This all works, with only two issues:
+
+1. `vm.rs` needs to be very careful to do the thread-local cell swap
+   dance before calling anything that might call `PartialEq::eq()`
+   (or any other method that expects to be able to pull the `VM` out
+   of thread-local storage).  There is no compile-time check that we
+   did the dance in all the right places.  If we forget to do the
+   dance somewhere we'll get a runtime panic from `Option::expect()`
+   (see next section).
+
+2. Since we need to call `Cell::from_mut()` on a `Box<VM>` rather
+   than a bare `VM`, we still need to rewrite all of `vm.rs` so that
+   every function takes a `&mut Box<VM>` instead of a `&mut self`.
+   This creates a huge amount of "noise" in the code.
+
+Fortunately, it turns out that nearly all the "noise" that arises
+from the second point can be eliminated by taking advantage of
+[deref coercions][deref-coercions]!  This was the last "shoe to
+drop".
+
+There is still the issue of having to be careful about calls from
+`vm.rs` to things outside that file, but it's manageable.
+
+### `Cell<Option<Box<VM>>>`
+
+In order to get the "safe and stable `#[thread_local]`"
+[exception][tls-const-init] we need a `const` initializer, which
+means we need to be able to put something into the `Cell` that isn't
+a `VM`.  So the type needs to be `Cell<Option<Box<VM>>>`.
+
+Recall that you can't turn an `Option<&T>` into an `&Option<T>`.
+The latter type has the "is this a `Some` or `None`" bit immediately
+adjacent to the bits representing `T`.  So if I hand you a `t:&T`
+and you wrap it as `Some(t)`, those bits aren't adjacent in memory.
+This means that all the VM methods need to operate on an
+`Option<Box<VM>>` -- we can't just wrap a `Some()` around `&mut
+self` "at the last minute" before inserting it into the thread-local
+storage cell.  Fortunately deref coercions save the day here too --
+the coercion is inferred through both layers (`Box` and `Option`) of
+wrapper, so there is no additional noise in the code.
+
+Note that Rust is clever and can find some sequence of bits that
+aren't a valid `T`, so `sizeof(Option<T>)==sizeof(T)`.  And in fact,
+`Box<T>` is one of these cases (and this is guaranteed).  So the
+`Option` has no overhead.
+
+# Closing thoughts, language-level support
+
+This would have been easier with language-level support.
+
+## What wouldn't help
+
+Although it [it was decreed][fiat-decree] that `Cell<T>` and `&mut
+T` are interchangeable, a `LocalKey<Cell<T>>` isn't quite the same
+thing as a `Cell<T>`, so it wouldn't be safe for the standard
+library to contain something like this:
+
+```
+impl<T> LocalKey<Cell<T>> {
+  fn get_mut(&self) -> &mut T {
+    unsafe {
+      // ... mem::transmute() voodoo goes here ...
+```
+
+The problem here is that you can call `LocalKey<Cell<T>>::get_mut()` twice and
+end up with two `&mut T`s that point to the same thing (mutable aliasing) which
+results in undefined behavior.
+
+## What would help
+
+The ideal solution is for Rust to let you call arbitrary methods
+`T::foo(&mut self...)` on a `LocalKey<Cell<T>>`.  This way you can
+have one (and only one) `&mut T` at any syntactical point in your
+program -- the `&mut self`.
+
+
+[tls-const-init]: https://github.com/rust-lang/rust/pull/90774
+[thread-local-unstable]: https://github.com/rust-lang/rust/issues/29594
+[thread-local-unsafe-generally]: https://github.com/rust-lang/rust/issues/54366
+[fiat-decree]: https://github.com/rust-lang/rust/issues/43038
+[from_mut]: https://doc.rust-lang.org/stable/std/cell/struct.Cell.html#method.from_mut
+[get_mut]: https://doc.rust-lang.org/stable/std/cell/struct.Cell.html#method.get_mut
+[thread-local-unsafe]: [https://github.com/rust-lang/rust/issues/54366]
+[deref-coercions]: https://doc.rust-lang.org/book/ch15-02-deref.html#implicit-deref-coercions-with-functions-and-methods
+[threadlocal-intrinsic]: https://llvm.org/docs/LangRef.html#llvm-threadlocal-address-intrinsic
diff --git a/tvix/eval/docs/bindings.md b/tvix/eval/docs/bindings.md
new file mode 100644
index 0000000000..2b062cb13d
--- /dev/null
+++ b/tvix/eval/docs/bindings.md
@@ -0,0 +1,133 @@
+Compilation of bindings
+=======================
+
+Compilation of Nix bindings is one of the most mind-bending parts of Nix
+evaluation. The implementation of just the compilation is currently almost 1000
+lines of code, excluding the various insane test cases we dreamt up for it.
+
+## What is a binding?
+
+In short, any attribute set or `let`-expression. Tvix currently does not treat
+formals in function parameters (e.g. `{ name ? "fred" }: ...`) the same as these
+bindings.
+
+They have two very difficult features:
+
+1. Keys can mutually refer to each other in `rec` sets or `let`-bindings,
+   including out of definition order.
+2. Attribute sets can be nested, and parts of one attribute set can be defined
+   in multiple separate bindings.
+
+Tvix resolves as much of this logic statically (i.e. at compile-time) as
+possible, but the procedure is quite complicated.
+
+## High-level concept
+
+The idea behind the way we compile bindings is to fully resolve nesting
+statically, and use the usual mechanisms (i.e. recursion/thunking/value
+capturing) for resolving dynamic values.
+
+This is done by compiling bindings in several phases:
+
+1. An initial compilation phase *only* for plain inherit statements (i.e.
+   `inherit name;`), *not* for namespaced inherits (i.e. `inherit (from)
+   name;`).
+
+2. A declaration-only phase, in which we use the compiler's scope tracking logic
+   to calculate the physical runtime stack indices (further referred to as
+   "stack slots" or just "slots") that all values will end up in.
+
+   In this phase, whenever we encounter a nested attribute set, it is merged
+   into a custom data structure that acts like a synthetic AST node.
+
+   This can be imagined similar to a rewrite like this:
+
+   ```nix
+   # initial code:
+   {
+       a.b = 1;
+       a.c = 2;
+   }
+
+   # rewritten form:
+   {
+       a = {
+           b = 1;
+           c = 2;
+       };
+   }
+   ```
+
+   The rewrite applies to attribute sets and `let`-bindings alike.
+
+   At the end of this phase, we know the stack slots of all namespaces for
+   inheriting from, all values inherited from them, and all values (and
+   optionall keys) of bindings at the current level.
+
+   Only statically known keys are actually merged, so any dynamic keys that
+   conflict will lead to a "key already defined" error at runtime.
+
+3. A compilation phase, in which all values (and, when necessary, keys) are
+   actually compiled. In this phase the custom data structure used for merging
+   is encountered when compiling values.
+
+   As this data structure acts like an AST node, the process begins recursively
+   for each nested attribute set.
+
+At the end of this process we have bytecode that leaves the required values (and
+optionally keys) on the stack. In the case of attribute sets, a final operation
+is emitted that constructs the actual attribute set structure at runtime. For
+`let`-bindings a final operation is emitted that removes these locals from the
+stack when the scope ends.
+
+## Moving parts
+
+WARNING: This documents the *current* implementation. If you only care about the
+conceptual aspects, see above.
+
+There's a few types involved:
+
+* `PeekableAttrs`: peekable iterator over an attribute path (e.g. `a.b.c`)
+* `BindingsKind`: enum defining the kind of bindings (attrs/recattrs/let)
+* `AttributeSet`: struct holding the bindings kind, the AST nodes with inherits
+  (both namespaced and not), and an internal representation of bindings
+  (essentially a vector of tuples of the peekable attrs and the expression to
+  compile for the value).
+* `Binding`: enum describing the kind of binding (namespaced inherit, attribute
+  set, plain binding of *any other value type*)
+* `KeySlot`: enum describing the location in which a key slot is placed at
+  runtime (nowhere, statically known value in a slot, dynamic value in a slot)
+* `TrackedBinding`: struct representing statically known information about a
+  single binding (its key slot, value slot and `Binding`)
+* `TrackedBindings`: vector of tracked bindings, which implements logic for
+  merging attribute sets together
+
+And quite a few methods on `Compiler`:
+
+* `compile_bindings`: entry point for compiling anything that looks like a
+  binding, this calls out to the functions below.
+* `compile_plain_inherits`: takes all inherits of a bindings node and compiles
+  the ones that are trivial to compile (i.e. just plain inherits without a
+  namespace). The `rnix` parser does not represent namespaced/plain inherits in
+  different nodes, so this function also aggregates the namespaced inherits and
+  returns them for further use
+* `declare_namespaced_inherits`: passes over all namespaced inherits and
+  declares them on the locals stack, as well as inserts them into the provided
+  `TrackedBindings`
+* `declare_bindings`: declares all regular key/value bindings in a bindings
+  scope, but without actually compiling their keys or values.
+
+  There's a lot of heavy lifting going on here:
+
+  1. It invokes the various pieces of logic responsible for merging nested
+     attribute sets together, creating intermediate data structures in the value
+     slots of bindings that can be recursively processed the same way.
+  2. It decides on the key slots of expressions based on the kind of bindings,
+     and the type of expression providing the key.
+* `bind_values`: runs the actual compilation of values. Notably this function is
+  responsible for recursively compiling merged attribute sets when it encounters
+  a `Binding::Set` (on which it invokes `compile_bindings` itself).
+
+In addition to these several methods (such as `compile_attr_set`,
+`compile_let_in`, ...) invoke the binding-kind specific logic and then call out
+to the functions above.
diff --git a/tvix/eval/docs/build-references.md b/tvix/eval/docs/build-references.md
new file mode 100644
index 0000000000..badcea1155
--- /dev/null
+++ b/tvix/eval/docs/build-references.md
@@ -0,0 +1,254 @@
+Build references in derivations
+===============================
+
+This document describes how build references are calculated in Tvix. Build
+references are used to determine which store paths should be available to a
+builder during the execution of a build (i.e. the full build closure of a
+derivation).
+
+## String contexts in C++ Nix
+
+In C++ Nix, each string value in the evaluator carries an optional so-called
+"string context".
+
+These contexts are themselves a list of strings that take one of the following
+formats:
+
+1. `!<output_name>!<drv_path>`
+
+   This format describes a build reference to a specific output of a derivation.
+
+2. `=<drv_path>`
+
+   This format is used for a special case where a derivation attribute directly
+   refers to a derivation path (e.g. by accessing `.drvPath` on a derivation).
+
+   Note: In C++ Nix this case is quite special and actually requires a
+   store-database query during evaluation.
+
+3. `<path>` - a non-descript store path input, usually a plain source file (e.g.
+   from something like `src = ./.` or `src = ./foo.txt`).
+
+   In the case of `unsafeDiscardOutputDependency` this is used to pass a raw
+   derivation file, but *not* pull in its outputs.
+
+Lets introduce names for these (in the same order) to make them easier to
+reference below:
+
+```rust
+enum BuildReference {
+    /// !<output_name>!<drv_path>
+    SingleOutput(OutputName, DrvPath),
+
+    /// =<drv_path>
+    DrvClosure(DrvPath),
+
+    /// <path>
+    Path(StorePath),
+}
+```
+
+String contexts are, broadly speaking, created whenever a string is the result
+of a computation (e.g. string interpolation) that used a *computed* path or
+derivation in any way.
+
+Note: This explicitly does *not* include simply writing a literal string
+containing a store path (whether valid or not). That is only permitted through
+the `storePath` builtin.
+
+## Derivation inputs
+
+Based on the data above, the fields `inputDrvs` and `inputSrcs` of derivations
+are populated in `builtins.derivationStrict` (the function which
+`builtins.derivation`, which isn't actually a builtin, wraps).
+
+`inputDrvs` is represented by a map of derivation paths to the set of their
+outputs that were referenced by the context.
+
+TODO: What happens if the set is empty? Somebody claimed this means all outputs.
+
+`inputSrcs` is represented by a set of paths.
+
+These are populated by the above references as follows:
+
+* `SingleOutput` entries are merged into `inputDrvs`
+* `Path` entries are inserted into `inputSrcs`
+* `DrvClosure` leads to a special store computation (`computeFSClosure`), which
+  finds all paths referenced by the derivation and then inserts all of them into
+  the fields as above (derivations with _all_ their outputs)
+
+This is then serialised in the derivation and passed down the pipe.
+
+## Builtins interfacing with contexts
+
+C++ Nix has several builtins that interface directly with string contexts:
+
+* `unsafeDiscardStringContext`: throws away a string's string context (if
+  present)
+* `hasContext`: returns `true`/`false` depending on whether the string has
+  context
+* `unsafeDiscardOutputDependency`: drops dependencies on the *outputs* of a
+  `.drv` in the context, passing only the literal `.drv` itself
+
+  Note: This is only used for special test-cases in nixpkgs, and deprecated Nix
+  commands like `nix-push`.
+* `getContext`: returns the string context in serialised form as a Nix attribute
+  set
+* `appendContext`: adds a given string context to the string in the same format
+  as returned by `getContext`
+
+Most of the string manipulation operations will propagate the context to the
+result based on their parameters' contexts.
+
+## Placeholders
+
+C++ Nix has `builtins.placeholder`, which given the name of an output (e.g.
+`out`) creates a hashed string representation of that output name. If that
+string is used anywhere in input attributes, the builder will replace it with
+the actual name of the corresponding output of the current derivation.
+
+C++ Nix does not use contexts for this, it blindly creates a rewrite map of
+these placeholder strings to the names of all outputs, and runs the output
+replacement logic on all environment variables it creates, attribute files it
+passes etc.
+
+## Tvix & string contexts
+
+In the past, Tvix did not track string contexts in its evaluator at all, see
+the historical section for more information about that.
+
+Tvix tracks string contexts in every `NixString` structure via a
+`HashSet<BuildReference>` and offers an API to combine the references while
+keeping the exact internal structure of that data private.
+
+## Historical attempt: Persistent reference tracking
+
+We were investigating implementing a system which allows us to drop string
+contexts in favour of reference scanning derivation attributes.
+
+This means that instead of maintaining and passing around a string context data
+structure in eval, we maintain a data structure of *known paths* from the same
+evaluation elsewhere in Tvix, and scan each derivation attribute against this
+set of known paths when instantiating derivations.
+
+We believed we could take the stance that the system of string contexts as
+implemented in C++ Nix is likely an implementation detail that should not be
+leaking to the language surface as it does now.
+
+### Tracking "known paths"
+
+Every time a Tvix evaluation does something that causes a store interaction, a
+"known path" is created. On the language surface, this is the result of one of:
+
+1. Path literals (e.g. `src = ./.`).
+2. Calls to `builtins.derivationStrict` yielding a derivation and its output
+   paths.
+3. Calls to `builtins.path`.
+
+Whenever one of these occurs, some metadata that persists for the duration of
+one evaluation should be created in Nix. This metadata needs to be available in
+`builtins.derivationStrict`, and should be able to respond to these queries:
+
+1. What is the set of all known paths? (used for e.g. instantiating an
+   Aho-Corasick type string searcher)
+2. What is the _type_ of a path? (derivation path, derivation output, source
+   file)
+3. What are the outputs of a derivation?
+4. What is the derivation of an output?
+
+These queries will need to be asked of the metadata when populating the
+derivation fields.
+
+Note: Depending on how we implement `builtins.placeholder`, it might be useful
+to track created placeholders in this metadata, too.
+
+### Context builtins
+
+Context-reading builtins can be implemented in Tvix by adding `hasContext` and
+`getContext` with the appropriate reference-scanning logic. However, we should
+evaluate how these are used in nixpkgs and whether their uses can be removed.
+
+Context-mutating builtins can be implemented by tracking their effects in the
+value representation of Tvix, however we should consider not doing this at all.
+
+`unsafeDiscardOutputDependency` should probably never be used and we should warn
+or error on it.
+
+`unsafeDiscardStringContext` is often used as a workaround for avoiding IFD in
+inconvenient places (e.g. in the TVL depot pipeline generation). This is
+unnecessary in Tvix. We should evaluate which other uses exist, and act on them
+appropriately.
+
+The initial danger with diverging here is that we might cause derivation hash
+discrepancies between Tvix and C++ Nix, which can make initial comparisons of
+derivations generated by the two systems difficult. If this occurs we need to
+discuss how to approach it, but initially we will implement the mutating
+builtins as no-ops.
+
+### Why this did not work for us?
+
+Nix has a feature to perform environmental checks of your derivation, e.g.
+"these derivation outputs should not be referenced in this derivation", this was
+introduced in Nix 2.2 by
+https://github.com/NixOS/nix/commit/3cd15c5b1f5a8e6de87d5b7e8cc2f1326b420c88.
+
+Unfortunately, this feature introduced a very unfortunate and critical bug: all
+usage of this feature with contextful strings will actually force the
+derivation to depend at least at build time on those specific paths, see
+https://github.com/NixOS/nix/issues/4629.
+
+For example, if you wanted to `disallowedReferences` to a package and you used a
+derivation as a path, you would actually register that derivation as a input
+derivation of that derivation.
+
+This bug is still unfixed in Nix and it seems that fixing it would require
+introducing different ways to evaluate Nix derivations to preserve the
+output path calculation for Nix expressions so far.
+
+All of this would be fine if the bug behavior was uniform in the sense that no
+one tried to force-workaround it. Since Nixpkgs 23.05, due to
+https://github.com/NixOS/nixpkgs/pull/211783 this is not true anymore.
+
+If you let nixpkgs be the disjoint union of bootstrapping derivations $A$ and
+`stdenv.mkDerivation`-built derivations $B$.
+
+$A$ suffers from the bug and $B$ doesn't by the forced usage of
+`unsafeDiscardStringContext` on those special checking fields.
+
+This means that to build hash-compatible $A$ **and** $B$, we need to
+distinguish $A$ and $B$. A lot of hacks could be imagined to support this
+problem.
+
+Let's assume we have a solution to that problem, it means that we are able to
+detect implicitly when a set of specific fields are
+`unsafeDiscardStringContext`-ed.
+
+Thus, we could use that same trick to implement `unsafeDiscardStringContext`
+entirely for all fields actually.
+
+Now, to implement `unsafeDiscardStringContext` in the persistent reference
+tracking model, you will need to store a disallowed list of strings that should
+not trigger a reference when we are scanning a derivation parameters.
+
+But assume you have something like:
+
+```nix
+derivation {
+   buildInputs = [
+     stdenv.cc
+   ];
+
+   disallowedReferences = [ stdenv.cc ];
+}
+```
+
+If you unregister naively the `stdenv.cc` reference, it will silence the fact
+that it is part of the `buildInputs`, so you will observe that Nix will fail
+the derivation during environmental check, but Tvix would silently force remove
+that reference.
+
+Until proven otherwise, it seems highly difficult to have the fine-grained
+information to prevent reference tracking of those specific fields. It is not a
+failure of the persistent reference tracking, it is an unresolved critical bug
+of Nix that only nixpkgs really workarounded for `stdenv.mkDerivation`-based
+derivations.
diff --git a/tvix/eval/docs/builtins.md b/tvix/eval/docs/builtins.md
new file mode 100644
index 0000000000..dba4c48c65
--- /dev/null
+++ b/tvix/eval/docs/builtins.md
@@ -0,0 +1,138 @@
+Nix builtins
+============
+
+Nix has a lot of built-in functions, some of which are accessible in
+the global scope, and some of which are only accessible through the
+global `builtins` attribute set.
+
+This document is an attempt to track all of these builtins, but
+without documenting their functionality.
+
+See also https://nixos.org/manual/nix/stable/expressions/builtins.html
+
+The `impl` column indicates implementation status in tvix:
+- implemented: "" (empty cell)
+- not yet implemented, but not blocked: `todo`
+- not yet implemented, but blocked by other prerequisites:
+  - `store`: awaiting eval<->store api(s)
+  - `context`: awaiting support for string contexts
+
+| name                          | global | arity | pure  | impl    |
+|-------------------------------|--------|-------|-------|---------|
+| abort                         | true   | 1     |       |         |
+| add                           | false  | 2     | true  |         |
+| addErrorContext               | false  | ?     |       | context |
+| all                           | false  | 2     | true  |         |
+| any                           | false  | 2     | true  |         |
+| appendContext                 | false  | ?     |       |         |
+| attrNames                     | false  | 1     | true  |         |
+| attrValues                    | false  |       | true  |         |
+| baseNameOf                    | true   |       |       |         |
+| bitAnd                        | false  |       |       |         |
+| bitOr                         | false  |       |       |         |
+| bitXor                        | false  |       |       |         |
+| builtins                      | true   |       |       |         |
+| catAttrs                      | false  |       |       |         |
+| compareVersions               | false  |       |       |         |
+| concatLists                   | false  |       |       |         |
+| concatMap                     | false  |       |       |         |
+| concatStringsSep              | false  |       |       |         |
+| currentSystem                 | false  |       |       |         |
+| currentTime                   | false  |       | false |         |
+| deepSeq                       | false  |       |       |         |
+| derivation                    | true   |       |       | store   |
+| derivationStrict              | true   |       |       | store   |
+| dirOf                         | true   |       |       |         |
+| div                           | false  |       |       |         |
+| elem                          | false  |       |       |         |
+| elemAt                        | false  |       |       |         |
+| false                         | true   |       |       |         |
+| fetchGit                      | true   |       |       | store   |
+| fetchMercurial                | true   |       |       | store   |
+| fetchTarball                  | true   |       |       | store   |
+| fetchurl                      | false  |       |       | store   |
+| filter                        | false  |       |       |         |
+| filterSource                  | false  |       |       | store   |
+| findFile                      | false  |       | false | todo    |
+| foldl'                        | false  |       |       |         |
+| fromJSON                      | false  |       |       |         |
+| fromTOML                      | true   |       |       |         |
+| functionArgs                  | false  |       |       |         |
+| genList                       | false  |       |       |         |
+| genericClosure                | false  |       |       | todo    |
+| getAttr                       | false  |       |       |         |
+| getContext                    | false  |       |       |         |
+| getEnv                        | false  |       | false |         |
+| hasAttr                       | false  |       |       |         |
+| hasContext                    | false  |       |       |         |
+| hashFile                      | false  |       | false |         |
+| hashString                    | false  |       |       |         |
+| head                          | false  |       |       |         |
+| import                        | true   |       |       |         |
+| intersectAttrs                | false  |       |       |         |
+| isAttrs                       | false  |       |       |         |
+| isBool                        | false  |       |       |         |
+| isFloat                       | false  |       |       |         |
+| isFunction                    | false  |       |       |         |
+| isInt                         | false  |       |       |         |
+| isList                        | false  |       |       |         |
+| isNull                        | true   |       |       |         |
+| isPath                        | false  |       |       |         |
+| isString                      | false  |       |       |         |
+| langVersion                   | false  |       |       |         |
+| length                        | false  |       |       |         |
+| lessThan                      | false  |       |       |         |
+| listToAttrs                   | false  |       |       |         |
+| map                           | true   |       |       |         |
+| mapAttrs                      | false  |       |       |         |
+| match                         | false  |       |       |         |
+| mul                           | false  |       |       |         |
+| nixPath                       | false  |       |       | todo    |
+| nixVersion                    | false  |       |       | todo    |
+| null                          | true   |       |       |         |
+| parseDrvName                  | false  |       |       |         |
+| partition                     | false  |       |       |         |
+| path                          | false  |       | sometimes | store |
+| pathExists                    | false  |       | false |         |
+| placeholder                   | true   |       |       | context |
+| readDir                       | false  |       | false |         |
+| readFile                      | false  |       | false |         |
+| removeAttrs                   | true   |       |       |         |
+| replaceStrings                | false  |       |       |         |
+| scopedImport                  | true   |       |       |         |
+| seq                           | false  |       |       |         |
+| sort                          | false  |       |       |         |
+| split                         | false  |       |       |         |
+| splitVersion                  | false  |       |       |         |
+| storeDir                      | false  |       |       | store   |
+| storePath                     | false  |       |       | store   |
+| stringLength                  | false  |       |       |         |
+| sub                           | false  |       |       |         |
+| substring                     | false  |       |       |         |
+| tail                          | false  |       |       |         |
+| throw                         | true   |       |       |         |
+| toFile                        | false  |       |       | store   |
+| toJSON                        | false  |       |       |         |
+| toPath                        | false  |       |       |         |
+| toString                      | true   |       |       |         |
+| toXML                         | true   |       |       |         |
+| trace                         | false  |       |       |         |
+| true                          | true   |       |       |         |
+| tryEval                       | false  |       |       |         |
+| typeOf                        | false  |       |       |         |
+| unsafeDiscardOutputDependency | false  |       |       |         |
+| unsafeDiscardStringContext    | false  |       |       |         |
+| unsafeGetAttrPos              | false  |       |       | todo    |
+| valueSize                     | false  |       |       | todo    |
+
+## Added after C++ Nix 2.3 (without Flakes enabled)
+
+| name          | global | arity | pure  | impl  |
+|---------------|--------|-------|-------|-------|
+| break         | false  | 1     |       | todo  |
+| ceil          | false  | 1     | true  |       |
+| fetchTree     | true   | 1     |       | todo  |
+| floor         | false  | 1     | true  |       |
+| groupBy       | false  | 2     | true  |       |
+| traceVerbose  | false  | 2     |       | todo  |
+| zipAttrsWith  | false  | 2     | true  | todo  |
diff --git a/tvix/eval/docs/catchable-errors.md b/tvix/eval/docs/catchable-errors.md
new file mode 100644
index 0000000000..ce320a9217
--- /dev/null
+++ b/tvix/eval/docs/catchable-errors.md
@@ -0,0 +1,131 @@
+# (Possible) Implementation(s) of Catchable Errors for `builtins.tryEval`
+
+## Terminology
+
+Talking about “catchable errors” in Nix in general is a bit precarious since
+there is no properly established terminology. Also, the existing terms are less
+than apt. The reason for this lies in the fact that catchable errors (or
+whatever you want to call them) don't properly _exist_ in the language: While
+Nix's `builtins.tryEval` is (originally) based on the C++ exception system,
+it specifically lacks the ability of such systems to have an exception _value_
+whilst handling it. Consequently, these errors don't have an obvious name
+as they never appear _in_ the Nix language. They just have to be named in the
+respective Nix implementation:
+
+- In C++ Nix the only term for such errors is `AssertionError` which is the
+  name of the (C++) exception used in the implementation internally. This
+  term isn't great, though, as `AssertionError`s can not only be generated
+  using `assert`, but also using `throw` and failed `NIX_PATH` resolutions.
+  Were this terminology to be used in documentation addressing Nix language
+  users, it would probably only serve confusion.
+
+- Tvix currently (as of r/7573) uses the term catchable errors. This term
+  relates to nothing in the language as such: Errors are not caught, we rather
+  try to evaluate an expression. Catching also sort of implies that a value
+  representation of the error is attainable (like in an exception system) which
+  is untrue.
+
+In light of this I (sterni) would like to suggest “tryable errors” as an
+alternative term going forward which isn't inaccurate and relates to terms
+already established by language internal naming.
+
+However, this document will continue using the term catchable error until the
+naming is adjusted in Tvix itself.
+
+## Implementation
+
+Below we discuss different implementation approaches in Tvix in order to arrive
+at a proposal for the new one. The historical discussion is intended as a basis
+for discussing the proposal: Are we committing to an old or current mistake? Are
+we solving all problems that cropped up or were solved at any given point in
+time?
+
+### Original
+
+The original implementation of `tryEval` in cl/6924 was quite straightforward:
+It would simply interrupt the propagation of a potential catchable error to the
+top level (which usually happened using the `?` operator) in the builtin and
+construct the appropriate representation of an unsuccessful evaluation if the
+error was deemed catchable. It had, however, multiple problems:
+
+- The VM was originally written without `tryEval` in mind, i.e. it largely
+  assumed that an error would always cause execution to be terminated. This
+  problem was later solved (cl/6940).
+- Thunks could not be `tryEval`-ed multiple times (b/281). This was another
+  consequence of VM architecture at the time: Thunks would be blackholed
+  before evaluation was started and the error could occur. Due to the
+  interaction of the generator-based VM code and `Value::force` the part
+  of the code altering the thunk state would never be informed about the
+  evaluation result in case of a failure, so the thunk would remain
+  blackholed leading to a crash if the same thunk was `tryEval`-ed or
+  forced again. To solve this issue, amjoseph completely overhauled
+  the implementation.
+
+One key point about this implementation is that it is based on the assumption
+that catchable errors can only be generated in thunks, i.e. expressions causing
+them are never evaluated strictly. This can be illustrated using C++ Nix:
+
+```console
+> nix-instantiate --eval -E '[ (assert false; true) (builtins.throw "") <nixpkgs> ]'
+[ <CODE> <CODE> <CODE> ]
+```
+
+If this wasn't the case, the VM could encounter the error in a situation where
+the error would not have needed to pass through the `tryEval` builtin, causing
+evaluation to abort.
+
+### Present
+
+The current system (mostly implemented in cl/9289) uses a very different
+approach: Instead of relying on the thunk boundary, catchable errors are no
+longer errors, but special values. They are created at the relevant points (e.g.
+`builtins.throw`) and propagated whenever they are encountered by VM ops or
+builtins. Finally, they either encounter `builtins.tryEval` (and are converted to
+an ordinary value again) or the top level where they become a normal error again.
+
+The problems with this mostly stem from the confusion between values and errors
+that it necessitates:
+
+- In most circumstances, catchable errors end up being errors again, as `tryEval`
+  is not used a lot. So `throw`s usually end up causing evaluation to abort.
+  Consequently, not only `Value::Catchable` is necessary, but also a corresponding
+  error variant that is _only_ created if a catchable value remains at the end of
+  evaluation. A requirement that was missed until cl/10991 (!) which illustrate
+  how strange that architecture is. A consequence of this is that catchable
+  errors have no location information at all.
+- `Value::Catchable` is similar to other internal values in Tvix, but is much
+  more problematic. Aside from thunks, internal values only exist for a brief
+  amount of time on the stack and it is very clear what parts of the VM or
+  builtins need to handle them. This means that the rest of the implementation
+  need to consider them, keeping the complexity caused by the internal value
+  low. `Value::Catchable`, on the other hand, may exist anywhere and be passed
+  to any VM op or builtin, so it needs to be correctly propagated _everywhere_.
+  This causes a lot of noise in the code as well as a big potential for bugs.
+  Essentially, catchable errors require as much attention by the Tvix developer
+  as laziness. This doesn't really correlate to the importance of the two
+  features to the Nix language.
+
+### Future?
+
+The core assumption of the original solution does offer a path forward: After
+cl/9289 we should be in a better position to introspect an error occurring from
+within the VM code, but we need a better way of storing such an error to prevent
+another b/281. If catchable errors can only be generated in thunks, we can just
+use the thunk representation for this. This would mean that `Thunk::force_`
+would need to check if evaluation was successful and (in case of failure)
+change the thunk representation
+
+- either to the original `ThunkRepr::Suspended` which would be simple, but of
+  course mean duplicated evaluation work in some expressions. In fact, this
+  would probably leave a lot of easy performance on the table for use cases we
+  would like to support, e.g. tree walkers for nixpkgs.
+- or to a new `ThunkRepr` variant that stores the kind of the error and all
+  necessary location info so stack traces can work properly. This of course
+  reintroduces some of the difficulty of having two kinds of errors, but it is
+  hopefully less problematic, as the thunk boundary (i.e. `Thunk::force`) is
+  where errors would usually occur.
+
+Besides the question whether this proposal can actually be implemented, another
+consideration is whether the underlying assumption will hold in the future, i.e.
+can we implement optimizations for thunk elimination in a way that thunks that
+generate catchable errors are never eliminated?
diff --git a/tvix/eval/docs/known-optimisation-potential.md b/tvix/eval/docs/known-optimisation-potential.md
new file mode 100644
index 0000000000..0ab185fe1b
--- /dev/null
+++ b/tvix/eval/docs/known-optimisation-potential.md
@@ -0,0 +1,162 @@
+Known Optimisation Potential
+============================
+
+There are several areas of the Tvix evaluator code base where
+potentially large performance gains can be achieved through
+optimisations that we are already aware of.
+
+The shape of most optimisations is that of moving more work into the
+compiler to simplify the runtime execution of Nix code. This leads, in
+some cases, to drastically higher complexity in both the compiler
+itself and in invariants that need to be guaranteed between the
+runtime and the compiler.
+
+For this reason, and because we lack the infrastructure to adequately
+track their impact (WIP), we have not yet implemented these
+optimisations, but note the most important ones here.
+
+* Use "open upvalues" [hard]
+
+  Right now, Tvix will immediately close over all upvalues that are
+  created and clone them into the `Closure::upvalues` array.
+
+  Instead of doing this, we can statically determine most locals that
+  are closed over *and escape their scope* (similar to how the
+  `compiler::scope::Scope` struct currently tracks whether locals are
+  used at all).
+
+  If we implement the machinery to track this, we can implement some
+  upvalues at runtime by simply sticking stack indices in the upvalue
+  array and only copy the values where we know that they escape.
+
+* Avoid `with` value duplication [easy]
+
+  If a `with` makes use of a local identifier in a scope that can not
+  close before the with (e.g. not across `LambdaCtx` boundaries), we
+  can avoid the allocation of the phantom value and duplication of the
+  `NixAttrs` value on the stack. In this case we simply push the stack
+  index of the known local.
+
+* Multiple attribute selection [medium]
+
+  An instruction could be introduced that avoids repeatedly pushing an
+  attribute set to/from the stack if multiple keys are being selected
+  from it. This occurs, for example, when inheriting from an attribute
+  set or when binding function formals.
+
+* Split closure/function representation [easy]
+
+  Functions have fewer fields that need to be populated at runtime and
+  can directly use the `value::function::Lambda` representation where
+  possible.
+
+* Apply `compiler::optimise_select` to other set operations [medium]
+
+  In addition to selects, statically known attribute resolution could
+  also be used for things like `?` or `with`. The latter might be a
+  little more complicated but is worth investigating.
+
+* Inline fully applied builtins with equivalent operators [medium]
+
+  Some `builtins` have equivalent operators, e.g. `builtins.sub`
+  corresponds to the `-` operator, `builtins.hasAttr` to the `?`
+  operator etc. These operators additionally compile to a primitive
+  VM opcode, so they should be just as cheap (if not cheaper) as
+  a builtin application.
+
+  In case the compiler encounters a fully applied builtin (i.e.
+  no currying is occurring) and the `builtins` global is unshadowed,
+  it could compile the equivalent operator bytecode instead: For
+  example, `builtins.sub 20 22` would be compiled as `20 - 22`.
+  This would ensure that equivalent `builtins` can also benefit
+  from special optimisations we may implement for certain operators
+  (in the absence of currying). E.g. we could optimise access
+  to the `builtins` attribute set which a call to
+  `builtins.getAttr "foo" builtins` should also profit from.
+
+* Avoid nested `VM::run` calls [hard]
+
+  Currently when encountering Nix-native callables (thunks, closures)
+  the VM's run loop will nest and return the value of the nested call
+  frame one level up. This makes the Rust call stack almost mirror the
+  Nix call stack, which is usually undesirable.
+
+  It is possible to detect situations where this is avoidable and
+  instead set up the VM in such a way that it continues and produces
+  the desired result in the same run loop, but this is kind of tricky
+  to get right - especially while other parts are still in flux.
+
+  For details consult the commit with Gerrit change ID
+  `I96828ab6a628136e0bac1bf03555faa4e6b74ece`, in which the initial
+  attempt at doing this was reverted.
+
+* Avoid thunks if only identifier closing is required [medium]
+
+  Some constructs, like `with`, mostly do not change runtime behaviour
+  if thunked. However, they are wrapped in thunks to ensure that
+  deferred identifiers are resolved correctly.
+
+  This can be avoided, as we statically analyse the scope and should
+  be able to tell whether any such logic was required.
+
+* Intern literals [easy]
+
+  Currently, the compiler emits a separate entry in the constant
+  table for each literal.  So the program `1 + 1 + 1` will have
+  three entries in its `Chunk::constants` instead of only one.
+
+* Do some list and attribute set operations in place [hard]
+
+  Algorithms that can not do a lot of work inside `builtins` like `map`,
+  `filter` or `foldl'` usually perform terribly if they use data structures like
+  lists and attribute sets.
+
+  `builtins` can do work in place on a copy of a `Value`, but naïvely expressed
+  recursive algorithms will usually use `//` and `++` to do a single change to a
+  `Value` at a time, requiring a full copy of the data structure each time.
+  It would be a big improvement if we could do some of these operations in place
+  without requiring a new copy.
+
+  There are probably two approaches: We could determine statically if a value is
+  reachable from elsewhere and emit a special in place instruction if not. An
+  easier alternative is probably to rely on reference counting at runtime: If no
+  other reference to a value exists, we can extend the list or update the
+  attribute set in place.
+
+  An **alternative** to this is using [persistent data
+  structures](https://en.wikipedia.org/wiki/Persistent_data_structure) or at the
+  very least [immutable data structures](https://docs.rs/im/latest/im/) that can
+  be copied more efficiently than the stock structures we are using at the
+  moment.
+
+* Skip finalising unfinalised thunks or non-thunks instead of crashing [easy]
+
+  Currently `OpFinalise` crashes the VM if it is called on values that don't
+  need to be finalised. This helps catching miscompilations where `OpFinalise`
+  operates on the wrong `StackIdx`. In the case of function argument patterns,
+  however, this means extra VM stack and instruction overhead for dynamically
+  determining if finalisation is necessary or not. This wouldn't be necessary
+  if `OpFinalise` would just noop on any values that don't need to be finalised
+  (anymore).
+
+* Phantom binding for from expression of inherits [easy]
+
+  The from expression of an inherit is reevaluated for each inherit. This can
+  be demonstrated using the following Nix expression which, counter-intuitively,
+  will print “plonk” twice.
+
+  ```nix
+  let
+    inherit (builtins.trace "plonk" { a = null; b = null; }) a b;
+  in
+  builtins.seq a (builtins.seq b null)
+  ```
+
+  In most Nix code, the from expression is just an identifier, so it is not
+  terribly inefficient, but in some cases a more expensive expression may
+  be used. We should create a phantom binding for the from expression that
+  is reused in the inherits, so only a single thunk is created for the from
+  expression.
+
+  Since we discovered this, C++ Nix has implemented a similar optimization:
+  <https://github.com/NixOS/nix/pull/9847>.
diff --git a/tvix/eval/docs/language-issues.md b/tvix/eval/docs/language-issues.md
new file mode 100644
index 0000000000..152e6594a1
--- /dev/null
+++ b/tvix/eval/docs/language-issues.md
@@ -0,0 +1,46 @@
+# Nix language issues
+
+In the absence of a language standard, what Nix (the language) is, is prescribed
+by the behavior of the C++ Nix implementation. Still, there are reasons not to
+accept some behavior:
+
+* Tvix aims for nixpkgs compatibility only. This means we can ignore behavior in
+  edge cases nixpkgs doesn't trigger as well as obscure features it doesn't use
+  (e.g. `__overrides`).
+* Some behavior of the Nix evaluator seems to be unintentional or an
+  implementation detail leaking out into language behavior.
+
+Especially in the latter case, it makes sense to raise the respective issue and
+maybe to get rid of the behavior in all implementations for good. Below is an
+(incomplete) list of such issues:
+
+* [Behaviour of nested attribute sets depends on definition order][i7111]
+* [Partially constructed attribute sets are observable during dynamic attr names construction][i7012]
+* [Nix parsers merges multiple attribute set literals for the same key incorrectly depending on definition order][i7115]
+
+On the other hand, there is behavior that seems to violate one's expectation
+about the language at first, but has good enough reasons from an implementor's
+perspective to keep them:
+
+* Dynamic keys are forbidden in `let` and `inherit`. This makes sure that we
+  only need to do runtime identifier lookups for `with`. More dynamic (i.e.
+  runtime) lookups would make the scoping system even more complicated as well
+  as hurt performance.
+* Dynamic attributes of `rec` sets are not added to its scope. This makes sense
+  for the same reason.
+* Dynamic and nested attributes in attribute sets don't get merged. This is a
+  tricky one, but avoids doing runtime (recursive) merges of attribute sets.
+  Instead all necessary merging can be inferred statically, i.e. the C++ Nix
+  implementation already merges at parse time, making nested attribute keys
+  syntactic sugar effectively.
+
+Other behavior is just odd, surprising or underdocumented:
+
+* `builtins.foldl'` doesn't force the initial accumulator (but all other
+  intermediate accumulator values), differing from e.g. Haskell, see
+  the [relevant PR discussion][p7158].
+
+[i7111]: https://github.com/NixOS/nix/issues/7111
+[i7012]: https://github.com/NixOS/nix/issues/7012
+[i7115]: https://github.com/NixOS/nix/issues/7115
+[p7158]: https://github.com/NixOS/nix/pull/7158
diff --git a/tvix/eval/docs/recursive-attrs.md b/tvix/eval/docs/recursive-attrs.md
new file mode 100644
index 0000000000..c30cfd33e6
--- /dev/null
+++ b/tvix/eval/docs/recursive-attrs.md
@@ -0,0 +1,68 @@
+Recursive attribute sets
+========================
+
+The construction behaviour of recursive attribute sets is very
+specific, and a bit peculiar.
+
+In essence, there are multiple "phases" of scoping that take place
+during attribute set construction:
+
+1. Every inherited value without an explicit source is inherited only
+   from the **outer** scope in which the attribute set is enclosed.
+
+2. A new scope is opened in which all recursive keys are evaluated.
+   This only considers **statically known keys**, attributes can
+   **not** recurse into dynamic keys in `self`!
+
+   For example, this code is invalid in C++ Nix:
+
+   ```
+   nix-repl> rec { ${"a"+""} = 2; b = a * 10; }
+   error: undefined variable 'a' at (string):1:26
+   ```
+
+3. Finally, a third scope is opened in which dynamic keys are
+   evaluated.
+
+This behaviour, while possibly a bit strange and unexpected, actually
+simplifies the implementation of recursive attribute sets in Tvix as
+well.
+
+Essentially, a recursive attribute set like this:
+
+```nix
+rec {
+  inherit a;
+  b = a * 10;
+  ${"c" + ""} = b * 2;
+}
+```
+
+Can be compiled like the following expression:
+
+```nix
+let
+  inherit a;
+in let
+  b = a * 10;
+  in {
+    inherit a b;
+    ${"c" + ""} = b * 2;
+  }
+```
+
+Completely deferring the resolution of recursive identifiers to the
+existing handling of recursive scopes (i.e. deferred access) in let
+bindings.
+
+In practice, we can further specialise this and compile each scope
+directly into the form expected by `OpAttrs` (that is, leaving
+attribute names on the stack) before each value's position.
+
+C++ Nix's Implementation
+------------------------
+
+* [`ExprAttrs`](https://github.com/NixOS/nix/blob/2097c30b08af19a9b42705fbc07463bea60dfb5b/src/libexpr/nixexpr.hh#L241-L268)
+  (AST representation of attribute sets)
+* [`ExprAttrs::eval`](https://github.com/NixOS/nix/blob/075bf6e5565aff9fba0ea02f3333c82adf4dccee/src/libexpr/eval.cc#L1333-L1414)
+* [`addAttr`](https://github.com/NixOS/nix/blob/master/src/libexpr/parser.y#L98-L156) (`ExprAttrs` construction in the parser)
diff --git a/tvix/eval/docs/vm-loop.md b/tvix/eval/docs/vm-loop.md
new file mode 100644
index 0000000000..6266d34709
--- /dev/null
+++ b/tvix/eval/docs/vm-loop.md
@@ -0,0 +1,315 @@
+tvix-eval VM loop
+=================
+
+This document describes the new tvix-eval VM execution loop implemented in the
+chain focusing around cl/8104.
+
+## Background
+
+The VM loop implemented in Tvix prior to cl/8104 had several functions:
+
+1. Advancing the instruction pointer for a chunk of Tvix bytecode and
+   executing instructions in a loop until a result was yielded.
+
+2. Tracking Nix call frames as functions/thunks were entered/exited.
+
+3. Catching trampoline requests returned from instructions to force suspended
+   thunks without increasing stack size *where possible*.
+
+4. Handling trampolines through an inner trampoline loop, switching between a
+   code execution mode and execution of subsequent trampolines.
+
+This implementation of the trampoline logic was added on to the existing VM,
+which previously always recursed for thunk forcing. There are some cases (for
+example values that need to be forced *inside* of the execution of a builtin)
+where trampolines could not previously be used, and the VM recursed anyways.
+
+As a result of this trampoline logic being added "on top" of the existing VM
+loop the code became quite difficult to understand. This led to several bugs,
+for example: b/251, b/246, b/245, and b/238.
+
+These bugs were tricky to deal with, as we had to try and make the VM do
+things that are somewhat difficult to fit into its model. We could of course
+keep extending the trampoline logic to accommodate all sorts of concepts (such
+as finalisers), but that seems like it does not solve the root problem.
+
+## New VM loop
+
+In cl/8104, a unified new solution is implemented with which the VM is capable
+of evaluating everything without increasing the call stack size.
+
+This is done by introducing a new frame stack in the VM, on which execution
+frames are enqueued that are either:
+
+1. A bytecode frame, consisting of Tvix bytecode that evaluates compiled Nix
+   code.
+2. A generator frame, consisting of some VM logic implemented in pure Rust
+   code that can be *suspended* when it hits a point where the VM would
+   previously need to recurse.
+
+We do this by making use of the `async` *keyword* in Rust, but notably
+*without* introducing asynchronous I/O or concurrency in tvix-eval (the
+complexity of which is currently undesirable for us).
+
+Specifically, when writing a Rust function that uses the `async` keyword, such
+as:
+
+```rust
+async fn some_builtin(input: Value) -> Result<Value, ErrorKind> {
+  let mut out = NixList::new();
+
+  for element in input.to_list()? {
+    let result = do_something_that_requires_the_vm(element).await;
+    out.push(result);
+  }
+
+  Ok(out)
+}
+```
+
+The compiler actually generates a state-machine under-the-hood which allows
+the execution of that function to be *suspended* whenever it hits an `await`.
+
+We use the [`genawaiter`][] crate that gives us a data structure and simple
+interface for getting instances of these state machines that can be stored in
+a struct (in our case, a *generator frame*).
+
+The execution of the VM then becomes the execution of an *outer loop*, which
+is responsible for selecting the next generator frame to execute, and two
+*inner loops*, which drive the execution of a bytecode frame or generator
+frame forward until it either yields a value or asks to be suspended in favour
+of another frame.
+
+All "communication" between frames happens solely through values left on the
+stack: Whenever a frame of either type runs to completion, it is expected to
+leave a *single* value on the stack. It follows that the whole VM, upon
+completion of the last (or initial, depending on your perspective) frame
+yields its result as the return value.
+
+The core of the VM restructuring is cl/8104, unfortunately one of the largest
+single commit changes we've had to make yet, as it touches pretty much all
+areas of tvix-eval. The introduction of the generators and the
+message/response system we built to request something from the VM, suspend a
+generator, and wait for the return is in cl/8148.
+
+The next sections describe in detail how the three different loops work.
+
+### Outer VM loop
+
+The outer VM loop is responsible for selecting the next frame to run, and
+dispatching it correctly to inner loops, as well as determining when to shut
+down the VM and return the final result.
+
+```
+                          ╭──────────────────╮
+                 ╭────────┤ match frame kind ├──────╮
+                 │        ╰──────────────────╯      │
+                 │                                  │
+    ┏━━━━━━━━━━━━┷━━━━━┓                ╭───────────┴───────────╮
+───►┃ frame_stack.pop()┃                ▼                       ▼
+    ┗━━━━━━━━━━━━━━━━━━┛       ┏━━━━━━━━━━━━━━━━┓      ┏━━━━━━━━━━━━━━━━━┓
+                 ▲             ┃ bytecode frame ┃      ┃ generator frame ┃
+                 │             ┗━━━━━━━━┯━━━━━━━┛      ┗━━━━━━━━┯━━━━━━━━┛
+                 │[yes, cont.]          │                       │
+                 │                      ▼                       ▼
+    ┏━━━━━━━━┓   │             ╔════════════════╗      ╔═════════════════╗
+◄───┨ return ┃   │             ║ inner bytecode ║      ║ inner generator ║
+    ┗━━━━━━━━┛   │             ║      loop      ║      ║      loop       ║
+        ▲        │             ╚════════╤═══════╝      ╚════════╤════════╝
+        │   ╭────┴─────╮                │                       │
+        │   │ has next │                ╰───────────┬───────────╯
+   [no] ╰───┤  frame?  │                            │
+            ╰────┬─────╯                            ▼
+                 │                         ┏━━━━━━━━━━━━━━━━━┓
+                 │                         ┃ frame completed ┃
+                 ╰─────────────────────────┨  or suspended   ┃
+                                           ┗━━━━━━━━━━━━━━━━━┛
+```
+
+Initially, the VM always pops a frame from the frame stack and then inspects
+the type of frame it found. As a consequence the next frame to execute is
+always the frame at the top of the stack, and setting up a VM initially for
+code execution is done by leaving a bytecode frame with the code to execute on
+the stack and passing control to the outer loop.
+
+Control is dispatched to either of the inner loops (depending on the type of
+frame) and the cycle continues once they return.
+
+When an inner loop returns, it has either finished its execution (and left its
+result value on the *value stack*), or its frame has requested to be
+suspended.
+
+Frames request suspension by re-enqueueing *themselves* through VM helper
+methods, and then leaving the frame they want to run *on top* of themselves in
+the frame stack before yielding control back to the outer loop.
+
+The inner control loops inform the outer loops about whether the frame has
+been *completed* or *suspended* by returning a boolean.
+
+### Inner bytecode loop
+
+The inner bytecode loop drives the execution of some Tvix bytecode by
+continously looking at the next instruction to execute, and dispatching to the
+instruction handler.
+
+```
+   ┏━━━━━━━━━━━━━┓
+◄──┨ return true ┃
+   ┗━━━━━━━━━━━━━┛
+          ▲
+     ╔════╧═════╗
+     ║ OpReturn ║
+     ╚══════════╝
+          ▲
+          ╰──┬────────────────────────────╮
+             │                            ▼
+             │                 ╔═════════════════════╗
+    ┏━━━━━━━━┷━━━━━┓           ║ execute instruction ║
+───►┃ inspect next ┃           ╚══════════╤══════════╝
+    ┃  instruction ┃                      │
+    ┗━━━━━━━━━━━━━━┛                      │
+             ▲                      ╭─────┴─────╮
+             ╰──────────────────────┤ suspends? │
+                       [no]         ╰─────┬─────╯
+                                          │
+                                          │
+   ┏━━━━━━━━━━━━━━┓                       │
+◄──┨ return false ┃───────────────────────╯
+   ┗━━━━━━━━━━━━━━┛              [yes]
+```
+
+With this refactoring, the compiler now emits a special `OpReturn` instruction
+at the end of bytecode chunks. This is a signal to the runtime that the chunk
+has completed and that its current value should be returned, without having to
+perform instruction pointer arithmetic.
+
+When `OpReturn` is encountered, the inner bytecode loop returns control to the
+outer loop and informs it (by returning `true`) that the bytecode frame has
+completed.
+
+Any other instruction may also request a suspension of the bytecode frame (for
+example, instructions that need to force a value). In this case the inner loop
+is responsible for setting up the frame stack correctly, and returning `false`
+to inform the outer loop of the suspension
+
+### Inner generator loop
+
+The inner generator loop is responsible for driving the execution of a
+generator frame by continously calling [`Gen::resume`][] until it requests a
+suspension (as a result of which control is returned to the outer loop), or
+until the generator is done and yields a value.
+
+```
+   ┏━━━━━━━━━━━━━┓
+◄──┨ return true ┃ ◄───────────────────╮
+   ┗━━━━━━━━━━━━━┛                     │
+                                       │
+                               [Done]  │
+                    ╭──────────────────┴─────────╮
+                    │ inspect generator response │◄────────────╮
+                    ╰──────────────────┬─────────╯             │
+                            [yielded]  │              ┏━━━━━━━━┷━━━━━━━━┓
+                                       │              ┃ gen.resume(msg) ┃◄──
+                                       ▼              ┗━━━━━━━━━━━━━━━━━┛
+                                 ╭────────────╮                ▲
+                                 │ same-frame │                │
+                                 │  request?  ├────────────────╯
+                                 ╰─────┬──────╯      [yes]
+   ┏━━━━━━━━━━━━━━┓                    │
+◄──┨ return false ┃ ◄──────────────────╯
+   ┗━━━━━━━━━━━━━━┛                [no]
+```
+
+On each execution of a generator frame, `resume_with` is called with a
+[`VMResponse`][] (i.e. a message *from* the VM *to* the generator). For a newly
+created generator, the initial message is just `Empty`.
+
+A generator may then respond by signaling that it has finished execution
+(`Done`), in which case the inner generator loop returns control to the outer
+loop and informs it that this generator is done (by returning `true`).
+
+A generator may also respond by signaling that it needs some data from the VM.
+This is implemented through a request-response pattern, in which the generator
+returns a `Yielded` message containing a [`VMRequest`][]. These requests can be
+very simple ("Tell me the current store path") or more complex ("Call this Nix
+function with these values").
+
+Requests are divided into two classes: Same-frame requests (requests that can be
+responded to *without* returning control to the outer loop, i.e. without
+executing a *different* frame), and multi-frame generator requests. Based on the
+type of request, the inner generator loop will either handle it right away and
+send the response in a new `resume_with` call, or return `false` to the outer
+generator loop after setting up the frame stack.
+
+Most of this logic is implemented in cl/8148.
+
+[`Gen::resume`]: https://docs.rs/genawaiter/0.99.1/genawaiter/rc/struct.Gen.html#method.resume_with
+[`VMRequest`]: https://cs.tvl.fyi/depot@2696839770c1ccb62929ff2575a633c07f5c9593/-/blob/tvix/eval/src/vm/generators.rs?L44
+[`VMResponse`]: https://cs.tvl.fyi/depot@2696839770c1ccb62929ff2575a633c07f5c9593/-/blob/tvix/eval/src/vm/generators.rs?L169
+
+## Advantages & Disadvantages of the approach
+
+This approach has several advantages:
+
+* The execution model is much simpler than before, making it fairly
+  straightforward to build up a mental model of what the VM does.
+
+* All "out of band requests" inside the VM are handled through the same
+  abstraction (generators).
+
+* Implementation is not difficult, albeit a little verbose in some cases (we
+  can argue about whether or not to introduce macros for simplifying it).
+
+* Several parts of the VM execution are now much easier to document,
+  potentially letting us onboard tvix-eval contributors faster.
+
+* The linear VM execution itself is much easier to trace now, with for example
+  the `RuntimeObserver` (and by extension `tvixbolt`) giving much clearer
+  output now.
+
+But it also comes with some disadvantages:
+
+* Even though we "only" use the `async` keyword without a full async-I/O
+  runtime, we still encounter many of the drawbacks of the fragmented Rust
+  async ecosystem.
+
+  The biggest issue with this is that parts of the standard library become
+  unavailable to us, for example the built-in `Vec::sort_by` can no longer be
+  used for sorting in Nix because our comparators themselves are `async`.
+
+  This led us to having to implement some logic on our own, as the design of
+  `async` in Rust even makes it difficult to provide usecase-generic
+  implementations of concepts like sorting.
+
+* We need to allocate quite a few new structures on the heap in order to drive
+  generators, as generators involve storing `Future` types (with unknown
+  sizes) inside of structs.
+
+  In initial testing this seems to make no significant difference in
+  performance (our performance in an actual nixpkgs-eval is still bottlenecked
+  by I/O concerns and reference scanning), but is something to keep in mind
+  later on when we start optimising more after the low-hanging fruits have
+  been reaped.
+
+## Alternatives considered
+
+1. Tacking on more functionality onto the existing VM loop
+   implementation to accomodate problems as they show up. This is not
+   preferred as the code is already getting messy.
+
+2. Making tvix-eval a fully `async` project, pulling in something like Tokio
+   or `async-std` as a runtime. This is not preferred due to the massively
+   increased complexity of those solutions, and all the known issues of fully
+   buying in to the async ecosystem.
+
+   tvix-eval fundamentally should work for use-cases besides building Nix
+   packages (e.g. for `//tvix/serde`), and its profile should be as slim as
+   possible.
+
+3. Convincing the Rust developers that Rust needs a way to guarantee
+   constant-stack-depth tail calls through something like a `tailcall`
+   keyword.
+
+4. ... ?
+
+[`genawaiter`]: https://docs.rs/genawaiter/
diff --git a/tvix/eval/proptest-regressions/value/mod.txt b/tvix/eval/proptest-regressions/value/mod.txt
new file mode 100644
index 0000000000..05b01b4c76
--- /dev/null
+++ b/tvix/eval/proptest-regressions/value/mod.txt
@@ -0,0 +1,10 @@
+# Seeds for failure cases proptest has generated in the past. It is
+# automatically read and these particular cases re-run before any
+# novel cases are generated.
+#
+# It is recommended to check this file in to source control so that
+# everyone who runs the test benefits from these saved cases.
+cc 241ec68db9f684f4280d4c7907f7105e7b746df433fbb5cbd6bf45323a7f3be0 # shrinks to input = _ReflexiveArgs { x: List(NixList([List(NixList([Path("𑁯")]))])) }
+cc b6ab5fb25f5280f39d2372e951544d8cc9e3fcd5da83351266a0a01161e12dd7 # shrinks to input = _ReflexiveArgs { x: Attrs(NixAttrs(KV { name: Path("𐎸-{\u{a81}lq9Z"), value: Bool(false) })) }
+cc 3656053e7a8dbe1c01dd68a8e06840fb6e693dde942717a7c18173876d9c2cce # shrinks to input = _SymmetricArgs { x: List(NixList([Path("\u{1daa2}:.H🢞ୡ\\🕴7iu𝋮T𝓕\\%i:"), Integer(-7435423970896550032), Float(1.2123587650724335e-5), Integer(5314432620816586712), Integer(-8316092768376026052), Integer(-7632521684027819842), String(NixString(Smol("ᰏ᥀\\\\ȷf8=\u{2003}\"𑁢רּA/%�bQffl<౯Ⱥ\u{1b3a}`T{"))), Bool(true), String(NixString(Smol("Ⱥힳ\"<\\`tZ/�൘🢦Ⱥ=x𑇬"))), Null, Bool(false), String(NixString(Smol(";%'࿎.ೊ🉡𑌊/🕴3Ja"))), Null, String(NixString(Smol("vNᛦ=\\`𝐓P\\"))), Bool(true), Null, String(NixString(Smol("\"zાë\u{11cb6}6%yꟽ𚿾🡖`!"))), Integer(1513983844724992869), Bool(true), Float(-8.036903674864022e114), Path("G࿐�/᠆₫%𝈇P"), Bool(false), Bool(true), Null, Attrs(NixAttrs(Empty)), Float(-6.394835856260315e-46), Null, Path("G?🭙O<🟰𐮭𑤳�*ܥ𞹉`?$/j1=p𑙕h\u{e0147}\u{1cf3b}"), Bool(false), Bool(false), Path("$"), Float(7.76801238087078e-309), Integer(-4304837936532390878), Attrs(NixAttrs(Im({NixString(Smol("")): Float(-1.5117468628684961e-307), NixString(Smol("#=B\"o~Ѩ\"Ѩ𐠅T")): String(NixString(Smol("Kঅ&NVꩋࠔ'𝄏<"))), NixString(Heap("&¥sଲ\"\\=𑖺Q𚿾VTຐ[ﬓ%")): Integer(-9079075359788064855), NixString(Smol("'``{:5𑚞=l𑣿")): Bool(true), NixString(Smol("*𐖚")): Bool(false), NixString(Heap(":*𐖛𑵨%C'Ѩ")): Null, NixString(Smol("?ծ/෴")): Bool(true), NixString(Smol("W𑌏")): Float(-1.606122565666547e-309), NixString(Smol("`𐺭¥\u{9d7}𖿡1Y𖤛>")): Float(0.0), NixString(Heap("{&]\\𞅂𞅅&7ସ")): Path("\"*o$ଇ🛢𞸤�🉐�🃏?##bﷅ|a¥࿔ᓊ\u{bd7}𑆍W?P𑊌𑩰"), NixString(Smol("Ⱥ\u{10a3a}a/🕴'\u{b3e}𐦨Sᅩ$1kw\u{cd5}B)\u{e01b7}1:R@")): Path("-*꣒=#\\🄣ﭘ𑴃\\ᤖ%3\u{1e00e}{YബL\'GE<|aȺ:\u{1daa8}𐮯{ಯ𝑠\\"), NixString(Smol("מ'%:")): Integer(-5866590459020557377), NixString(Heap("୪3\u{1a79}-l\u{bd7}Ξ<ন<")): Float(1.4001769654461214e-61), NixString(Heap("ங𞹔𑊍")): Path("*%q<%=LU.TჍw𭴟[Ѩ𑌭𞁈?Ì?X%מּ¥𞲥𞹒ౚ"), NixString(Heap("டѨמּ&'𐌺𫈔.=\u{5af}\u{10a39}₼\\G")): String(NixString(Smol("ₙ.\u{10f83}<x/Ⱥ𝼐$=𑵧{jE𞹺/f*𐢯ഒ𖿣﹂:ᨧ𐞴¥`%"))), NixString(Smol("𐠈𐠁`\u{b57}ꛪ`@$y")): Float(1.2295541964458574e-308), NixString(Smol("𐮫`:𐠂$4%\u{ac5}𐕯🕴")): String(NixString(Heap("='/𑍐<🕴\"=I᭻9𐮙\u{1e01b}"))), NixString(Heap("𑌏({?kG:ﺡ𑌵0𝒚q\"ঐ")): String(NixString(Heap(":ਕ"))), NixString(Heap("𑌐𞸡?𑙓,?🩒7\":'..'<𐀏𑓒Ეd𑊈ῠ𖽙3'&🀪𞅏")): Path("."), NixString(Smol("𑒬𑵡?KᲾWȺ`ᅯ0{<ኘzEÃ\"û𝤕$🞀𝋯�.\"𐕾\u{1daad}㈃\\𐤿ʊs")): Integer(2276487328720525493), NixString(Heap("𝔾\u{11c9f}<Δ]T𐞡𐞵𞹉�")): Bool(false), NixString(Heap("🕴Ⱥ\u{f82}/%*𛅐᠐Ѩ$🢕 *�)𝓁𑃀𝒦𐝂ö𑤕Ѩh")): Path("🕴")}))), String(NixString(Smol("&m𝈶%&+\u{ecd}:&¥\u{11d3d}°%'.𞹙2\u{10eac}-ඈ\u{11369}𞹰¥𒑴'xѨ𞹝.𑼖V"))), Path(""), Path("𑫒%J\u{11ca3}"), Integer(5535573838430566518), String(NixString(Heap("⿵𛅐🀰Z$,\\/v\\⏇\u{a02}ள𝕁?\u{11ef4}%&/�|\"<Ⱥ&cUÛἜᥣ𐠼𘴄"))), Float(-1.0028921870468647e243), Path(".j*𑣱ÜM𑈅I?MvZy:𐄂�𞹋%?Ⴧ%"), Null, String(NixString(Smol("ퟘ🕴𝒴𒐋$\u{afd}ਃÜѨ`\u{11d44}E\\;\"?𬛕$"))), String(NixString(Heap("/)!.P🇪ਾ'⮮𐰣א=tៜ⮏m:\u{1773}\"Ⴧsቪ+HNk"))), Float(-3.7874900882792316e-77), String(NixString(Heap("Ⱥ<𐺰L:🭝𐡆𞅀Ѩ𑬄a.m𐀼V\"𝋊A𝄀\u{1e131}﹝"))), Path("aᨩ�?"), Float(6.493959567338054e87), Null, Null, Float(1.13707995848205e-115), Integer(-4231403127163468251), Float(-0.0), Float(-1.1096242386070431e-45), Integer(-5080222544395825040), Integer(2218353666908906569), Bool(false), Bool(true), Null, Float(-334324631469448.56), String(NixString(Heap("j%ѨáѨਭ\"᠖𐔅𛲂"))), Null, Float(5.823165830825334e-224), Path("&𞹋𖭖:$\\𑂻&ኊ(𞹋LH{ꟓ@=\\nલ&lyໃd"), String(NixString(Smol(""))), Bool(false), Float(1.0892031562808535e81), Null, Integer(-3110788464663743166), Bool(false), Null, Null])), y: List(NixList([Integer(7749573686807634185), Float(0.0), Attrs(NixAttrs(Im({NixString(Heap("")): Null, NixString(Smol("*<Y")): Bool(true), NixString(Heap("*\u{eb5}*:꒶Ѩ&m🕴🛫:_\u{ecb}1$pk!\u{1183a}b*:")): String(NixString(Heap("🕴𞻰𛃁𞹛𱬒Ⱥ=*]\u{1bad}t\u{11d40}ං𞓤꧑\"Ⱥ\"\u{9d7}𬇨$/Û\"zz*ఏ"))), NixString(Smol("?�𐄀&TbY&<'ᾍ?ⶱ工=%¥Ѩ:<Ѩ")): Null, NixString(Smol("Y𐣵🢅$è𝕊f&5ꬢN🕴🉁z�𐺰Mꕁ")): Integer(7378053691404749008), NixString(Heap("]\u{1712}&🕴{𛲗భ")): Path("`ꬪᝋȺѨ𞹴:Ⱥꕎwp𖩂Ⴧ𑜶ল2/?¥\"DL¥?�\'𞹇𛅒p=Ⱥ"), NixString(Heap("`𝋤\"m\\.𐠼🕴𖺊?")): Null, NixString(Heap("\u{a71}ࡷ6lꙧ{�ன`&�GןQ$(")): String(NixString(Heap("f\"<\"X!"))), NixString(Heap("᪠cꪙ\\ਲࠐ🕴?Ⱥ\u{11c97}<🕴«Ù\u{10eff}𐒣:<íN%y\\𐮙ꩅR\"=e𐺭")): Float(0.0), NixString(Smol("𐮙MჍ:%ஊ\u{dd6}$Ù?,)Z/𑌉?Qo?=\u{b01}cȺ,\\*")): Integer(-750568219128686012), NixString(Smol("𖫵𑶨ಭ\u{10a06}\\C_=🕴\u{10eac}೨喝W]\u{fb3}Ⱥ8")): Path("vLeড়ys..Ѩ�𑈓!ଳ&y<ໂ§{EUⴭ꩗U*\u{1e132}"), NixString(Smol("𞢀TAC\\*2🕴>%Ѩ𑩮P?G\u{a0}f𮗻*v¥Uq\u{b62}")): Float(3.882048884582667e95), NixString(Smol("𞸱𐏍2$ml.?.*U𫊴꭪<~gへ𑾰")): Null, NixString(Smol("🕴%`᭄N{3?k𑓗%:/D𑤘ᅴP^9=Z៦")): Null, NixString(Smol("𫘦*ຢ'7﹠KѨj𝔊q\u{bc0}bલ𐤿%🕴�𞠳�L&¥")): Null}))), Path("A\u{a3c}l᪅q.ȺA&"), Integer(4907301083808036161), Bool(false), Null, Bool(true), Attrs(NixAttrs(Empty)), Bool(false), String(NixString(Smol("᧹H$T൧𞅏=𞹟🕴{[y"))), Path("\u{1da43}$�uῚÈ¥�¥\'\u{b82}ල\u{11d3a}Ⱥ:🆨`𝍨1%`=ꝵ\u{11d41}\\X:*ྈኋ𞸯"), Integer(-8507829082635299848), Integer(-3606086848261110558), Float(2.2176784412249313e-278), Bool(true), Float(-1.1853240079167073e253), Path("=𐠕ߕ*🕴ȺȺ:Ⱥ%L💇Ô\\`Ὑ%🢖ዀ ൿ\\🕴Iቫ=~;\"ȺΌ"), Null, Attrs(NixAttrs(Im({NixString(Heap("")): Float(-7.81298889375788e-309), NixString(Smol("%>=ꨜᇲກ𖭜\u{1a60}Q(/X㈌\"*{ㄟ`¨=&'")): Bool(true), NixString(Heap("%n𛄲Y'𞹧𞺧")): Bool(true), NixString(Smol("&/.ȺV�︒\u{afb}'𞹔~\\Oᬽ�")): Float(8.59582436625354e-309), NixString(Heap("&y")): Path(".<\'ꢿ.W&¥�"), NixString(Smol("'1\u{11d91}¥O-𑨩ȺrὝ¥:Wෳቍq𑼌@^𑊜?")): Bool(true), NixString(Smol("*E𑖬ꦊ¹𛅤ೡ/'𝐔j𐖏𞸹7)ㅚ")): Integer(3181119108337466410), NixString(Smol("*r¥[.ª\u{10a3a}\u{1e132}EB⵰𞹗")): String(NixString(Heap("{🟰𖫧🢚/𐍰'𞸁ൻ🃪�𛲃?s&2𑴉"))), NixString(Smol("*\u{1e08f}Sü🕴N'Eƪ")): Bool(false), NixString(Smol("/=`Ⱥ")): Float(-1.8155608059955555e-303), NixString(Heap("/੮RE=L,/*\"'𑊿=�+�🡨ᢒ𖮁ਹ𑱟Ѩk᧧\"\"R")): Integer(-1311391009295683341), NixString(Heap("?%$'<<-23᪕^ቝȺj\\𐴆")): Null, NixString(Heap("?&?⑁ವ\u{bd7}𑓘\u{112e9}ᝑl9p`")): Integer(-5524232499716374878), NixString(Smol("?״{j\"8ࢿ𞹔")): Integer(1965352054196388057), NixString(Smol("@𬖗{0.:?")): Null, NixString(Heap("C\"$𖮂\"/𐬗IyU_𑴈/N=\u{ac7}𐬉:ꬁ<ꟓ<.えG𖦐I/ᠤ")): Float(-2.6565358047869407e-299), NixString(Heap("G`🟡y𖾝𐣴`#+'<")): Float(-1.643412216185574e-73), NixString(Smol("O𞹤៷.?𒑲")): Integer(8639041881884941660), NixString(Smol("R<X𐚒¥=ጚ,.ᤦ/?{\u{b57}꯴ೝ/¥m🕴პ🟰f𒑱X_.")): Null, NixString(Heap("R珞એ�$f")): Null, NixString(Heap("ZbȺὖ")): Integer(300189569873494072), NixString(Smol("\\9r𝒢𐩈¥Ð𐼚\\?Ѩ{$")): Integer(-5531416284385072043), NixString(Smol("`j$�ⓧ\u{16b34}'ⷆt\\𞠏|🢒'%&𑂕𐖼/$\u{ac7}")): String(NixString(Heap("1=¡<𐞵១🢜Ð℧p\\4𐨗𐤿=ශ`.[<\u{dd6}."))), NixString(Smol("`ᥳ")): Bool(true), NixString(Heap("`𐨕``ቓ𑜿$*Dᤱ`:/}🕴N'𘅺ൎ7")): Path("/"), NixString(Heap("fෳ\\ßϐ*𞸭'%𑤁$jા=:Ѩt{\"0ߢ/ಐ𐁆𞹛i𗹱'🕴")): Bool(false), NixString(Smol("n6&<𞟤'JB¦x🩤vቚ\u{1e008}Ѩ¥𐖛j🕴b¥𐼙'\u{c00}a")): Path("\u{110c2}p:Ѩ\u{1a58}ⶰO<?𞸡𐖙"), NixString(Smol("ntU爵�^🪫'%&ਰ/")): Bool(false), NixString(Smol("o")): Path("¥�\'/ìRV𐄚"), NixString(Smol("t\u{2df0}b.𐠈¹𰃦*𖭞:🮻𛅤7𞢼𑊩XL\\ਐ.N")): Null, NixString(Heap("yg'«🡓")): Path("/`]𞻰`\u{1cf0c}Ë\u{c55}<bȺ�!"), NixString(Heap("y🕴𑤑/ල$🢱\\~\u{aaec}`ຄ")): String(NixString(Smol(":b🭑𑅟ౘත'�:hiⷊ*{*/ꙟ"))), NixString(Smol("{?^n𑴉🩻៵o<ಮz-뗨")): Bool(false), NixString(Smol("ð୭ண?𑅢\\<<%?<=$[<d𑋶\\w𐖔u<")): Attrs(NixAttrs(KV { name: Path("ଛ🕴R`𱡍=\u{2028}?¤𐔘ῐw41A𑃰𑥙<&:/"), value: Integer(-4007996343681736077) })), NixString(Smol("ù5 \u{c4a}ᝮ")): List(NixList([Integer(3829726756090354238), Bool(false), Integer(-7605748774039015772), Integer(-2904585304126015516), Float(1.668751782763388e125), Path("ጓ𑍐״𑖁$~෮¥"), Float(0.00010226480079820994), String(NixString(Heap("y¥C\u{c62}3"))), Integer(4954265069162436553), String(NixString(Heap("f𝔗/^%}₧Ѩ᪈\u{aa43}$𐆔𘴄𑤤N\u{c47}𑃹𑧄⿹𞹻"))), Bool(false), Float(7.883608538511117e36), Path("*ષඉ"), Integer(8893667840985960833), Null, String(NixString(Heap("Ѩ|f2\u{11300}Ⱥ\u{11374}🕴࿘\\e$ᢊR𑌐๔Ⴧ$�'`\u{1e028}🕴"))), Path("<Uc?𞹑\"🕴ᥳ:/ꬍ=ꮫ\\:ךּ&&jq\u{11d41}<_�%(῝Ⱥ�"), Float(-3.7947462255873233e189), Integer(1963485019377075037), Null, Integer(2642884952152033378), String(NixString(Heap("=\\Ѩqদ)%@�NH𑼄ⴭ.ዀ*Ⱥ$&\u{d01}`ভI𑩧h\u{1da9e}v𑀰/wl"))), Float(-3.1057935562909707e-153), Path("ÊȺ𐬗$?\'$ዀ%J`_𞹤"), String(NixString(Smol(":`'ຣ𐅔':𑴯\"R&r2h5\\�\\ਲ�<\u{11c3c}¥{Ⴭ!\":𝕆<*"))), Path("`%�Ⱥ࠽¥3Ⱥ?r&�🕴n<𐭰ȺȺᛞ$Ⱥ\u{a41}$ﭱප%\u{1b6b}.𖿰🛱?d"), String(NixString(Smol("ම𑌰d𞹺B𞹩&𑣄$ꬃO(ಝ{�/¥"))), Null, Path(""), Null, Integer(-5596806430051718833), String(NixString(Heap("Ѩ.<\\?𑴠¥<ப=~湮𑤉`v\\Hf\u{ac5}Lᾑ&.𞥟🕴5A\\'¥"))), Integer(-4563843222966965028), Integer(-1260016740228553697), Path("𑼊W𐄡ຄ<u\u{11357}e"), Float(-1.4738886746660203e-287), Float(-2.1710863308744702e271), Integer(-4463138123798208283), Null, Integer(7334938770111854006), String(NixString(Smol("&<\\𖺎CKཛ="))), Null, Float(3.6654773986616826e238), Path("¥\"=ᝯ𐢭$ஏ{.𐢫8ujx"), Bool(true), Path("𐩼ᨅ�\\ம}oѨ𐖌Y$�/z𑇧/`%¼𖭘𑃦:ᥲ$-"), Integer(4610198342416185998), Integer(-8760902751118060791), Path("Hí{𖬩~\u{733}{𝒹\':𞅀ݼ:𑣘Aஜp𑦧𞁦K=Z*"), String(NixString(Smol("\\\"\\O=𝆩𐝋0🕴\">.🟇/𝔇`¥ⷒ"))), String(NixString(Smol(".𝼩𑵢"))), Path("&cἾV&🈫WȺ2{:Uஔi𢢨�$\\Ѩⷉ<+𞥑︾�🢅𝄦"), Bool(false), String(NixString(Smol("?H\"ᝨᛧD🕴e"))), Bool(false), Null, Path("~"), String(NixString(Heap("*<𐄘బu;.𝁮🛵𞸹g\\mF%[LgG.𐭸𐫃*倱🕴`"))), String(NixString(Smol("`ó¥!0ѨW.ଠಏퟞ\\ਫ਼?🫳"))), Integer(5647514456840216227), Null, Bool(true), Bool(false), Integer(-7154144835313791397), Path("\\=🕴�ᣲ*𞹷𛲕cꮈ🫣CȺÏ𑤉נּ/$ਜ.\u{dd6}*%আ`𐄿y꡶"), String(NixString(Smol("`2¥/�ꫤX\"Lᱽ"))), Float(-1.5486826515105273e-100), Bool(false), Path("\\A6𝼥^]<🢖"), Null, Path("G`6𱡎%\u{1e08f}ᳰ"), Float(0.0), Float(-5.1289125333715925e299), Integer(-2181421333849729760), Bool(false), Null, Float(1.8473914799193903e206), Float(-0.0), Integer(-1376655844349042067), Integer(-5430097094598507290)])), NixString(Heap("Ϳ¥%h:?=$🟙p\u{1cf24}*𑴠Ⱥ]Xb")): Path("]l\'*𑇡\u{1e08f}*𝄍&,÷nc෴G¥,🕴𑌏+`?"), NixString(Smol("\u{85b}/")): String(NixString(Smol("%Ⱥ{𑤉pO𑱀$d/ñPF\"="))), NixString(Smol("\u{a51}H𞹾$`𐒡:�¥𐝡{𐺙౾�i${ಇTG��¥{`Òބ^")): Path("<𛁤Eh𑱅"), NixString(Heap("ક\"1ਐȺ")): String(NixString(Smol("𞹉ÕI$𑁔﹫xpnὝ{`RgX.&]ଘ"))), NixString(Smol("ங+ח𐼌ஏ:=R0D\u{afe}ð<%𖭴?/CT%Ⱥo=?𞥔𐴷\"")): Null, NixString(Heap("\u{e4d}/Ð\u{11d3c}m6౨࿒ਫ਼K¥u\\𝐪ୋ")): String(NixString(Smol("𐋴+Z\\𞸻kמּﲿ𐤨tn>ΐ/>3Ѩ<E{𐧆!"))), NixString(Heap("ጒ.ퟴ%\u{1e016}🕴𑨍ἢ=~\\:7𐦜")): Integer(7492187363855822507), NixString(Smol("ⱅ/𞹯=\\ꬆ^ᰢ.F𒿤𒾖ȺlȺÐ")): String(NixString(Smol("𞹗R"))), NixString(Heap("ⶤB2.$\u{10a05}𖫦&*\";y$¸𛲒𑊲U🕴Û\\🕴𞹂E಄,୨")): Float(4.8766603500240926e-73), NixString(Smol("ꬮ\\'\"A\\\\\"R.")): Bool(false), NixString(Heap("漢$%`Ⱥ")): Float(-6.1502027459326004e57), NixString(Heap("ᅵ𛲅</\"\\:�=h𑵥V=\u{c4a}")): Path(""), NixString(Heap("�I𐔋\u{1bc9d}\u{1e029}𛅹𑻨𐊾I¥?ѨѨ:�È\\'𞟾'Ѩ")): Float(4.528490506607037e180), NixString(Heap("𐮪qՎ4𝒦?F𐙍?")): String(NixString(Heap("`🂻𐺭` 𑊚ൽ/ⶻ🛶fȺ(f𐖻Έ{᪐𑌫Z%𑍍ꦘ𐴐&zdৡ𑼩"))), NixString(Heap("𒓸ං#.Ѩ㈞�i")): Path("$/"), NixString(Smol("𖤔𐖔%𖭖\u{1bc9e}")): Float(-2.70849802708656e-257), NixString(Heap("𖾖:y'𐮫dmvਫ਼`*QR𐏐::P=\\B🕴\"c𒓕eᎾΑ")): Path("\\ÔA"), NixString(Heap("𞄐'𖭕:𖽭𐖘\"{Ⱥ.\"⺈??ৎ🁡ଊ𦩽🕴𞣏w.:ࡰ")): Null, NixString(Heap("𞺖Ⱥೇ𥢁⹂Ѩ@ઋ𞹏<Ⱥ6ⴏ𑽗ஶ=L𑍐M.<ꭚ*J\"@~𝁌$\\]")): Float(-2.5300443460528325e91), NixString(Smol("🕴7𪻎𑃦𑤏+ῴᎣ\\ౝ?ட\"\\ꜭ")): Integer(8622149561196801422)}))), Path("I<:🫢:.𑋛ோ\'ⶣ[𑆔%)எM!1<ூ-J>/`$🠁<\\u*ল"), String(NixString(Smol("M|s?ଏ\\"))), Float(-0.0), Integer(6467180586052157790), Bool(false), Bool(true), Float(8.564068787661153e-156), Float(6.773183212257874e294), Integer(4333417029772452811), List(NixList([String(NixString(Smol("/%\u{9d7}𖮎\u{b43}𑰅𝋓ᝠi`\u{a02}aѨ𐢒>ⴭȺ𑌃C፧Â𐭰>G\"ፙ𐍁𐠈&$"))), Integer(2000343086436224127), Integer(3499236969186180442), Integer(4699855887288445431), String(NixString(Heap("ங𐮛𖿣Y𘡌`.𒑳𞋤R7$@`")))])), Bool(false), String(NixString(Heap("*🕴,/𐀬tyk𒑰\u{f90}"))), Integer(5929691397747217334), String(NixString(Smol(".=𝒢.Eⶇ⁃੮\u{fe04}𛅕C៰🢝Ὑ`{.g¥¥"))), Path("\\*J(\'%\u{1a68}k\':ⷋ?/%&"), Bool(false), Float(3.7904416693932316e-70), String(NixString(Heap("/Ⱥc$𐠼<�⾹ഉ "))), Integer(3823980300672166035), Null, Null, Bool(true), String(NixString(Heap("$�𐖔aᅨවw-=$🕴$𞹟xѨb🫂,mຄ"))), Float(-5.5969604383718855e-279), Path("Ἓቇ !\'𐍈𑙥&ಐz"), Bool(true), Integer(429169896063360948), Float(8.239424415661606e-193), Path(""), Attrs(NixAttrs(KV { name: Null, value: Float(-3.5244218644363005e64) })), Float(2.1261149106688998e-250), Float(2322171.9185311636), Integer(5934552133431813912), Integer(5774025761810842546), Float(7.97420158066399e225), Integer(4350620466621982631), Attrs(NixAttrs(Empty)), Integer(-6698369106426730093), Bool(false), Null, Null, Float(-5.41368837946135e190), Null, Path("\u{1112b}`¥𐀇=h𛅕`/?qG%GȺ\u{cd5}𝔼.𞊠\'\'."), Null, Bool(true), Float(-1.0226054851755721e-231), String(NixString(Heap("\"$ල%𐴴*s\"D:ᘯᜩ9𑌗"))), Integer(-713882901472215672), Path("/{𝇘𑒥*ﬧH`ਸ਼í$𞲏ῄZ`🫳𓊯vg]YசȺS𞹢𑼱ó3")])) }
+cc b0bf56ae751ef47cd6a2fc751b278f1246d61497fbf3f7235fe586d830df8ebd # shrinks to input = _TransitiveArgs { x: Attrs(NixAttrs(Im({NixString(Heap("")): Bool(false), NixString(Heap("\"𑃷{+🕴𐹷𐌉𞥞'🯃ⶢvPw")): Bool(false), NixString(Heap("#z1j B\u{9d7}BUQÉ\"𞹎%-𑵣Შ")): Path("𐆠az𐮯\u{c56}�ㄘ&𞄕ନz[zdஐ�%𐕛*ȺDዻ{𞊭꠱:."), NixString(Smol("&?")): Float(-1.47353330827237e-166), NixString(Smol("&🡹B$ѨK-/<4JvѨȺn?\u{11369}𐠈D-%େ/=ਹ𛂔")): Float(-1.2013756386823606e-129), NixString(Heap("'")): Float(-0.0), NixString(Heap(".`%+ᩣ\"HÎ&\"𐊸A%ἚO🅂<𞺦¥ைEAh.⥘?𑩦")): Integer(-5195668090573806811), NixString(Heap("4l\"𖫭¥r&𐡈{\u{11c9b}&磌લ𐀷𑊣Â\"'𓄋{`?¬𝔔")): Attrs(NixAttrs(Im({NixString(Heap("\"!`=Wj㆐ᬢ\\è\u{1183a}T\u{11046}ꬕ&ȺȺ")): Float(-8.138682035627315e228), NixString(Smol("$E𑱼ஃ:\"ெB🮮.�🡾🕴:𑍇𛲜")): String(NixString(Smol("Qf1𝜻A\\L'?U"))), NixString(Heap("$kส𐀽ச𐠸<`\u{f37}5ቘ\u{cc6}G\"1ລÕ𑙓ἡמּt/𖭒𝓨₃ÑѨ𑤨a𖿰ѨA")): Integer(-8744311468850207194), NixString(Smol("%'?Xl(ࡻ.C+T𝒿𘴈L-𑌳\\\u{1cd1}bK>SA")): Path("\\🕴એѨ𐹼-﹔Ð𘴂:?{\u{1e02a}¥\u{c46}#𚿾?K"), NixString(Heap("%e🕴v𐢕&:שׂத")): String(NixString(Heap(""))), NixString(Smol("%¥\u{1d167} |M")): String(NixString(Heap("𒌶$O🁹/𑑝ò"))), NixString(Heap("&=?\u{ec9}Ⱥuい𑜿Ѩ/�𝼦\"$𑤷T{?ⶾ,𑋲9Ⱥ�")): Null, NixString(Smol("&𑌖𐎀?`F𑍍g¥`\"Ⱥ𖿢ລ𝼦L{\u{b01}ѨO*.&K%🫳\"🕴f𑆓¥/")): Null, NixString(Smol("'P𖮆𐽀𐓲𝔊SⶄᰎE\\kF𑦣`�ఎG&/K*")): Path("ᠦlѨꬮ𞸷:\u{1344f}ዀME.&\u{aff}𐧣ᡋb𐦟ቘᄨ"), NixString(Heap("'౻🕴_𐠷𑍡!')?&🕴.\\{{𐾵�*>sj\u{9e3}న`Ѩ¤")): Bool(true), NixString(Smol("+ས&1:᳇P%{r?Ѩ/d`𐠷\u{1ac5}>'🢧")): Bool(true), NixString(Smol("3\\Ѩ龜=&පౝ𑈋🢱\"/<.&🕴/ૠᥬ ̄|&Â$'")): Bool(false), NixString(Heap("6|ዅ`ժy𐠃*")): Bool(false), NixString(Smol("8=᠊<ඩTRળ(Q𑝆=෨\\?Ϳ{>n&")): Null, NixString(Heap(":6zꬑ\\फ़\u{1cf15}װ𝼦🛩Lv<?\u{10a3a}*𑌂wೝ𝓃u%C.R%$෪l")): Bool(true), NixString(Smol("<V🛷VȺ𐩈𓈤ౝ&ₔѨ")): Integer(-2521102349508766000), NixString(Heap("=/e�S=d𐖪\\bᘔ8$Z'")): Float(1.7754451890487876e-308), NixString(Smol("D𑊓Ꮅ%")): String(NixString(Heap("𑌏ë;ල"))), NixString(Heap("E</")): Float(1.74088780910557e-309), NixString(Heap("Fༀ.൷𒀖>¹𐍮Ⱥ$M\\\u{10a3f}ಎ𒿮Ⴭ5ዓ6{🕴:")): Null, NixString(Heap("GCક\\¥{4🕴?ࠏ🕴=🕴𖭝'?𐝥%Ⱥ\u{10f48}�kኲ:%¥")): Path("𝉅w𞹟`ῼ`Ѩh/\u{11d3d}\u{65e}j/🉥\\&𛅥אּѨ𑧒\"Ѩkໄ\\a~𑚩-(:"), NixString(Smol("K|*`\u{9e2}Ѩ𖠉%𑦺=u</ৌ⮕ꬋ\u{a01}*6𐩒P᰿𞅎'🉁")): Bool(false), NixString(Smol("MSᅳ")): Integer(-8585013260819116073), NixString(Smol("P&ꩁ࡞🩠&𑈳:⮲.ز")): String(NixString(Heap("G]Ѩ{D"))), NixString(Heap("R𝒞j:𝄁{𑍝ﹰ\u{309a}5Ѩyべk🟰𑊈𱁿:𑑡'🉥ⷊ{ౠ<=&:ᦗ*_K")): Float(1.215573811363203e25), NixString(Heap("Xoy_Z🕴𞅈ⷈ⁵\"ݭ)<𑽎]𒒴")): Float(-2.0196288842285875e215), NixString(Heap("[y𞹨/")): Path("(p\'/.Ⱥࡹ?🄎ቌm�>>%z~{`%4ѨȺ𑫎"), NixString(Smol("[𝒻W𑬀\")a𛄲𑰄&ⶹ.\":𖮂")): Path("\u{a51}<:ꫧ㈘🕴𑒻gȺ𑌃D🮦𫝓"), NixString(Heap("^?𔕃{\"ꢭ𝍥{💿o𝼨\u{b01}Ѩõ")): Bool(true), NixString(Smol("`.\u{1e005}&Ⱥ🕴=%𑩦৩\u{a3c}{ⷜ:F�h'\":Ὓ࠸")): Integer(958752561685496671), NixString(Smol("`𑤓(b𝔵3=𞁕<\u{f93}𐇞𖭶$🕴¥.:&?=oఋN\u{9c3}")): Float(-1.2016436878109123e-90), NixString(Heap("`🂣`𐤢.üI¥::.$)𐨵/\\𒑚ZὍ𖹕e𝒟具Yຈ|🀙࡞")): Path("/ᅵȺ𝒽Ⴭ<🉀u)🕴מּ/ந"), NixString(Smol("d®댓7M𑍐_‽sxⶋ𖩈Xⶭ]ⷓ?`o𞥟\"@,ㄛ𞲔ೠ🕴j#y{'")): Path("ቝg𑅇ዀ🕴𐌼�t=:.|*]�🕴,Ѩ*ᝡㄧ¥nȺ"), NixString(Heap("eቊഎȺﹰ¾0𐣰/அ🠄r~=𐞌_ఽ")): Integer(-4346221131161118847), NixString(Smol("e𐠪ቘ<$=Q\u{ecc}𚿷*}ଐ$/𐂢Y?y\u{11d3d}fଐ𐋹\u{20ed}H\\�û'g<L{")): Integer(1302899345904266282), NixString(Heap("fJ🉀!6$F𑰤𐎁C&ౚ𞓤T\"\u{d81}«Տ𘴃.𑰍Â﬩*")): Float(-9.550597053049143e239), NixString(Heap("j﮼\"🕴$%'Gૌ3?\u{302d}¥")): Bool(false), NixString(Heap("u~Ⱥ¥𝣤YN𐂜 ¥' `r𐿄/\u{b57}")): Integer(3809742241627978303), NixString(Smol("z%c流ü🕴$`.F*`Ѩଏ<\"𣅅'<3p=r:Y\u{1a60}ড:/]𐾁")): String(NixString(Smol("\\*E■{:🕴"))), NixString(Heap("{<B9-Y𑣊N61ѨH.¡\\ꮬ'Ãkô")): Path("=𑥘^*¥K𚿾સ*Ðᝰf[:P𐤅*<⻡<\"8"), NixString(Smol("{ຄ±𑼉Ѩ`ⶮ𚿺Í%𞹡`!/")): Integer(-7455997128197210401), NixString(Smol("|🕴ේ0\u{1e131}🡑=ਜ਼_`𝔗.ો1ㄈ𐹣\u{1773}ꭆⵯ\u{1c2e}ࠨ𐧔੫=\u{b42}1\u{bd7}�/�¥$")): Null, NixString(Heap("¥⵰ᅬ<*'Z¥\"Ὓ$ᥖᅭ𚿱\"ꥳÒ𖿱𞓕Ѩ9MÌ¥u{ୈ<﹨")): Float(6.4374351060383694e243), NixString(Smol("¥🕴=r`z&.<V<.Ⱥ๏\"")): Bool(true), NixString(Smol("Ê9𝐐/JkᡶUl\"🕴{i")): Path("z/¥&Wzꡭ`?Ѩ\\🟰þ၈$ࠓ"), NixString(Heap("Ù𑥘c\"�\u{8ce}𑤸𝆥೮?~")): Bool(true), NixString(Heap("ãU&𐔐/")): Bool(false), NixString(Heap("ѨѨ?<'?࿎ȺՉⷊ$\u{fe09}ꞙ")): String(NixString(Smol("Ό/`j🕴🕴𐞂🕴\u{ce3}ਲ$𘟛ºװ?𞸮J𝍣Q\"}🕴q𐧸ࢺ\u{a48}"))), NixString(Heap("ࡩ꯳%^ౝ")): String(NixString(Smol("{%"))), NixString(Heap("\u{b57}🪃ⷚȺMC?¥\u{11c9a}+:*<B𐖰y~/.1&$𐺭hর\"\"")): Path("ቕ𝌫=(|R{ei\u{1a5b}Z!2\u{c3c}+<rí:\"f"), NixString(Heap("ங¸4ᨔ\u{17cb}ਜ'ಣ¥z&=='c𜽫ࡠﹳ꠲𐨖এ<¥🕴𑙫𐖷")): Integer(-2404377207820357643), NixString(Heap("ቘ🕴{ȺL'.")): Null, NixString(Smol("ኸ\"𖦆d\\&𐞷C🠀k1\u{eb1}KV%🪿\u{1cf35}>:3𐡾P.\"Ѩ.ໆኍ*'")): Bool(true), NixString(Heap("ኸ8")): String(NixString(Smol("�?Ia🀛*\u{10d27}U¥𐖙9ીㇺIW:%&G+?"))), NixString(Heap("ᱰῙ:ஙᤄ.:*𐺰𝄿勉Ѩ&¥𐧾7&`AຂѨ𑗒&¥𛅕/🕴Ѩ")): Float(-0.0), NixString(Heap("ꝏs𞊡<g=𝕃ᅭRȺ�u𛱜")): Bool(false), NixString(Heap("ꡩ$𰻍🯷@a\\")): Null, NixString(Smol("𐭻𞸧Ã\\Ⱥ𞸢𘌧%h+|@ꖦ*\"~𑄣")): Bool(false), NixString(Smol("𑐑𞟣Ѩ<''¥Ѩ<ອ᪦\"፵𛄲𞀾ឮo")): Float(1.540295382708916e-173), NixString(Heap("\u{1145e}p\u{ac1}e\u{10a05}𝼩#ந*/?🡗\u{c4d}ͼᲿꟑ")): Integer(-4194082747744625061), NixString(Smol("𑘏𞟨:Ѩ?^ c🃊tๆ.Ⱥ9 𔑳&៵ׯ🬔\u{11f01}𬢪*ﷲȺ`𑼆n")): Path("7�$Ì\u{1e00c}海2&𐝀<"), NixString(Smol("𑚆")): String(NixString(Smol("𝒢\u{11727};க\u{c56}P'𝄎🪬*ⷍ\u{1e08f}59/𑨙𐤿ොZ$Jෳ*𞺓"))), NixString(Heap("𑴋¥#=%")): Integer(1639298533614063138), NixString(Heap("\u{11d3d}𑊌o:}=ਫ਼$໓ᅨѨ𑌅6🫄ᅮ🛴")): Integer(4745566200697725742), NixString(Heap("\u{16af4}𞲡🡕:ೈ𝒩$𝄀\u{c3e}ࡨ")): Float(2.8652739787522095e-21), NixString(Heap("𖽩$,®'w5ޤ*𖿣H'6¥ੀ")): Path("+ꡕ:/f"), NixString(Heap("𞋿@%\u{20d6}÷MqȺÏ🪧𞟣&𝼆Ⱥn2?ᦽ")): Path("n𞺣Ꮝ*𐤤FU.T"), NixString(Smol("🕴/Ⱥᜄ&{.\u{e0109}]𐣨|ຆ᱓𐃯🂥𐺭𑤬@$r{Ⱥ‿🕴〧\u{c3e}'X")): Null, NixString(Smol("🕴<V𖬑u\u{bd7}ໆC`xῴ𐄂f.M^ਐ𑼂;%.𐠈¥໔ꘄዀѨઑ")): Null, NixString(Heap("🕴¥\u{10a38}/{\\-?🕴'Ýὓ𛱱`XuᾺ𐊎ࠅ|𞹒𐆗𑌲")): Bool(true)}))), NixString(Heap("9;G'.%𑴆")): String(NixString(Heap("🕴8?$=𞺒%V*\u{9bc}𑊄ଡ଼JbѨ=ળ=𑛉\u{a81}É;ᅤᲿN=ਫ਼:�`𐁙"))), NixString(Smol(":Ѩ\\Lꝅ5𐤀'uꦙ~*肋J")): List(NixList([String(NixString(Smol(".࡞\\P?*<<\"𐖄9&𘚯%Ѩю1G\\౭𒑃&𞹛=9:𞥐@("))), Path("<𝒗ঽ¥<\u{fa7}ୌD={=ଡ଼\"Ⱥ\u{bd7}ઐ$\'�¥𞹼E𐦙🡓t`\u{11357}"), Float(-3.099020840259708e-255), Path("𞟫᪓h𖭁")])), NixString(Heap("<:^X𞹇𞹷\\\u{f7e}\u{1713}^ⴝQ`.|G𝂜𞹍𑐬𐮅{x𑍇סּ\"`🉤𝚚𰳳Ⱥ")): Integer(6011461020988750685), NixString(Smol("<{פּ%#¥🕴d:�,y")): Float(-2.901654271651391e201), NixString(Heap("=/I\"🮧{z&a�<?`L¥𞋿``.|")): String(NixString(Smol("=𖫉m\"ȺhC_"))), NixString(Smol("=L=X'aਿ9.")): Path("<&]Mভ"), NixString(Smol("Eנּ:b$?JὙs𝒢O?ꥠѨ{\\ਸ𝕎𒾮`𐀢")): Float(3.6870021161947895e-16), NixString(Heap("\\�' ìrᩆ#ລB¥𐖻l%𛅥D3{/.4O¥\\.{𐼓^𒾬")): Path("/íR›d🕴-K\u{ac8}"), NixString(Heap("\\\u{1e028}೫I/ͽ4")): Float(-8.628193252502098e81), NixString(Heap("a𐠈/K33/&𝐵¥𐼰.y/🫢\\𐧨𞟹")): String(NixString(Heap("𞹡W<᱂a=ᡞಇ�%&𑴈ࢳ¥𑊈"))), NixString(Smol("eD�¥𐌋x<`B𞣍H\\`èZ𐐅៥{\\?T*")): Bool(false), NixString(Heap("oP𝒬(ѨJਈ𛀺Ѩ\"?T=O𛄲s�🕴LK🝈ከⶩ+\u{1344f}hu?E")): Bool(false), NixString(Heap("vNꟓȺM]")): Float(-2.9637346989743125e232), NixString(Smol("{!¥\\\u{a47}\u{10a38}.E4Ѩ;=R\u{a48}<=/\\&స𐢩N'?.9𑗊\u{7ec}ਸ਼:𐫃")): Null, NixString(Smol("~+!$M.𑐯ౚ\\>.¥𐖥<Q'໙0.kQ{𞹾༺𑼉sEὕ")): Float(9.45765137266644e-294), NixString(Smol("¥;Z$*&+�&\"🉆`ኲ")): Null, NixString(Heap("ãF&<` \u{64c}-:Ѩ*�")): Integer(-7654340132753689736), NixString(Heap("Ⱥ`\u{a81}Ⱥ𞺥\u{11727}<m\u{a51}{$`\u{11d3d}શR/E")): Bool(true), NixString(Heap("Ѩ%gὋ 4𑶠𑤕Y{Q<")): String(NixString(Heap("𛃛Ꟑꩀ𑊈RoR<ৌ:4`ⶥ(ೳ>8*𑌟/𛅧<}&꒩᠙\""))), NixString(Heap("ѨನQ𑠃%=i𘓦ශ\u{1939}𞟭7;ﺸ𐼗ꟘW")): Bool(true), NixString(Smol("ਬͿ𝒩./🪡Ѩ𝁾ໆ+ఫJ{𑰏*\u{cd6}ΐѨ'APȺ*")): Path("Q%︹֏Ὓ<$Ꟗ$ᦣ𐖻Ѩ\u{c3c}MѨ"), NixString(Heap("ଢ଼")): List(NixList([Path("?&�=\u{fe0a}z\"વ🟰}ৱᦥ𐕅\"𞹭MⷝDJຄ"), String(NixString(Smol("𐺱`𞱸Ⱥ🜛?ୈ&Ꞣ=j¥`൷@ਐෂ't=7)ꬖన2Dꪩl𐝣ጓ=W"))), Path("`ಲ=𑘀eલ7𖽧Ù&ᜉ:.ﺴ𐳂\'𞹎n🕴𓊚>𝓓ை:\'{`o"), Null, Bool(true), Float(-1.424807473222878e-261), String(NixString(Heap("𑤅w𑌏ኸ¡ਊ/𞺩Ⱥ\\𛅤𞢊?\""))), Null, Bool(true), Float(-4.714901519361897e-299), Null, Integer(2676153683650725840), Null, Integer(3879649205909941200), Bool(false), Integer(-7874695792262285476), String(NixString(Heap("Wᥳ⑁*\\%ᜦ.⮏꫞Q𞹔L|ௌѨdU=*Ѩ\u{20dc}"))), Null, String(NixString(Smol("U𑐎𖺗$𞹡𞅏𝔯﮴<)&𑌫\\{\u{1acd}wѨ!𞸤𖭓º"))), Integer(802198132362652319), Path(":\u{b4d}Fᝊ:w:꯵"), Integer(-8241314039419932440), Null, Bool(true), Float(-0.0), Float(0.0), Integer(-3815417798906879402), Path("\"Dෆૌ෪\u{b01}ૐ%M№"), Null, Path("ab`𐒨\\{÷௶𖮌$=ë"), Bool(true), Null, Float(2.607265033718189e-240), String(NixString(Smol("𐖤𑤷=6$`:0ѨE\"Ѩ\"𗾮ኴ}.𞠸𞺢ߔ𑵡"))), Integer(340535348291582986), String(NixString(Heap("᠑%*&t𐮮':\\\"ꩂລ'\"𞟳|J\\\\V𑧈𛇝췭𐮬$\u{eb4}"))), Null, Float(-0.0), Float(2.533509218227985e-50), Integer(2424692299527350019), Integer(8550372276678005182), Integer(2463774675297034756), Float(-1.5273858905127126e203), String(NixString(Smol("𐕐𐐀ùI"))), String(NixString(Heap("?:🕴𑴞ﹲѨË𐫬Zf{𝋍{{࿀&\"Ô:<CJN?"))), Integer(-916756719790576181), Float(5.300552697992164e116), Path("𞹝�.?𝌼uଢ଼%~"), Bool(true), Float(-4.423451855615858e107), String(NixString(Smol("Ώﮱ¥8gH^ᛨꟖjR𐢮\"S𖩑𞸻🪁"))), Integer(8503745651802746605), Integer(8360793923494146338), Bool(false), Path("𝄞3¨/𑢸mº~𑍋ha𐫮$⹎\u{dd6}*ᲬѨク?𝔔¿"), Null, Float(-1.116670032902463e-188)])), NixString(Smol("ഌ⾞V𑄾7𑚅D𞅎$\"")): String(NixString(Heap("¤<ꡀὝ¥𞸤𞸇𑴄�ⷍa𑁟O\"f&𘴇`*<z𑨧ᤗ"))), NixString(Smol("ዄS𐾆ꟓ\")ኛ7G<oѨ🕴?𛄲×𑿕ᩣb。𖩒w")): Float(-2.1782297015402654e-308), NixString(Heap("ᩌਲ਼Ⱥﹲ<'=ւລמּ�p")): Path("𐀽𐠄"), NixString(Smol("\u{1a7b}$༄XୈÂ`7\"$*=࡞៣𐀸.tw")): String(NixString(Smol("ﬖ<j%𑊊kૐmȺrᎇ𒄹ëল.y*ᤫCョB½"))), NixString(Smol("Ὸ:𝍷🢰8g>\u{fe0d}RѨꥫ^ῷ&ຈ/'𑌅gው=s𝂀'�𑛂𞴍~𑅮?𝑪")): Float(1.6480784640549027e-202), NixString(Smol("ⴭ8🕴V𞹙2𑶦ං=")): String(NixString(Heap("ਓ𞹗Ѩ𖭨𝓼¥..ᾶ`oU{ₖB\\©:/ѨI𝆍.&🕴:ම𞹉fi*v"))), NixString(Smol("יּN\"@𐦽Ë`𑇑ȺC{¥b$ಭೱp$hS[좵&")): Float(0.0), NixString(Smol("ﺵ🟰\"nj𞺡o&*Oz{Ⱥ\"/\\፨\u{8dd}^v´𞹺`")): Path("𑇞.🕂d"), NixString(Smol("�𞸹מּz?_R'ᥲ<<¥/*﹛Ⱥ�R𑗓𑙥𑜄ೱa'𐀞7")): Float(1.6499558125107454e233), NixString(Heap("𐏊എ🟩ﬔV?Ѩ𝔢𖭡\u{1bc9d}𝌅𑤸ڽ𞹺V.𝕊B⸛*")): Null, NixString(Heap("𐞢u1%<🕴â}𛱷$٭b𛲟ຜv着\"𝕃\\$Ⱥ<ºףּ%ଓ")): Null, NixString(Heap("𐞶𐏃*$🂭eස\u{dd6};𞁝'ᩯ")): String(NixString(Heap("'&ⷘᰎ\"𐠸𑈪"))), NixString(Heap("\u{10a05}/")): String(NixString(Heap("ㄴኲY¥'𑛅Ῥ\"𔕑𐖕נּ🇽$l.=$🠤𛰜-𝒢R{$'$^"))), NixString(Heap("\u{11300}🃆+$�ₜj\"ჇA*r&𑥐𑊙W\u{10a0d}yR<\\Ⱥ׳𞄽C&?")): Integer(6886651566886381061), NixString(Heap("𑍌🕴`'\\G¥<ꬤⶮM%𑵠꒦ૉS\u{9d7}Ⴭඹ8🕴*[Ö")): Float(2.2707656538220278e250), NixString(Smol("𘴀'>$¦C/`M*𞻰¢꩙᠖�*🕴:&è")): String(NixString(Smol("'<ড়ᰦ"))), NixString(Smol("𞊭")): Attrs(NixAttrs(Empty)), NixString(Heap("𞥖🢙=ཏ=�%𛲁$zi¥&꧗𫝒૱G𐧠𝓾$/𐼍𫟨Yhⷕ")): String(NixString(Smol("Us:)?𘘼"))), NixString(Smol("𞹪*𑌳`f𝐋ଐ𝕆j")): String(NixString(Heap("𖭿5^Zq𐨗𑄿𑥄ߤ𑜱2ঐ"))), NixString(Smol("𱾼\u{1e4ef}õ<\u{a0}{ö\\$�ῷq")): Null}))), y: Attrs(NixAttrs(KV { name: String(NixString(Smol("�𛲑ો\\?É'{<?W2𫳫p%"))), value: Integer(134481456438872098) })), z: Attrs(NixAttrs(Empty)) }
diff --git a/tvix/eval/src/builtins/hash.rs b/tvix/eval/src/builtins/hash.rs
new file mode 100644
index 0000000000..d0145f1e7d
--- /dev/null
+++ b/tvix/eval/src/builtins/hash.rs
@@ -0,0 +1,29 @@
+use bstr::ByteSlice;
+use data_encoding::HEXLOWER;
+use md5::Md5;
+use sha1::Sha1;
+use sha2::{digest::Output, Digest, Sha256, Sha512};
+
+use crate::ErrorKind;
+
+/// Reads through all data from the passed reader, and returns the resulting [Digest].
+/// The exact hash function used is left generic over all [Digest].
+fn hash<D: Digest + std::io::Write>(mut r: impl std::io::Read) -> Result<Output<D>, ErrorKind> {
+    let mut hasher = D::new();
+    std::io::copy(&mut r, &mut hasher)?;
+    Ok(hasher.finalize())
+}
+
+/// For a given algo "string" and reader for data, calculate the digest
+/// and return it as a hexlower encoded [String].
+pub fn hash_nix_string(algo: impl AsRef<[u8]>, s: impl std::io::Read) -> Result<String, ErrorKind> {
+    match algo.as_ref() {
+        b"md5" => Ok(HEXLOWER.encode(hash::<Md5>(s)?.as_bstr())),
+        b"sha1" => Ok(HEXLOWER.encode(hash::<Sha1>(s)?.as_bstr())),
+        b"sha256" => Ok(HEXLOWER.encode(hash::<Sha256>(s)?.as_bstr())),
+        b"sha512" => Ok(HEXLOWER.encode(hash::<Sha512>(s)?.as_bstr())),
+        _ => Err(ErrorKind::UnknownHashType(
+            algo.as_ref().as_bstr().to_string(),
+        )),
+    }
+}
diff --git a/tvix/eval/src/builtins/impure.rs b/tvix/eval/src/builtins/impure.rs
new file mode 100644
index 0000000000..c82b910f5f
--- /dev/null
+++ b/tvix/eval/src/builtins/impure.rs
@@ -0,0 +1,110 @@
+use builtin_macros::builtins;
+use genawaiter::rc::Gen;
+
+use std::{
+    env,
+    time::{SystemTime, UNIX_EPOCH},
+};
+
+use crate::{
+    self as tvix_eval,
+    errors::ErrorKind,
+    io::FileType,
+    value::NixAttrs,
+    vm::generators::{self, GenCo},
+    NixString, Value,
+};
+
+#[builtins]
+mod impure_builtins {
+    use std::ffi::OsStr;
+    use std::os::unix::ffi::OsStrExt;
+
+    use super::*;
+    use crate::builtins::{coerce_value_to_path, hash::hash_nix_string};
+
+    #[builtin("getEnv")]
+    async fn builtin_get_env(co: GenCo, var: Value) -> Result<Value, ErrorKind> {
+        Ok(env::var(OsStr::from_bytes(&var.to_str()?))
+            .unwrap_or_else(|_| "".into())
+            .into())
+    }
+
+    #[builtin("hashFile")]
+    async fn builtin_hash_file(co: GenCo, algo: Value, path: Value) -> Result<Value, ErrorKind> {
+        let path = match coerce_value_to_path(&co, path).await? {
+            Err(cek) => return Ok(Value::from(cek)),
+            Ok(p) => p,
+        };
+        let r = generators::request_open_file(&co, path).await;
+        hash_nix_string(algo.to_str()?, r).map(Value::from)
+    }
+
+    #[builtin("pathExists")]
+    async fn builtin_path_exists(co: GenCo, path: Value) -> Result<Value, ErrorKind> {
+        match coerce_value_to_path(&co, path).await? {
+            Err(cek) => Ok(Value::from(cek)),
+            Ok(path) => Ok(generators::request_path_exists(&co, path).await),
+        }
+    }
+
+    #[builtin("readDir")]
+    async fn builtin_read_dir(co: GenCo, path: Value) -> Result<Value, ErrorKind> {
+        match coerce_value_to_path(&co, path).await? {
+            Err(cek) => Ok(Value::from(cek)),
+            Ok(path) => {
+                let dir = generators::request_read_dir(&co, path).await;
+                let res = dir.into_iter().map(|(name, ftype)| {
+                    (
+                        // TODO: propagate Vec<u8> or bytes::Bytes into NixString.
+                        NixString::from(
+                            String::from_utf8(name.to_vec()).expect("parsing file name as string"),
+                        ),
+                        Value::from(match ftype {
+                            FileType::Directory => "directory",
+                            FileType::Regular => "regular",
+                            FileType::Symlink => "symlink",
+                            FileType::Unknown => "unknown",
+                        }),
+                    )
+                });
+
+                Ok(Value::attrs(NixAttrs::from_iter(res)))
+            }
+        }
+    }
+
+    #[builtin("readFile")]
+    async fn builtin_read_file(co: GenCo, path: Value) -> Result<Value, ErrorKind> {
+        match coerce_value_to_path(&co, path).await? {
+            Err(cek) => Ok(Value::from(cek)),
+            Ok(path) => {
+                let mut buf = Vec::new();
+                generators::request_open_file(&co, path)
+                    .await
+                    .read_to_end(&mut buf)?;
+                Ok(Value::from(buf))
+            }
+        }
+    }
+}
+
+/// Return all impure builtins, that is all builtins which may perform I/O
+/// outside of the VM and so cannot be used in all contexts (e.g. WASM).
+pub fn impure_builtins() -> Vec<(&'static str, Value)> {
+    let mut result = impure_builtins::builtins();
+
+    // currentTime pins the time at which evaluation was started
+    {
+        let seconds = match SystemTime::now().duration_since(UNIX_EPOCH) {
+            Ok(dur) => dur.as_secs() as i64,
+
+            // This case is hit if the system time is *before* epoch.
+            Err(err) => -(err.duration().as_secs() as i64),
+        };
+
+        result.push(("currentTime", Value::Integer(seconds)));
+    }
+
+    result
+}
diff --git a/tvix/eval/src/builtins/mod.rs b/tvix/eval/src/builtins/mod.rs
new file mode 100644
index 0000000000..04a0b3dd33
--- /dev/null
+++ b/tvix/eval/src/builtins/mod.rs
@@ -0,0 +1,1728 @@
+//! This module implements the builtins exposed in the Nix language.
+//!
+//! See //tvix/eval/docs/builtins.md for a some context on the
+//! available builtins in Nix.
+
+use bstr::{ByteSlice, ByteVec};
+use builtin_macros::builtins;
+use genawaiter::rc::Gen;
+use imbl::OrdMap;
+use regex::Regex;
+use std::cmp::{self, Ordering};
+use std::collections::VecDeque;
+use std::collections::{BTreeMap, HashSet};
+use std::path::PathBuf;
+
+use crate::arithmetic_op;
+use crate::value::PointerEquality;
+use crate::vm::generators::{self, GenCo};
+use crate::warnings::WarningKind;
+use crate::{
+    self as tvix_eval,
+    builtins::hash::hash_nix_string,
+    errors::{CatchableErrorKind, ErrorKind},
+    value::{CoercionKind, NixAttrs, NixList, NixString, Thunk, Value},
+};
+
+use self::versions::{VersionPart, VersionPartsIter};
+
+mod hash;
+mod to_xml;
+mod versions;
+
+#[cfg(test)]
+pub use to_xml::value_to_xml;
+
+#[cfg(feature = "impure")]
+mod impure;
+
+#[cfg(feature = "impure")]
+pub use impure::impure_builtins;
+
+// we set TVIX_CURRENT_SYSTEM in build.rs
+pub const CURRENT_PLATFORM: &str = env!("TVIX_CURRENT_SYSTEM");
+
+/// Coerce a Nix Value to a plain path, e.g. in order to access the
+/// file it points to via either `builtins.toPath` or an impure
+/// builtin. This coercion can _never_ be performed in a Nix program
+/// without using builtins (i.e. the trick `path: /. + path` to
+/// convert from a string to a path wouldn't hit this code).
+///
+/// This operation doesn't import a Nix path value into the store.
+pub async fn coerce_value_to_path(
+    co: &GenCo,
+    v: Value,
+) -> Result<Result<PathBuf, CatchableErrorKind>, ErrorKind> {
+    let value = generators::request_force(co, v).await;
+    if let Value::Path(p) = value {
+        return Ok(Ok(*p));
+    }
+
+    match generators::request_string_coerce(
+        co,
+        value,
+        CoercionKind {
+            strong: false,
+            import_paths: false,
+        },
+    )
+    .await
+    {
+        Ok(vs) => {
+            let path = vs.to_path()?.to_owned();
+            if path.is_absolute() {
+                Ok(Ok(path))
+            } else {
+                Err(ErrorKind::NotAnAbsolutePath(path))
+            }
+        }
+        Err(cek) => Ok(Err(cek)),
+    }
+}
+
+#[builtins]
+mod pure_builtins {
+    use std::ffi::OsString;
+
+    use bstr::{BString, ByteSlice, B};
+    use imbl::Vector;
+    use itertools::Itertools;
+    use os_str_bytes::OsStringBytes;
+
+    use crate::{value::PointerEquality, AddContext, NixContext, NixContextElement};
+
+    use super::*;
+
+    macro_rules! try_value {
+        ($value:expr) => {{
+            let val = $value;
+            if val.is_catchable() {
+                return Ok(val);
+            }
+            val
+        }};
+    }
+
+    #[builtin("abort")]
+    async fn builtin_abort(co: GenCo, message: Value) -> Result<Value, ErrorKind> {
+        // TODO(sterni): coerces to string
+        // Although `abort` does not make use of any context,
+        // we must still accept contextful strings as parameters.
+        // If `to_str` was used, this would err out with an unexpected type error.
+        // Therefore, we explicitly accept contextful strings and ignore their contexts.
+        Err(ErrorKind::Abort(message.to_contextful_str()?.to_string()))
+    }
+
+    #[builtin("add")]
+    async fn builtin_add(co: GenCo, x: Value, y: Value) -> Result<Value, ErrorKind> {
+        arithmetic_op!(&x, &y, +)
+    }
+
+    #[builtin("all")]
+    async fn builtin_all(co: GenCo, pred: Value, list: Value) -> Result<Value, ErrorKind> {
+        for value in list.to_list()?.into_iter() {
+            let pred_result = generators::request_call_with(&co, pred.clone(), [value]).await;
+            let pred_result = try_value!(generators::request_force(&co, pred_result).await);
+
+            if !pred_result.as_bool()? {
+                return Ok(Value::Bool(false));
+            }
+        }
+
+        Ok(Value::Bool(true))
+    }
+
+    #[builtin("any")]
+    async fn builtin_any(co: GenCo, pred: Value, list: Value) -> Result<Value, ErrorKind> {
+        for value in list.to_list()?.into_iter() {
+            let pred_result = generators::request_call_with(&co, pred.clone(), [value]).await;
+            let pred_result = try_value!(generators::request_force(&co, pred_result).await);
+
+            if pred_result.as_bool()? {
+                return Ok(Value::Bool(true));
+            }
+        }
+
+        Ok(Value::Bool(false))
+    }
+
+    #[builtin("attrNames")]
+    async fn builtin_attr_names(co: GenCo, set: Value) -> Result<Value, ErrorKind> {
+        let xs = set.to_attrs()?;
+        let mut output = Vec::with_capacity(xs.len());
+
+        for (key, _val) in xs.iter() {
+            output.push(Value::from(key.clone()));
+        }
+
+        Ok(Value::List(NixList::construct(output.len(), output)))
+    }
+
+    #[builtin("attrValues")]
+    async fn builtin_attr_values(co: GenCo, set: Value) -> Result<Value, ErrorKind> {
+        let xs = set.to_attrs()?;
+        let mut output = Vec::with_capacity(xs.len());
+
+        for (_key, val) in xs.iter() {
+            output.push(val.clone());
+        }
+
+        Ok(Value::List(NixList::construct(output.len(), output)))
+    }
+
+    #[builtin("baseNameOf")]
+    async fn builtin_base_name_of(co: GenCo, s: Value) -> Result<Value, ErrorKind> {
+        let span = generators::request_span(&co).await;
+        let s = s
+            .coerce_to_string(
+                co,
+                CoercionKind {
+                    strong: false,
+                    import_paths: false,
+                },
+                span,
+            )
+            .await?
+            .to_contextful_str()?;
+
+        let mut bs = (**s).to_owned();
+        if let Some(last_slash) = bs.rfind_char('/') {
+            bs = bs[(last_slash + 1)..].into();
+        }
+        Ok(NixString::new_inherit_context_from(&s, bs).into())
+    }
+
+    #[builtin("bitAnd")]
+    async fn builtin_bit_and(co: GenCo, x: Value, y: Value) -> Result<Value, ErrorKind> {
+        Ok(Value::Integer(x.as_int()? & y.as_int()?))
+    }
+
+    #[builtin("bitOr")]
+    async fn builtin_bit_or(co: GenCo, x: Value, y: Value) -> Result<Value, ErrorKind> {
+        Ok(Value::Integer(x.as_int()? | y.as_int()?))
+    }
+
+    #[builtin("bitXor")]
+    async fn builtin_bit_xor(co: GenCo, x: Value, y: Value) -> Result<Value, ErrorKind> {
+        Ok(Value::Integer(x.as_int()? ^ y.as_int()?))
+    }
+
+    #[builtin("catAttrs")]
+    async fn builtin_cat_attrs(co: GenCo, key: Value, list: Value) -> Result<Value, ErrorKind> {
+        let key = key.to_str()?;
+        let list = list.to_list()?;
+        let mut output = vec![];
+
+        for item in list.into_iter() {
+            let set = generators::request_force(&co, item).await.to_attrs()?;
+
+            if let Some(value) = set.select(&key) {
+                output.push(value.clone());
+            }
+        }
+
+        Ok(Value::List(NixList::construct(output.len(), output)))
+    }
+
+    #[builtin("ceil")]
+    async fn builtin_ceil(co: GenCo, double: Value) -> Result<Value, ErrorKind> {
+        Ok(Value::Integer(double.as_float()?.ceil() as i64))
+    }
+
+    #[builtin("compareVersions")]
+    async fn builtin_compare_versions(co: GenCo, x: Value, y: Value) -> Result<Value, ErrorKind> {
+        let s1 = x.to_str()?;
+        let s1 = VersionPartsIter::new_for_cmp((&s1).into());
+        let s2 = y.to_str()?;
+        let s2 = VersionPartsIter::new_for_cmp((&s2).into());
+
+        match s1.cmp(s2) {
+            std::cmp::Ordering::Less => Ok(Value::Integer(-1)),
+            std::cmp::Ordering::Equal => Ok(Value::Integer(0)),
+            std::cmp::Ordering::Greater => Ok(Value::Integer(1)),
+        }
+    }
+
+    #[builtin("concatLists")]
+    async fn builtin_concat_lists(co: GenCo, lists: Value) -> Result<Value, ErrorKind> {
+        let mut out = imbl::Vector::new();
+
+        for value in lists.to_list()? {
+            let list = try_value!(generators::request_force(&co, value).await).to_list()?;
+            out.extend(list.into_iter());
+        }
+
+        Ok(Value::List(out.into()))
+    }
+
+    #[builtin("concatMap")]
+    async fn builtin_concat_map(co: GenCo, f: Value, list: Value) -> Result<Value, ErrorKind> {
+        let list = list.to_list()?;
+        let mut res = imbl::Vector::new();
+        for val in list {
+            let out = generators::request_call_with(&co, f.clone(), [val]).await;
+            let out = try_value!(generators::request_force(&co, out).await);
+            res.extend(out.to_list()?);
+        }
+        Ok(Value::List(res.into()))
+    }
+
+    #[builtin("concatStringsSep")]
+    async fn builtin_concat_strings_sep(
+        co: GenCo,
+        separator: Value,
+        list: Value,
+    ) -> Result<Value, ErrorKind> {
+        let mut separator = separator.to_contextful_str()?;
+        let mut context = NixContext::new();
+        if let Some(sep_context) = separator.context_mut() {
+            context = context.join(sep_context);
+        }
+        let list = list.to_list()?;
+        let mut res = BString::default();
+        for (i, val) in list.into_iter().enumerate() {
+            if i != 0 {
+                res.push_str(&separator);
+            }
+            match generators::request_string_coerce(
+                &co,
+                val,
+                CoercionKind {
+                    strong: false,
+                    import_paths: true,
+                },
+            )
+            .await
+            {
+                Ok(mut s) => {
+                    res.push_str(&s);
+                    if let Some(ref mut other_context) = s.context_mut() {
+                        // It is safe to consume the other context here
+                        // because the `list` and `separator` are originally
+                        // moved, here.
+                        // We are not going to use them again
+                        // because the result here is a string.
+                        context = context.join(other_context);
+                    }
+                }
+                Err(c) => return Ok(Value::Catchable(Box::new(c))),
+            }
+        }
+        // FIXME: pass immediately the string res.
+        Ok(NixString::new_context_from(context, res).into())
+    }
+
+    #[builtin("deepSeq")]
+    async fn builtin_deep_seq(co: GenCo, x: Value, y: Value) -> Result<Value, ErrorKind> {
+        generators::request_deep_force(&co, x).await;
+        Ok(y)
+    }
+
+    #[builtin("div")]
+    async fn builtin_div(co: GenCo, x: Value, y: Value) -> Result<Value, ErrorKind> {
+        arithmetic_op!(&x, &y, /)
+    }
+
+    #[builtin("dirOf")]
+    async fn builtin_dir_of(co: GenCo, s: Value) -> Result<Value, ErrorKind> {
+        let is_path = s.is_path();
+        let span = generators::request_span(&co).await;
+        let str = s
+            .coerce_to_string(
+                co,
+                CoercionKind {
+                    strong: false,
+                    import_paths: false,
+                },
+                span,
+            )
+            .await?
+            .to_contextful_str()?;
+        let result = str
+            .rfind_char('/')
+            .map(|last_slash| {
+                let x = &str[..last_slash];
+                if x.is_empty() {
+                    B("/")
+                } else {
+                    x
+                }
+            })
+            .unwrap_or(b".");
+        if is_path {
+            Ok(Value::Path(Box::new(PathBuf::from(
+                OsString::assert_from_raw_vec(result.to_owned()),
+            ))))
+        } else {
+            Ok(Value::from(NixString::new_inherit_context_from(
+                &str, result,
+            )))
+        }
+    }
+
+    #[builtin("elem")]
+    async fn builtin_elem(co: GenCo, x: Value, xs: Value) -> Result<Value, ErrorKind> {
+        for val in xs.to_list()? {
+            match generators::check_equality(&co, x.clone(), val, PointerEquality::AllowAll).await?
+            {
+                Ok(true) => return Ok(true.into()),
+                Ok(false) => continue,
+                Err(cek) => return Ok(Value::from(cek)),
+            }
+        }
+        Ok(false.into())
+    }
+
+    #[builtin("elemAt")]
+    async fn builtin_elem_at(co: GenCo, xs: Value, i: Value) -> Result<Value, ErrorKind> {
+        let xs = xs.to_list()?;
+        let i = i.as_int()?;
+        if i < 0 {
+            Err(ErrorKind::IndexOutOfBounds { index: i })
+        } else {
+            match xs.get(i as usize) {
+                Some(x) => Ok(x.clone()),
+                None => Err(ErrorKind::IndexOutOfBounds { index: i }),
+            }
+        }
+    }
+
+    #[builtin("filter")]
+    async fn builtin_filter(co: GenCo, pred: Value, list: Value) -> Result<Value, ErrorKind> {
+        let list: NixList = list.to_list()?;
+        let mut out = imbl::Vector::new();
+
+        for value in list {
+            let result = generators::request_call_with(&co, pred.clone(), [value.clone()]).await;
+            let verdict = try_value!(generators::request_force(&co, result).await);
+            if verdict.as_bool()? {
+                out.push_back(value);
+            }
+        }
+
+        Ok(Value::List(out.into()))
+    }
+
+    #[builtin("floor")]
+    async fn builtin_floor(co: GenCo, double: Value) -> Result<Value, ErrorKind> {
+        Ok(Value::Integer(double.as_float()?.floor() as i64))
+    }
+
+    #[builtin("foldl'")]
+    async fn builtin_foldl(
+        co: GenCo,
+        op: Value,
+        #[lazy] nul: Value,
+        list: Value,
+    ) -> Result<Value, ErrorKind> {
+        let mut nul = nul;
+        let list = list.to_list()?;
+        for val in list {
+            // Every call of `op` is forced immediately, but `nul` is not, see
+            // https://github.com/NixOS/nix/blob/940e9eb8/src/libexpr/primops.cc#L3069-L3070C36
+            // and our tests for foldl'.
+            nul = generators::request_call_with(&co, op.clone(), [nul, val]).await;
+            nul = generators::request_force(&co, nul).await;
+            if let c @ Value::Catchable(_) = nul {
+                return Ok(c);
+            }
+        }
+
+        Ok(nul)
+    }
+
+    #[builtin("functionArgs")]
+    async fn builtin_function_args(co: GenCo, f: Value) -> Result<Value, ErrorKind> {
+        let lambda = &f.as_closure()?.lambda();
+        let formals = if let Some(formals) = &lambda.formals {
+            formals
+        } else {
+            return Ok(Value::attrs(NixAttrs::empty()));
+        };
+        Ok(Value::attrs(NixAttrs::from_iter(
+            formals.arguments.iter().map(|(k, v)| (k.clone(), (*v))),
+        )))
+    }
+
+    #[builtin("fromJSON")]
+    async fn builtin_from_json(co: GenCo, json: Value) -> Result<Value, ErrorKind> {
+        let json_str = json.to_str()?;
+
+        serde_json::from_slice(&json_str).map_err(|err| err.into())
+    }
+
+    #[builtin("toJSON")]
+    async fn builtin_to_json(co: GenCo, val: Value) -> Result<Value, ErrorKind> {
+        match val.into_contextful_json(&co).await? {
+            Err(cek) => Ok(Value::from(cek)),
+            Ok((json_value, ctx)) => {
+                let json_str = serde_json::to_string(&json_value)?;
+                Ok(NixString::new_context_from(ctx, json_str).into())
+            }
+        }
+    }
+
+    #[builtin("fromTOML")]
+    async fn builtin_from_toml(co: GenCo, toml: Value) -> Result<Value, ErrorKind> {
+        let toml_str = toml.to_str()?;
+
+        toml::from_str(toml_str.to_str()?).map_err(|err| err.into())
+    }
+
+    #[builtin("filterSource")]
+    #[allow(non_snake_case)]
+    async fn builtin_filterSource(_co: GenCo, #[lazy] _e: Value) -> Result<Value, ErrorKind> {
+        // TODO: implement for nixpkgs compatibility
+        Ok(Value::from(CatchableErrorKind::UnimplementedFeature(
+            "filterSource".into(),
+        )))
+    }
+
+    #[builtin("genericClosure")]
+    async fn builtin_generic_closure(co: GenCo, input: Value) -> Result<Value, ErrorKind> {
+        let attrs = input.to_attrs()?;
+
+        // The work set is maintained as a VecDeque because new items
+        // are popped from the front.
+        let mut work_set: VecDeque<Value> =
+            generators::request_force(&co, attrs.select_required("startSet")?.clone())
+                .await
+                .to_list()?
+                .into_iter()
+                .collect();
+
+        let operator = attrs.select_required("operator")?;
+
+        let mut res = imbl::Vector::new();
+        let mut done_keys: Vec<Value> = vec![];
+
+        while let Some(val) = work_set.pop_front() {
+            let val = generators::request_force(&co, val).await;
+            let attrs = val.to_attrs()?;
+            let key = attrs.select_required("key")?;
+
+            let value_missing = bgc_insert_key(&co, key.clone(), &mut done_keys).await?;
+
+            if let Err(cek) = value_missing {
+                return Ok(Value::Catchable(Box::new(cek)));
+            }
+
+            if let Ok(false) = value_missing {
+                continue;
+            }
+
+            res.push_back(val.clone());
+
+            let op_result = generators::request_force(
+                &co,
+                generators::request_call_with(&co, operator.clone(), [val]).await,
+            )
+            .await;
+
+            work_set.extend(op_result.to_list()?.into_iter());
+        }
+
+        Ok(Value::List(NixList::from(res)))
+    }
+
+    #[builtin("genList")]
+    async fn builtin_gen_list(
+        co: GenCo,
+        // Nix 2.3 doesn't propagate failures here
+        #[lazy] generator: Value,
+        length: Value,
+    ) -> Result<Value, ErrorKind> {
+        let mut out = imbl::Vector::<Value>::new();
+        let len = length.as_int()?;
+        // the best span we can get…
+        let span = generators::request_span(&co).await;
+
+        for i in 0..len {
+            let val = Value::Thunk(Thunk::new_suspended_call(
+                generator.clone(),
+                i.into(),
+                span.clone(),
+            ));
+            out.push_back(val);
+        }
+
+        Ok(Value::List(out.into()))
+    }
+
+    #[builtin("getAttr")]
+    async fn builtin_get_attr(co: GenCo, key: Value, set: Value) -> Result<Value, ErrorKind> {
+        let k = key.to_str()?;
+        let xs = set.to_attrs()?;
+
+        match xs.select(&k) {
+            Some(x) => Ok(x.clone()),
+            None => Err(ErrorKind::AttributeNotFound {
+                name: k.to_string(),
+            }),
+        }
+    }
+
+    #[builtin("groupBy")]
+    async fn builtin_group_by(co: GenCo, f: Value, list: Value) -> Result<Value, ErrorKind> {
+        let mut res: BTreeMap<NixString, imbl::Vector<Value>> = BTreeMap::new();
+        for val in list.to_list()? {
+            let key = try_value!(
+                generators::request_force(
+                    &co,
+                    generators::request_call_with(&co, f.clone(), [val.clone()]).await,
+                )
+                .await
+            )
+            .to_str()?;
+
+            res.entry(key).or_default().push_back(val);
+        }
+        Ok(Value::attrs(NixAttrs::from_iter(
+            res.into_iter()
+                .map(|(k, v)| (k, Value::List(NixList::from(v)))),
+        )))
+    }
+
+    #[builtin("hasAttr")]
+    async fn builtin_has_attr(co: GenCo, key: Value, set: Value) -> Result<Value, ErrorKind> {
+        let k = key.to_str()?;
+        let xs = set.to_attrs()?;
+
+        Ok(Value::Bool(xs.contains(&k)))
+    }
+
+    #[builtin("hasContext")]
+    #[allow(non_snake_case)]
+    async fn builtin_hasContext(co: GenCo, e: Value) -> Result<Value, ErrorKind> {
+        if e.is_catchable() {
+            return Ok(e);
+        }
+
+        let v = e.to_contextful_str()?;
+        Ok(Value::Bool(v.has_context()))
+    }
+
+    #[builtin("getContext")]
+    #[allow(non_snake_case)]
+    async fn builtin_getContext(co: GenCo, e: Value) -> Result<Value, ErrorKind> {
+        if e.is_catchable() {
+            return Ok(e);
+        }
+
+        // also forces the value
+        let span = generators::request_span(&co).await;
+        let v = e
+            .coerce_to_string(
+                co,
+                CoercionKind {
+                    strong: true,
+                    import_paths: true,
+                },
+                span,
+            )
+            .await?;
+        let s = v.to_contextful_str()?;
+
+        let groups = s
+            .iter_context()
+            .flat_map(|context| context.iter())
+            // Do not think `group_by` works here.
+            // `group_by` works on consecutive elements of the iterator.
+            // Due to how `HashSet` works (ordering is not guaranteed),
+            // this can become a source of non-determinism if you `group_by` naively.
+            // I know I did.
+            .into_grouping_map_by(|ctx_element| match ctx_element {
+                NixContextElement::Plain(spath) => spath,
+                NixContextElement::Single { derivation, .. } => derivation,
+                NixContextElement::Derivation(drv_path) => drv_path,
+            })
+            .collect::<Vec<_>>();
+
+        let elements = groups
+            .into_iter()
+            .map(|(key, group)| {
+                let mut outputs: Vector<NixString> = Vector::new();
+                let mut is_path = false;
+                let mut all_outputs = false;
+
+                for ctx_element in group {
+                    match ctx_element {
+                        NixContextElement::Plain(spath) => {
+                            debug_assert!(spath == key, "Unexpected group containing mixed keys, expected: {:?}, encountered {:?}", key, spath);
+                            is_path = true;
+                        }
+
+                        NixContextElement::Single { name, derivation } => {
+                            debug_assert!(derivation == key, "Unexpected group containing mixed keys, expected: {:?}, encountered {:?}", key, derivation);
+                            outputs.push_back(name.clone().into());
+                        }
+
+                        NixContextElement::Derivation(drv_path) => {
+                            debug_assert!(drv_path == key, "Unexpected group containing mixed keys, expected: {:?}, encountered {:?}", key, drv_path);
+                            all_outputs = true;
+                        }
+                    }
+                }
+
+                // FIXME(raitobezarius): is there a better way to construct an attribute set
+                // conditionally?
+                let mut vec_attrs: Vec<(&str, Value)> = Vec::new();
+
+                if is_path {
+                    vec_attrs.push(("path", true.into()));
+                }
+
+                if all_outputs {
+                    vec_attrs.push(("allOutputs", true.into()));
+                }
+
+                if !outputs.is_empty() {
+                    outputs.sort();
+                    vec_attrs.push(("outputs", Value::List(outputs
+                                .into_iter()
+                                .map(|s| s.into())
+                                .collect::<Vector<Value>>()
+                                .into()
+                    )));
+                }
+
+                (key.clone(), Value::attrs(NixAttrs::from_iter(vec_attrs.into_iter())))
+            });
+
+        Ok(Value::attrs(NixAttrs::from_iter(elements)))
+    }
+
+    #[builtin("appendContext")]
+    #[allow(non_snake_case)]
+    async fn builtin_appendContext(
+        co: GenCo,
+        origin: Value,
+        added_context: Value,
+    ) -> Result<Value, ErrorKind> {
+        // `appendContext` is a "grow" context function.
+        // It cannot remove a context element, neither replace a piece of its contents.
+        //
+        // Growing context is always a safe operation, there's no loss of dependency tracking
+        // information.
+        //
+        // This is why this operation is not prefixed by `unsafe` and is deemed *safe*.
+        // Nonetheless, it is possible to craft nonsensical context elements referring
+        // to inexistent derivations, output paths or output names.
+        //
+        // In Nix, those nonsensical context elements are partially mitigated by checking
+        // that various parameters are indeed syntatically valid store paths in the context, i.e.
+        // starting with the same prefix as `builtins.storeDir`, or ending with `.drv`.
+        // In addition, if writing to the store is possible (evaluator not in read-only mode), Nix
+        // will realize some paths and ensures they are present in the store.
+        //
+        // In this implementation, we do none of that, no syntax checks, no realization.
+        // The next `TODO` are the checks that Nix implements.
+        let mut ctx_elements: HashSet<NixContextElement> = HashSet::new();
+        let span = generators::request_span(&co).await;
+        let origin = origin
+            .coerce_to_string(
+                co,
+                CoercionKind {
+                    strong: true,
+                    import_paths: true,
+                },
+                span,
+            )
+            .await?;
+        let mut origin = origin.to_contextful_str()?;
+
+        let added_context = added_context.to_attrs()?;
+        for (context_key, context_element) in added_context.into_iter() {
+            // Invariant checks:
+            // - TODO: context_key must be a syntactically valid store path.
+            // - Perform a deep force `context_element`.
+            let context_element = context_element.to_attrs()?;
+            if let Some(path) = context_element.select("path") {
+                if path.as_bool()? {
+                    ctx_elements.insert(NixContextElement::Plain(context_key.to_string()));
+                }
+            }
+            if let Some(all_outputs) = context_element.select("allOutputs") {
+                if all_outputs.as_bool()? {
+                    // TODO: check if `context_key` is a derivation path.
+                    // This may require realization.
+                    ctx_elements.insert(NixContextElement::Derivation(context_key.to_string()));
+                }
+            }
+            if let Some(some_outputs) = context_element.select("outputs") {
+                let some_outputs = some_outputs.to_list()?;
+                // TODO: check if `context_key` is a derivation path.
+                // This may require realization.
+                for output in some_outputs.into_iter() {
+                    let output = output.to_str()?;
+                    ctx_elements.insert(NixContextElement::Single {
+                        derivation: context_key.to_string(),
+                        name: output.to_string(),
+                    });
+                }
+            }
+        }
+
+        if let Some(origin_ctx) = origin.context_mut() {
+            // FUTUREWORK(performance): avoid this clone
+            // and extend in-place.
+            *origin_ctx = origin_ctx.clone().join(&mut ctx_elements.into());
+        }
+
+        Ok(origin.into())
+    }
+
+    #[builtin("hashString")]
+    async fn builtin_hash_string(co: GenCo, algo: Value, s: Value) -> Result<Value, ErrorKind> {
+        hash_nix_string(algo.to_str()?, std::io::Cursor::new(s.to_str()?)).map(Value::from)
+    }
+
+    #[builtin("head")]
+    async fn builtin_head(co: GenCo, list: Value) -> Result<Value, ErrorKind> {
+        if list.is_catchable() {
+            return Ok(list);
+        }
+
+        match list.to_list()?.get(0) {
+            Some(x) => Ok(x.clone()),
+            None => Err(ErrorKind::IndexOutOfBounds { index: 0 }),
+        }
+    }
+
+    #[builtin("intersectAttrs")]
+    async fn builtin_intersect_attrs(co: GenCo, x: Value, y: Value) -> Result<Value, ErrorKind> {
+        if x.is_catchable() {
+            return Ok(x);
+        }
+        if y.is_catchable() {
+            return Ok(y);
+        }
+        let left_set = x.to_attrs()?;
+        if left_set.is_empty() {
+            return Ok(Value::attrs(NixAttrs::empty()));
+        }
+        let mut left_keys = left_set.keys();
+
+        let right_set = y.to_attrs()?;
+        if right_set.is_empty() {
+            return Ok(Value::attrs(NixAttrs::empty()));
+        }
+        let mut right_keys = right_set.keys();
+
+        let mut out: OrdMap<NixString, Value> = OrdMap::new();
+
+        // Both iterators have at least one entry
+        let mut left = left_keys.next().unwrap();
+        let mut right = right_keys.next().unwrap();
+
+        // Calculate the intersection of the attribute sets by simultaneously
+        // advancing two key iterators, and inserting into the result set from
+        // the right side when the keys match. Iteration over Nix attribute sets
+        // is in sorted lexicographical order, so we can advance either iterator
+        // until it "catches up" with its counterpart.
+        //
+        // Only when keys match are the key and value clones actually allocated.
+        //
+        // We opted for this implementation over simpler ones because of the
+        // heavy use of this function in nixpkgs.
+        loop {
+            if left == right {
+                // We know that the key exists in the set, and can
+                // skip the check instructions.
+                unsafe {
+                    out.insert(
+                        right.clone(),
+                        right_set.select(right).unwrap_unchecked().clone(),
+                    );
+                }
+
+                left = match left_keys.next() {
+                    Some(x) => x,
+                    None => break,
+                };
+
+                right = match right_keys.next() {
+                    Some(x) => x,
+                    None => break,
+                };
+
+                continue;
+            }
+
+            if left < right {
+                left = match left_keys.next() {
+                    Some(x) => x,
+                    None => break,
+                };
+                continue;
+            }
+
+            if right < left {
+                right = match right_keys.next() {
+                    Some(x) => x,
+                    None => break,
+                };
+                continue;
+            }
+        }
+
+        Ok(Value::attrs(out.into()))
+    }
+
+    #[builtin("isAttrs")]
+    async fn builtin_is_attrs(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
+        // TODO(edef): make this beautiful
+        if value.is_catchable() {
+            return Ok(value);
+        }
+
+        Ok(Value::Bool(matches!(value, Value::Attrs(_))))
+    }
+
+    #[builtin("isBool")]
+    async fn builtin_is_bool(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
+        if value.is_catchable() {
+            return Ok(value);
+        }
+
+        Ok(Value::Bool(matches!(value, Value::Bool(_))))
+    }
+
+    #[builtin("isFloat")]
+    async fn builtin_is_float(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
+        if value.is_catchable() {
+            return Ok(value);
+        }
+
+        Ok(Value::Bool(matches!(value, Value::Float(_))))
+    }
+
+    #[builtin("isFunction")]
+    async fn builtin_is_function(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
+        if value.is_catchable() {
+            return Ok(value);
+        }
+
+        Ok(Value::Bool(matches!(
+            value,
+            Value::Closure(_) | Value::Builtin(_)
+        )))
+    }
+
+    #[builtin("isInt")]
+    async fn builtin_is_int(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
+        if value.is_catchable() {
+            return Ok(value);
+        }
+
+        Ok(Value::Bool(matches!(value, Value::Integer(_))))
+    }
+
+    #[builtin("isList")]
+    async fn builtin_is_list(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
+        if value.is_catchable() {
+            return Ok(value);
+        }
+
+        Ok(Value::Bool(matches!(value, Value::List(_))))
+    }
+
+    #[builtin("isNull")]
+    async fn builtin_is_null(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
+        if value.is_catchable() {
+            return Ok(value);
+        }
+
+        Ok(Value::Bool(matches!(value, Value::Null)))
+    }
+
+    #[builtin("isPath")]
+    async fn builtin_is_path(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
+        if value.is_catchable() {
+            return Ok(value);
+        }
+
+        Ok(Value::Bool(matches!(value, Value::Path(_))))
+    }
+
+    #[builtin("isString")]
+    async fn builtin_is_string(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
+        if value.is_catchable() {
+            return Ok(value);
+        }
+
+        Ok(Value::Bool(matches!(value, Value::String(_))))
+    }
+
+    #[builtin("length")]
+    async fn builtin_length(co: GenCo, list: Value) -> Result<Value, ErrorKind> {
+        if list.is_catchable() {
+            return Ok(list);
+        }
+        Ok(Value::Integer(list.to_list()?.len() as i64))
+    }
+
+    #[builtin("lessThan")]
+    async fn builtin_less_than(co: GenCo, x: Value, y: Value) -> Result<Value, ErrorKind> {
+        let span = generators::request_span(&co).await;
+        match x.nix_cmp_ordering(y, co, span).await? {
+            Err(cek) => Ok(Value::from(cek)),
+            Ok(Ordering::Less) => Ok(Value::Bool(true)),
+            Ok(_) => Ok(Value::Bool(false)),
+        }
+    }
+
+    #[builtin("listToAttrs")]
+    async fn builtin_list_to_attrs(co: GenCo, list: Value) -> Result<Value, ErrorKind> {
+        let list = list.to_list()?;
+        let mut map = BTreeMap::new();
+        for val in list {
+            let attrs = try_value!(generators::request_force(&co, val).await).to_attrs()?;
+            let name = try_value!(
+                generators::request_force(&co, attrs.select_required("name")?.clone()).await
+            )
+            .to_str()?;
+            let value = attrs.select_required("value")?.clone();
+            // Map entries earlier in the list take precedence over entries later in the list
+            map.entry(name).or_insert(value);
+        }
+        Ok(Value::attrs(NixAttrs::from_iter(map.into_iter())))
+    }
+
+    #[builtin("map")]
+    async fn builtin_map(co: GenCo, #[lazy] f: Value, list: Value) -> Result<Value, ErrorKind> {
+        let mut out = imbl::Vector::<Value>::new();
+
+        // the best span we can get…
+        let span = generators::request_span(&co).await;
+
+        for val in list.to_list()? {
+            let result = Value::Thunk(Thunk::new_suspended_call(f.clone(), val, span.clone()));
+            out.push_back(result)
+        }
+
+        Ok(Value::List(out.into()))
+    }
+
+    #[builtin("mapAttrs")]
+    async fn builtin_map_attrs(
+        co: GenCo,
+        #[lazy] f: Value,
+        attrs: Value,
+    ) -> Result<Value, ErrorKind> {
+        let attrs = attrs.to_attrs()?;
+        let mut out = imbl::OrdMap::new();
+
+        // the best span we can get…
+        let span = generators::request_span(&co).await;
+
+        for (key, value) in attrs.into_iter() {
+            let result = Value::Thunk(Thunk::new_suspended_call(
+                f.clone(),
+                key.clone().into(),
+                span.clone(),
+            ));
+            let result = Value::Thunk(Thunk::new_suspended_call(result, value, span.clone()));
+
+            out.insert(key, result);
+        }
+
+        Ok(Value::attrs(out.into()))
+    }
+
+    #[builtin("match")]
+    async fn builtin_match(co: GenCo, regex: Value, str: Value) -> Result<Value, ErrorKind> {
+        let s = str;
+        if s.is_catchable() {
+            return Ok(s);
+        }
+        let s = s.to_contextful_str()?;
+        let re = regex;
+        if re.is_catchable() {
+            return Ok(re);
+        }
+        let re = re.to_str()?;
+        let re: Regex = Regex::new(&format!("^{}$", re.to_str()?)).unwrap();
+        match re.captures(s.to_str()?) {
+            Some(caps) => Ok(Value::List(
+                caps.iter()
+                    .skip(1)
+                    .map(|grp| {
+                        // Surprisingly, Nix does not propagate
+                        // the original context here.
+                        // Though, it accepts contextful strings as an argument.
+                        // An example of such behaviors in nixpkgs
+                        // can be observed in make-initrd.nix when it comes
+                        // to compressors which are matched over their full command
+                        // and then a compressor name will be extracted from that.
+                        grp.map(|g| Value::from(g.as_str())).unwrap_or(Value::Null)
+                    })
+                    .collect::<imbl::Vector<Value>>()
+                    .into(),
+            )),
+            None => Ok(Value::Null),
+        }
+    }
+
+    #[builtin("mul")]
+    async fn builtin_mul(co: GenCo, x: Value, y: Value) -> Result<Value, ErrorKind> {
+        arithmetic_op!(&x, &y, *)
+    }
+
+    #[builtin("parseDrvName")]
+    async fn builtin_parse_drv_name(co: GenCo, s: Value) -> Result<Value, ErrorKind> {
+        if s.is_catchable() {
+            return Ok(s);
+        }
+
+        // This replicates cppnix's (mis?)handling of codepoints
+        // above U+007f following 0x2d ('-')
+        let s = s.to_str()?;
+        let slice: &[u8] = s.as_ref();
+        let (name, dash_and_version) = slice.split_at(
+            slice
+                .windows(2)
+                .enumerate()
+                .find_map(|x| match x {
+                    (idx, [b'-', c1]) if !c1.is_ascii_alphabetic() => Some(idx),
+                    _ => None,
+                })
+                .unwrap_or(slice.len()),
+        );
+        let version = dash_and_version
+            .split_first()
+            .map(|x| core::str::from_utf8(x.1))
+            .unwrap_or(Ok(""))?;
+        Ok(Value::attrs(NixAttrs::from_iter(
+            [("name", core::str::from_utf8(name)?), ("version", version)].into_iter(),
+        )))
+    }
+
+    #[builtin("partition")]
+    async fn builtin_partition(co: GenCo, pred: Value, list: Value) -> Result<Value, ErrorKind> {
+        let mut right: imbl::Vector<Value> = Default::default();
+        let mut wrong: imbl::Vector<Value> = Default::default();
+
+        let list: NixList = list.to_list()?;
+        for elem in list {
+            let result = generators::request_call_with(&co, pred.clone(), [elem.clone()]).await;
+
+            if try_value!(generators::request_force(&co, result).await).as_bool()? {
+                right.push_back(elem);
+            } else {
+                wrong.push_back(elem);
+            };
+        }
+
+        let res = [
+            ("right", Value::List(NixList::from(right))),
+            ("wrong", Value::List(NixList::from(wrong))),
+        ];
+
+        Ok(Value::attrs(NixAttrs::from_iter(res.into_iter())))
+    }
+
+    #[builtin("removeAttrs")]
+    async fn builtin_remove_attrs(
+        co: GenCo,
+        attrs: Value,
+        keys: Value,
+    ) -> Result<Value, ErrorKind> {
+        let attrs = attrs.to_attrs()?;
+        let keys = keys
+            .to_list()?
+            .into_iter()
+            .map(|v| v.to_str())
+            .collect::<Result<HashSet<_>, _>>()?;
+        let res = attrs.iter().filter_map(|(k, v)| {
+            if !keys.contains(k) {
+                Some((k.clone(), v.clone()))
+            } else {
+                None
+            }
+        });
+        Ok(Value::attrs(NixAttrs::from_iter(res)))
+    }
+
+    #[builtin("replaceStrings")]
+    async fn builtin_replace_strings(
+        co: GenCo,
+        from: Value,
+        to: Value,
+        s: Value,
+    ) -> Result<Value, ErrorKind> {
+        let from = from.to_list()?;
+        for val in &from {
+            try_value!(generators::request_force(&co, val.clone()).await);
+        }
+
+        let to = to.to_list()?;
+        for val in &to {
+            try_value!(generators::request_force(&co, val.clone()).await);
+        }
+
+        let mut string = s.to_contextful_str()?;
+
+        let mut res = BString::default();
+
+        let mut i: usize = 0;
+        let mut empty_string_replace = false;
+        let mut context = NixContext::new();
+
+        if let Some(string_context) = string.context_mut() {
+            context = context.join(string_context);
+        }
+
+        // This can't be implemented using Rust's string.replace() as
+        // well as a map because we need to handle errors with results
+        // as well as "reset" the iterator to zero for the replacement
+        // everytime there's a successful match.
+        // Also, Rust's string.replace allocates a new string
+        // on every call which is not preferable.
+        'outer: while i < string.len() {
+            // Try a match in all the from strings
+            for elem in std::iter::zip(from.iter(), to.iter()) {
+                let from = elem.0.to_contextful_str()?;
+                let mut to = elem.1.to_contextful_str()?;
+
+                if i + from.len() > string.len() {
+                    continue;
+                }
+
+                // We already applied a from->to with an empty from
+                // transformation.
+                // Let's skip it so that we don't loop infinitely
+                if empty_string_replace && from.is_empty() {
+                    continue;
+                }
+
+                // if we match the `from` string, let's replace
+                if string[i..i + from.len()] == *from {
+                    res.push_str(&to);
+                    i += from.len();
+                    if let Some(to_ctx) = to.context_mut() {
+                        context = context.join(to_ctx);
+                    }
+
+                    // remember if we applied the empty from->to
+                    empty_string_replace = from.is_empty();
+
+                    continue 'outer;
+                }
+            }
+
+            // If we don't match any `from`, we simply add a character
+            res.push_str(&string[i..i + 1]);
+            i += 1;
+
+            // Since we didn't apply anything transformation,
+            // we reset the empty string replacement
+            empty_string_replace = false;
+        }
+
+        // Special case when the string is empty or at the string's end
+        // and one of the from is also empty
+        for elem in std::iter::zip(from.iter(), to.iter()) {
+            let from = elem.0.to_contextful_str()?;
+            // We mutate `to` by consuming its context
+            // if we perform a successful replacement.
+            // Therefore, it's fine if `to` was mutate and we reuse it here.
+            // We don't need to merge again the context, it's already in the right state.
+            let mut to = elem.1.to_contextful_str()?;
+
+            if from.is_empty() {
+                res.push_str(&to);
+                if let Some(to_ctx) = to.context_mut() {
+                    context = context.join(to_ctx);
+                }
+                break;
+            }
+        }
+
+        Ok(Value::from(NixString::new_context_from(context, res)))
+    }
+
+    #[builtin("seq")]
+    async fn builtin_seq(co: GenCo, _x: Value, y: Value) -> Result<Value, ErrorKind> {
+        // The builtin calling infra has already forced both args for us, so
+        // we just return the second and ignore the first
+        Ok(y)
+    }
+
+    #[builtin("split")]
+    async fn builtin_split(co: GenCo, regex: Value, str: Value) -> Result<Value, ErrorKind> {
+        if str.is_catchable() {
+            return Ok(str);
+        }
+
+        if regex.is_catchable() {
+            return Ok(regex);
+        }
+
+        let s = str.to_contextful_str()?;
+        let text = s.to_str()?;
+        let re = regex.to_str()?;
+        let re = Regex::new(re.to_str()?).unwrap();
+        let mut capture_locations = re.capture_locations();
+        let num_captures = capture_locations.len();
+        let mut ret = imbl::Vector::new();
+        let mut pos = 0;
+
+        while let Some(thematch) = re.captures_read_at(&mut capture_locations, text, pos) {
+            // push the unmatched characters preceding the match
+            ret.push_back(Value::from(NixString::new_inherit_context_from(
+                &s,
+                &text[pos..thematch.start()],
+            )));
+
+            // Push a list with one element for each capture
+            // group in the regex, containing the characters
+            // matched by that capture group, or null if no match.
+            // We skip capture 0; it represents the whole match.
+            let v: imbl::Vector<Value> = (1..num_captures)
+                .map(|i| capture_locations.get(i))
+                .map(|o| {
+                    o.map(|(start, end)| {
+                        // Here, a surprising thing happens: we silently discard the original
+                        // context. This is as intended, Nix does the same.
+                        Value::from(&text[start..end])
+                    })
+                    .unwrap_or(Value::Null)
+                })
+                .collect();
+            ret.push_back(Value::List(NixList::from(v)));
+            pos = thematch.end();
+        }
+
+        // push the unmatched characters following the last match
+        // Here, a surprising thing happens: we silently discard the original
+        // context. This is as intended, Nix does the same.
+        ret.push_back(Value::from(&text[pos..]));
+
+        Ok(Value::List(NixList::from(ret)))
+    }
+
+    #[builtin("sort")]
+    async fn builtin_sort(co: GenCo, comparator: Value, list: Value) -> Result<Value, ErrorKind> {
+        let list = list.to_list()?;
+        let mut len = list.len();
+        let mut data = list.into_inner();
+
+        // Asynchronous sorting algorithm in which the comparator can make use of
+        // VM requests (required as `builtins.sort` uses comparators written in
+        // Nix).
+        //
+        // This is a simple, optimised bubble sort implementation. The choice of
+        // algorithm is constrained by the comparator in Nix not being able to
+        // yield equality, and us being unable to use the standard library
+        // implementation of sorting (which is a lot longer, but a lot more
+        // efficient) here.
+        // TODO(amjoseph): Investigate potential impl in Nix code, or Tvix bytecode.
+        loop {
+            let mut new_len = 0;
+            for i in 1..len {
+                if try_value!(
+                    generators::request_force(
+                        &co,
+                        generators::request_call_with(
+                            &co,
+                            comparator.clone(),
+                            [data[i].clone(), data[i - 1].clone()],
+                        )
+                        .await,
+                    )
+                    .await
+                )
+                .as_bool()
+                .context("evaluating comparator in `builtins.sort`")?
+                {
+                    data.swap(i, i - 1);
+                    new_len = i;
+                }
+            }
+
+            if new_len == 0 {
+                break;
+            }
+
+            len = new_len;
+        }
+
+        Ok(Value::List(data.into()))
+    }
+
+    #[builtin("splitVersion")]
+    async fn builtin_split_version(co: GenCo, s: Value) -> Result<Value, ErrorKind> {
+        if s.is_catchable() {
+            return Ok(s);
+        }
+        let s = s.to_str()?;
+        let s = VersionPartsIter::new((&s).into());
+
+        let parts = s
+            .map(|s| {
+                Value::from(match s {
+                    VersionPart::Number(n) => n,
+                    VersionPart::Word(w) => w,
+                })
+            })
+            .collect::<Vec<Value>>();
+        Ok(Value::List(NixList::construct(parts.len(), parts)))
+    }
+
+    #[builtin("stringLength")]
+    async fn builtin_string_length(co: GenCo, #[lazy] s: Value) -> Result<Value, ErrorKind> {
+        // also forces the value
+        let span = generators::request_span(&co).await;
+        let s = s
+            .coerce_to_string(
+                co,
+                CoercionKind {
+                    strong: false,
+                    import_paths: true,
+                },
+                span,
+            )
+            .await?;
+
+        if s.is_catchable() {
+            return Ok(s);
+        }
+
+        Ok(Value::Integer(s.to_contextful_str()?.len() as i64))
+    }
+
+    #[builtin("sub")]
+    async fn builtin_sub(co: GenCo, x: Value, y: Value) -> Result<Value, ErrorKind> {
+        arithmetic_op!(&x, &y, -)
+    }
+
+    #[builtin("substring")]
+    async fn builtin_substring(
+        co: GenCo,
+        start: Value,
+        len: Value,
+        s: Value,
+    ) -> Result<Value, ErrorKind> {
+        let beg = start.as_int()?;
+        let len = len.as_int()?;
+        let span = generators::request_span(&co).await;
+        let x = s
+            .coerce_to_string(
+                co,
+                CoercionKind {
+                    strong: false,
+                    import_paths: true,
+                },
+                span,
+            )
+            .await?;
+        if x.is_catchable() {
+            return Ok(x);
+        }
+        let x = x.to_contextful_str()?;
+
+        if beg < 0 {
+            return Err(ErrorKind::IndexOutOfBounds { index: beg });
+        }
+        let beg = beg as usize;
+
+        // Nix doesn't assert that the length argument is
+        // non-negative when the starting index is GTE the
+        // string's length.
+        if beg >= x.len() {
+            return Ok(Value::from(NixString::new_inherit_context_from(
+                &x,
+                BString::default(),
+            )));
+        }
+
+        let end = if len < 0 {
+            x.len()
+        } else {
+            cmp::min(beg + (len as usize), x.len())
+        };
+
+        Ok(Value::from(NixString::new_inherit_context_from(
+            &x,
+            &x[beg..end],
+        )))
+    }
+
+    #[builtin("tail")]
+    async fn builtin_tail(co: GenCo, list: Value) -> Result<Value, ErrorKind> {
+        if list.is_catchable() {
+            return Ok(list);
+        }
+
+        let xs = list.to_list()?;
+
+        if xs.is_empty() {
+            Err(ErrorKind::TailEmptyList)
+        } else {
+            let output = xs.into_iter().skip(1).collect::<Vec<_>>();
+            Ok(Value::List(NixList::construct(output.len(), output)))
+        }
+    }
+
+    #[builtin("throw")]
+    async fn builtin_throw(co: GenCo, message: Value) -> Result<Value, ErrorKind> {
+        // If it's already some error, let's propagate it immediately.
+        if message.is_catchable() {
+            return Ok(message);
+        }
+        // TODO(sterni): coerces to string
+        // We do not care about the context here explicitly.
+        Ok(Value::from(CatchableErrorKind::Throw(
+            message.to_contextful_str()?.to_string().into(),
+        )))
+    }
+
+    #[builtin("toString")]
+    async fn builtin_to_string(co: GenCo, #[lazy] x: Value) -> Result<Value, ErrorKind> {
+        // TODO(edef): please fix me w.r.t. to catchability.
+        // coerce_to_string forces for us
+        // FIXME: should `coerce_to_string` preserve context?
+        // it does for now.
+        let span = generators::request_span(&co).await;
+        x.coerce_to_string(
+            co,
+            CoercionKind {
+                strong: true,
+                import_paths: false,
+            },
+            span,
+        )
+        .await
+    }
+
+    #[builtin("toXML")]
+    async fn builtin_to_xml(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
+        let value = generators::request_deep_force(&co, value).await;
+        if value.is_catchable() {
+            return Ok(value);
+        }
+
+        let mut buf: Vec<u8> = vec![];
+        to_xml::value_to_xml(&mut buf, &value)?;
+        Ok(String::from_utf8(buf)?.into())
+    }
+
+    #[builtin("placeholder")]
+    async fn builtin_placeholder(co: GenCo, #[lazy] _x: Value) -> Result<Value, ErrorKind> {
+        generators::emit_warning_kind(&co, WarningKind::NotImplemented("builtins.placeholder"))
+            .await;
+        Ok("<builtins.placeholder-is-not-implemented-in-tvix-yet>".into())
+    }
+
+    #[builtin("trace")]
+    async fn builtin_trace(co: GenCo, message: Value, value: Value) -> Result<Value, ErrorKind> {
+        // TODO(grfn): `trace` should be pluggable and capturable, probably via a method on
+        // the VM
+        eprintln!("trace: {} :: {}", message, message.type_of());
+        Ok(value)
+    }
+
+    #[builtin("toPath")]
+    async fn builtin_to_path(co: GenCo, s: Value) -> Result<Value, ErrorKind> {
+        if s.is_catchable() {
+            return Ok(s);
+        }
+
+        match coerce_value_to_path(&co, s).await? {
+            Err(cek) => Ok(Value::from(cek)),
+            Ok(path) => {
+                let path: Value = crate::value::canon_path(path).into();
+                let span = generators::request_span(&co).await;
+                Ok(path
+                    .coerce_to_string(
+                        co,
+                        CoercionKind {
+                            strong: false,
+                            import_paths: false,
+                        },
+                        span,
+                    )
+                    .await?)
+            }
+        }
+    }
+
+    #[builtin("tryEval")]
+    async fn builtin_try_eval(co: GenCo, #[lazy] e: Value) -> Result<Value, ErrorKind> {
+        let res = match generators::request_try_force(&co, e).await {
+            Value::Catchable(_) => [("value", false.into()), ("success", false.into())],
+            value => [("value", value), ("success", true.into())],
+        };
+
+        Ok(Value::attrs(NixAttrs::from_iter(res.into_iter())))
+    }
+
+    #[builtin("typeOf")]
+    async fn builtin_type_of(co: GenCo, x: Value) -> Result<Value, ErrorKind> {
+        if x.is_catchable() {
+            return Ok(x);
+        }
+
+        Ok(Value::from(x.type_of()))
+    }
+}
+
+/// Internal helper function for genericClosure, determining whether a
+/// value has been seen before.
+async fn bgc_insert_key(
+    co: &GenCo,
+    key: Value,
+    done: &mut Vec<Value>,
+) -> Result<Result<bool, CatchableErrorKind>, ErrorKind> {
+    for existing in done.iter() {
+        match generators::check_equality(
+            co,
+            existing.clone(),
+            key.clone(),
+            // TODO(tazjin): not actually sure which semantics apply here
+            PointerEquality::ForbidAll,
+        )
+        .await?
+        {
+            Ok(true) => return Ok(Ok(false)),
+            Ok(false) => (),
+            Err(cek) => return Ok(Err(cek)),
+        }
+    }
+
+    done.push(key);
+    Ok(Ok(true))
+}
+
+/// The set of standard pure builtins in Nix, mostly concerned with
+/// data structure manipulation (string, attrs, list, etc. functions).
+pub fn pure_builtins() -> Vec<(&'static str, Value)> {
+    let mut result = pure_builtins::builtins();
+
+    // Pure-value builtins
+    result.push(("nixVersion", Value::from("2.3-compat-tvix-0.1")));
+    result.push(("langVersion", Value::Integer(6)));
+    result.push(("null", Value::Null));
+    result.push(("true", Value::Bool(true)));
+    result.push(("false", Value::Bool(false)));
+
+    result.push((
+        "currentSystem",
+        crate::systems::llvm_triple_to_nix_double(CURRENT_PLATFORM).into(),
+    ));
+
+    // TODO: implement for nixpkgs compatibility
+    result.push((
+        "__curPos",
+        Value::from(CatchableErrorKind::UnimplementedFeature("__curPos".into())),
+    ));
+
+    result
+}
+
+#[builtins]
+mod placeholder_builtins {
+    use super::*;
+
+    #[builtin("unsafeDiscardStringContext")]
+    async fn builtin_unsafe_discard_string_context(
+        co: GenCo,
+        s: Value,
+    ) -> Result<Value, ErrorKind> {
+        let span = generators::request_span(&co).await;
+        let mut v = s
+            .coerce_to_string(
+                co,
+                // It's weak because
+                // lists, integers, floats and null are not
+                // accepted as parameters.
+                CoercionKind {
+                    strong: false,
+                    import_paths: true,
+                },
+                span,
+            )
+            .await?
+            .to_contextful_str()?;
+        v.clear_context();
+        Ok(Value::from(v))
+    }
+
+    #[builtin("unsafeDiscardOutputDependency")]
+    async fn builtin_unsafe_discard_output_dependency(
+        co: GenCo,
+        s: Value,
+    ) -> Result<Value, ErrorKind> {
+        let span = generators::request_span(&co).await;
+        let mut v = s
+            .coerce_to_string(
+                co,
+                // It's weak because
+                // lists, integers, floats and null are not
+                // accepted as parameters.
+                CoercionKind {
+                    strong: false,
+                    import_paths: true,
+                },
+                span,
+            )
+            .await?
+            .to_contextful_str()?;
+
+        // If there's any context, we will swap any ... by a path one.
+        if let Some(ctx) = v.context_mut() {
+            let new_context: tvix_eval::NixContext = ctx
+                .iter()
+                .map(|elem| match elem {
+                    // FUTUREWORK(performance): ideally, we should either:
+                    // (a) do interior mutation of the existing context.
+                    // (b) let the structural sharing make those clones cheap.
+                    crate::NixContextElement::Derivation(drv_path) => {
+                        crate::NixContextElement::Plain(drv_path.to_string())
+                    }
+                    elem => elem.clone(),
+                })
+                .collect::<HashSet<_>>()
+                .into();
+
+            *ctx = new_context;
+        }
+
+        Ok(Value::from(v))
+    }
+
+    #[builtin("addErrorContext")]
+    async fn builtin_add_error_context(
+        co: GenCo,
+        #[lazy] _context: Value,
+        #[lazy] val: Value,
+    ) -> Result<Value, ErrorKind> {
+        generators::emit_warning_kind(&co, WarningKind::NotImplemented("builtins.addErrorContext"))
+            .await;
+        Ok(val)
+    }
+
+    #[builtin("unsafeGetAttrPos")]
+    async fn builtin_unsafe_get_attr_pos(
+        co: GenCo,
+        _name: Value,
+        _attrset: Value,
+    ) -> Result<Value, ErrorKind> {
+        generators::emit_warning_kind(
+            &co,
+            WarningKind::NotImplemented("builtins.unsafeGetAttrsPos"),
+        )
+        .await;
+        let res = [
+            ("line", 42.into()),
+            ("column", 42.into()),
+            ("file", Value::String("/deep/thought".into())),
+        ];
+        Ok(Value::attrs(NixAttrs::from_iter(res.into_iter())))
+    }
+}
+
+pub fn placeholders() -> Vec<(&'static str, Value)> {
+    placeholder_builtins::builtins()
+}
diff --git a/tvix/eval/src/builtins/to_xml.rs b/tvix/eval/src/builtins/to_xml.rs
new file mode 100644
index 0000000000..bb12cebfc9
--- /dev/null
+++ b/tvix/eval/src/builtins/to_xml.rs
@@ -0,0 +1,154 @@
+//! This module implements `builtins.toXML`, which is a serialisation
+//! of value information as well as internal tvix state that several
+//! things in nixpkgs rely on.
+
+use bstr::ByteSlice;
+use std::{io::Write, rc::Rc};
+use xml::writer::events::XmlEvent;
+use xml::writer::EmitterConfig;
+use xml::writer::EventWriter;
+
+use crate::{ErrorKind, Value};
+
+/// Recursively serialise a value to XML. The value *must* have been
+/// deep-forced before being passed to this function.
+pub fn value_to_xml<W: Write>(mut writer: W, value: &Value) -> Result<(), ErrorKind> {
+    let config = EmitterConfig {
+        perform_indent: true,
+        pad_self_closing: true,
+
+        // Nix uses single-quotes *only* in the document declaration,
+        // so we need to write it manually.
+        write_document_declaration: false,
+        ..Default::default()
+    };
+
+    // Write a literal document declaration, using C++-Nix-style
+    // single quotes.
+    writeln!(writer, "<?xml version='1.0' encoding='utf-8'?>")?;
+
+    let mut writer = EventWriter::new_with_config(writer, config);
+
+    writer.write(XmlEvent::start_element("expr"))?;
+    value_variant_to_xml(&mut writer, value)?;
+    writer.write(XmlEvent::end_element())?;
+
+    // Unwrap the writer to add the final newline that C++ Nix adds.
+    writeln!(writer.into_inner())?;
+
+    Ok(())
+}
+
+fn write_typed_value<W: Write, V: ToString>(
+    w: &mut EventWriter<W>,
+    name: &str,
+    value: V,
+) -> Result<(), ErrorKind> {
+    w.write(XmlEvent::start_element(name).attr("value", &value.to_string()))?;
+    w.write(XmlEvent::end_element())?;
+    Ok(())
+}
+
+fn value_variant_to_xml<W: Write>(w: &mut EventWriter<W>, value: &Value) -> Result<(), ErrorKind> {
+    match value {
+        Value::Thunk(t) => return value_variant_to_xml(w, &t.value()),
+
+        Value::Null => {
+            w.write(XmlEvent::start_element("null"))?;
+            w.write(XmlEvent::end_element())
+        }
+
+        Value::Bool(b) => return write_typed_value(w, "bool", b),
+        Value::Integer(i) => return write_typed_value(w, "int", i),
+        Value::Float(f) => return write_typed_value(w, "float", f),
+        Value::String(s) => return write_typed_value(w, "string", s.to_str()?),
+        Value::Path(p) => return write_typed_value(w, "path", p.to_string_lossy()),
+
+        Value::List(list) => {
+            w.write(XmlEvent::start_element("list"))?;
+
+            for elem in list.into_iter() {
+                value_variant_to_xml(w, elem)?;
+            }
+
+            w.write(XmlEvent::end_element())
+        }
+
+        Value::Attrs(attrs) => {
+            w.write(XmlEvent::start_element("attrs"))?;
+
+            for elem in attrs.iter() {
+                w.write(XmlEvent::start_element("attr").attr("name", &elem.0.to_str_lossy()))?;
+                value_variant_to_xml(w, elem.1)?;
+                w.write(XmlEvent::end_element())?;
+            }
+
+            w.write(XmlEvent::end_element())
+        }
+
+        Value::Closure(c) => {
+            w.write(XmlEvent::start_element("function"))?;
+
+            match &c.lambda.formals {
+                Some(formals) => {
+                    let mut attrspat = XmlEvent::start_element("attrspat");
+                    if formals.ellipsis {
+                        attrspat = attrspat.attr("ellipsis", "1");
+                    }
+                    if let Some(ref name) = &formals.name {
+                        attrspat = attrspat.attr("name", name.as_str());
+                    }
+
+                    w.write(attrspat)?;
+
+                    for arg in formals.arguments.iter() {
+                        w.write(
+                            XmlEvent::start_element("attr").attr("name", &arg.0.to_str_lossy()),
+                        )?;
+                        w.write(XmlEvent::end_element())?;
+                    }
+
+                    w.write(XmlEvent::end_element())?;
+                }
+                None => {
+                    // TODO(tazjin): tvix does not currently persist function
+                    // argument names anywhere (whereas we do for formals, as
+                    // that is required for other runtime behaviour). Because of
+                    // this the implementation here is fake, always returning
+                    // the same argument name.
+                    //
+                    // If we don't want to persist the data, we can re-parse the
+                    // AST from the spans of the lambda's bytecode and figure it
+                    // out that way, but it needs some investigating.
+                    w.write(XmlEvent::start_element("varpat").attr("name", /* fake: */ "x"))?;
+                    w.write(XmlEvent::end_element())?;
+                }
+            }
+
+            w.write(XmlEvent::end_element())
+        }
+
+        Value::Builtin(_) => {
+            w.write(XmlEvent::start_element("unevaluated"))?;
+            w.write(XmlEvent::end_element())
+        }
+
+        Value::AttrNotFound
+        | Value::Blueprint(_)
+        | Value::DeferredUpvalue(_)
+        | Value::UnresolvedPath(_)
+        | Value::Json(..)
+        | Value::FinaliseRequest(_) => {
+            return Err(ErrorKind::TvixBug {
+                msg: "internal value variant encountered in builtins.toXML",
+                metadata: Some(Rc::new(value.clone())),
+            })
+        }
+
+        Value::Catchable(_) => {
+            panic!("tvix bug: value_to_xml() called on a value which had not been deep-forced")
+        }
+    }?;
+
+    Ok(())
+}
diff --git a/tvix/eval/src/builtins/versions.rs b/tvix/eval/src/builtins/versions.rs
new file mode 100644
index 0000000000..6de5121424
--- /dev/null
+++ b/tvix/eval/src/builtins/versions.rs
@@ -0,0 +1,163 @@
+use std::cmp::Ordering;
+use std::iter::{once, Chain, Once};
+use std::ops::RangeInclusive;
+
+use bstr::{BStr, ByteSlice, B};
+
+/// Version strings can be broken up into Parts.
+/// One Part represents either a string of digits or characters.
+/// '.' and '_' represent deviders between parts and are not included in any part.
+#[derive(PartialEq, Eq, Clone, Debug)]
+pub enum VersionPart<'a> {
+    Word(&'a BStr),
+    Number(&'a BStr),
+}
+
+impl PartialOrd for VersionPart<'_> {
+    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+        Some(self.cmp(other))
+    }
+}
+
+impl Ord for VersionPart<'_> {
+    fn cmp(&self, other: &Self) -> Ordering {
+        match (self, other) {
+            (VersionPart::Number(s1), VersionPart::Number(s2)) => {
+                // Note: C++ Nix uses `int`, but probably doesn't make a difference
+                // We trust that the splitting was done correctly and parsing will work
+                let n1: u64 = s1.to_str_lossy().parse().unwrap();
+                let n2: u64 = s2.to_str_lossy().parse().unwrap();
+                n1.cmp(&n2)
+            }
+
+            // `pre` looses unless the other part is also a `pre`
+            (VersionPart::Word(x), VersionPart::Word(y)) if *x == B("pre") && *y == B("pre") => {
+                Ordering::Equal
+            }
+            (VersionPart::Word(x), _) if *x == B("pre") => Ordering::Less,
+            (_, VersionPart::Word(y)) if *y == B("pre") => Ordering::Greater,
+
+            // Number wins against Word
+            (VersionPart::Number(_), VersionPart::Word(_)) => Ordering::Greater,
+            (VersionPart::Word(_), VersionPart::Number(_)) => Ordering::Less,
+
+            (VersionPart::Word(w1), VersionPart::Word(w2)) => w1.cmp(w2),
+        }
+    }
+}
+
+/// Type used to hold information about a VersionPart during creation
+enum InternalPart {
+    Number { range: RangeInclusive<usize> },
+    Word { range: RangeInclusive<usize> },
+    Break,
+}
+
+/// An iterator which yields the parts of a version string.
+///
+/// This can then be directly used to compare two versions
+pub struct VersionPartsIter<'a> {
+    cached_part: InternalPart,
+    iter: bstr::CharIndices<'a>,
+    version: &'a BStr,
+}
+
+impl<'a> VersionPartsIter<'a> {
+    pub fn new(version: &'a BStr) -> Self {
+        Self {
+            cached_part: InternalPart::Break,
+            iter: version.char_indices(),
+            version,
+        }
+    }
+
+    /// Create an iterator that yields all version parts followed by an additional
+    /// `VersionPart::Word("")` part (i.e. you can think of this as
+    /// `builtins.splitVersion version ++ [ "" ]`). This is necessary, because
+    /// Nix's `compareVersions` is not entirely lexicographical: If we have two
+    /// equal versions, but one is longer, the longer one is only considered
+    /// greater if the first additional part of the longer version is not `pre`,
+    /// e.g. `2.3 > 2.3pre`. It is otherwise lexicographical, so peculiar behavior
+    /// like `2.3 < 2.3.0pre` ensues. Luckily for us, this means that we can
+    /// lexicographically compare two version strings, _if_ we append an extra
+    /// component to both versions.
+    pub fn new_for_cmp(version: &'a BStr) -> Chain<Self, Once<VersionPart>> {
+        Self::new(version).chain(once(VersionPart::Word("".into())))
+    }
+}
+
+impl<'a> Iterator for VersionPartsIter<'a> {
+    type Item = VersionPart<'a>;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        let char = self.iter.next();
+
+        if char.is_none() {
+            let cached_part = std::mem::replace(&mut self.cached_part, InternalPart::Break);
+            match cached_part {
+                InternalPart::Break => return None,
+                InternalPart::Number { range } => {
+                    return Some(VersionPart::Number(&self.version[range]))
+                }
+                InternalPart::Word { range } => {
+                    return Some(VersionPart::Word(&self.version[range]))
+                }
+            }
+        }
+
+        let (start, end, char) = char.unwrap();
+        match char {
+            // Divider encountered
+            '.' | '-' => {
+                let cached_part = std::mem::replace(&mut self.cached_part, InternalPart::Break);
+                match cached_part {
+                    InternalPart::Number { range } => {
+                        Some(VersionPart::Number(&self.version[range]))
+                    }
+                    InternalPart::Word { range } => Some(VersionPart::Word(&self.version[range])),
+                    InternalPart::Break => self.next(),
+                }
+            }
+
+            // digit encountered
+            _ if char.is_ascii_digit() => {
+                let cached_part = std::mem::replace(
+                    &mut self.cached_part,
+                    InternalPart::Number {
+                        range: start..=(end - 1),
+                    },
+                );
+                match cached_part {
+                    InternalPart::Number { range } => {
+                        self.cached_part = InternalPart::Number {
+                            range: *range.start()..=*range.end() + 1,
+                        };
+                        self.next()
+                    }
+                    InternalPart::Word { range } => Some(VersionPart::Word(&self.version[range])),
+                    InternalPart::Break => self.next(),
+                }
+            }
+
+            // char encountered
+            _ => {
+                let mut cached_part = InternalPart::Word {
+                    range: start..=(end - 1),
+                };
+                std::mem::swap(&mut cached_part, &mut self.cached_part);
+                match cached_part {
+                    InternalPart::Word { range } => {
+                        self.cached_part = InternalPart::Word {
+                            range: *range.start()..=*range.end() + char.len_utf8(),
+                        };
+                        self.next()
+                    }
+                    InternalPart::Number { range } => {
+                        Some(VersionPart::Number(&self.version[range]))
+                    }
+                    InternalPart::Break => self.next(),
+                }
+            }
+        }
+    }
+}
diff --git a/tvix/eval/src/chunk.rs b/tvix/eval/src/chunk.rs
index 3475e58f18..f1a35a6ce1 100644
--- a/tvix/eval/src/chunk.rs
+++ b/tvix/eval/src/chunk.rs
@@ -1,26 +1,289 @@
+use std::io::Write;
+use std::ops::{Index, IndexMut};
+
 use crate::opcode::{CodeIdx, ConstantIdx, OpCode};
 use crate::value::Value;
+use crate::SourceCode;
+
+/// Represents a source location from which one or more operations
+/// were compiled.
+///
+/// The span itself is an index into a [codemap::CodeMap], and the
+/// structure tracks the number of operations that were yielded from
+/// the same span.
+///
+/// At error reporting time, it becomes possible to either just fetch
+/// the textual representation of that span from the codemap, or to
+/// even re-parse the AST using rnix to create more semantically
+/// interesting errors.
+#[derive(Clone, Debug, PartialEq)]
+struct SourceSpan {
+    /// Span into the [codemap::CodeMap].
+    span: codemap::Span,
+
+    /// Index of the first operation covered by this span.
+    start: usize,
+}
 
+/// A chunk is a representation of a sequence of bytecode
+/// instructions, associated constants and additional metadata as
+/// emitted by the compiler.
 #[derive(Debug, Default)]
 pub struct Chunk {
     pub code: Vec<OpCode>,
-    constants: Vec<Value>,
+    pub constants: Vec<Value>,
+    spans: Vec<SourceSpan>,
+}
+
+impl Index<ConstantIdx> for Chunk {
+    type Output = Value;
+
+    fn index(&self, index: ConstantIdx) -> &Self::Output {
+        &self.constants[index.0]
+    }
+}
+
+impl Index<CodeIdx> for Chunk {
+    type Output = OpCode;
+
+    fn index(&self, index: CodeIdx) -> &Self::Output {
+        &self.code[index.0]
+    }
+}
+
+impl IndexMut<CodeIdx> for Chunk {
+    fn index_mut(&mut self, index: CodeIdx) -> &mut Self::Output {
+        &mut self.code[index.0]
+    }
 }
 
 impl Chunk {
-    pub fn add_op(&mut self, data: OpCode) -> CodeIdx {
+    pub fn push_op(&mut self, data: OpCode, span: codemap::Span) -> CodeIdx {
         let idx = self.code.len();
         self.code.push(data);
+        self.push_span(span, idx);
         CodeIdx(idx)
     }
 
-    pub fn add_constant(&mut self, data: Value) -> ConstantIdx {
+    /// Get the first span of a chunk, no questions asked.
+    pub fn first_span(&self) -> codemap::Span {
+        self.spans[0].span
+    }
+
+    /// Return a reference to the last op in the chunk, if any
+    pub fn last_op(&self) -> Option<&OpCode> {
+        self.code.last()
+    }
+
+    /// Pop the last operation from the chunk and clean up its tracked
+    /// span. Used when the compiler backtracks.
+    pub fn pop_op(&mut self) {
+        // Simply drop the last op.
+        self.code.pop();
+
+        if let Some(span) = self.spans.last() {
+            // If the last span started at this op, drop it.
+            if span.start == self.code.len() {
+                self.spans.pop();
+            }
+        }
+    }
+
+    pub fn push_constant(&mut self, data: Value) -> ConstantIdx {
         let idx = self.constants.len();
         self.constants.push(data);
         ConstantIdx(idx)
     }
 
-    pub fn constant(&self, idx: ConstantIdx) -> &Value {
-        &self.constants[idx.0]
+    /// Return a reference to the constant at the given [`ConstantIdx`]
+    pub fn get_constant(&self, constant: ConstantIdx) -> Option<&Value> {
+        self.constants.get(constant.0)
+    }
+
+    // Span tracking implementation
+
+    fn push_span(&mut self, span: codemap::Span, start: usize) {
+        match self.spans.last_mut() {
+            // We do not need to insert the same span again, as this
+            // instruction was compiled from the same span as the last
+            // one.
+            Some(last) if last.span == span => {}
+
+            // In all other cases, this is a new source span.
+            _ => self.spans.push(SourceSpan { span, start }),
+        }
+    }
+
+    /// Retrieve the [codemap::Span] from which the instruction at
+    /// `offset` was compiled.
+    pub fn get_span(&self, offset: CodeIdx) -> codemap::Span {
+        let position = self
+            .spans
+            .binary_search_by(|span| span.start.cmp(&offset.0));
+
+        let span = match position {
+            Ok(index) => &self.spans[index],
+            Err(index) => {
+                if index == 0 {
+                    &self.spans[0]
+                } else {
+                    &self.spans[index - 1]
+                }
+            }
+        };
+
+        span.span
+    }
+
+    /// Write the disassembler representation of the operation at
+    /// `idx` to the specified writer.
+    pub fn disassemble_op<W: Write>(
+        &self,
+        writer: &mut W,
+        source: &SourceCode,
+        width: usize,
+        idx: CodeIdx,
+    ) -> Result<(), std::io::Error> {
+        write!(writer, "{:#width$x}\t ", idx.0, width = width)?;
+
+        // Print continuation character if the previous operation was at
+        // the same line, otherwise print the line.
+        let line = source.get_line(self.get_span(idx));
+        if idx.0 > 0 && source.get_line(self.get_span(CodeIdx(idx.0 - 1))) == line {
+            write!(writer, "   |\t")?;
+        } else {
+            write!(writer, "{:4}\t", line)?;
+        }
+
+        match self[idx] {
+            OpCode::OpConstant(idx) => {
+                let val_str = match &self[idx] {
+                    Value::Thunk(t) => t.debug_repr(),
+                    Value::Closure(c) => format!("closure({:p})", c.lambda),
+                    val => format!("{}", val),
+                };
+
+                writeln!(writer, "OpConstant({}@{})", val_str, idx.0)
+            }
+            op => writeln!(writer, "{:?}", op),
+        }?;
+
+        Ok(())
+    }
+
+    /// Extend this chunk with the content of another, moving out of the other
+    /// in the process.
+    ///
+    /// This is used by the compiler when it detects that it unnecessarily
+    /// thunked a nested expression.
+    pub fn extend(&mut self, other: Self) {
+        // Some operations need to be modified in certain ways before being
+        // valid as part of the new chunk.
+        let const_count = self.constants.len();
+        for (idx, op) in other.code.iter().enumerate() {
+            let span = other.get_span(CodeIdx(idx));
+            match op {
+                // As the constants shift, the index needs to be moved relatively.
+                OpCode::OpConstant(ConstantIdx(idx)) => {
+                    self.push_op(OpCode::OpConstant(ConstantIdx(idx + const_count)), span)
+                }
+
+                // Other operations either operate on relative offsets, or no
+                // offsets, and are safe to keep as-is.
+                _ => self.push_op(*op, span),
+            };
+        }
+
+        self.constants.extend(other.constants);
+        self.spans.extend(other.spans);
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::test_utils::dummy_span;
+
+    // Note: These tests are about the functionality of the `Chunk` type, the
+    // opcodes used below do *not* represent valid, executable Tvix code (and
+    // don't need to).
+
+    #[test]
+    fn push_op() {
+        let mut chunk = Chunk::default();
+        chunk.push_op(OpCode::OpAdd, dummy_span());
+        assert_eq!(chunk.code.last().unwrap(), &OpCode::OpAdd);
+    }
+
+    #[test]
+    fn extend_empty() {
+        let mut chunk = Chunk::default();
+        chunk.push_op(OpCode::OpAdd, dummy_span());
+
+        let other = Chunk::default();
+        chunk.extend(other);
+
+        assert_eq!(
+            chunk.code,
+            vec![OpCode::OpAdd],
+            "code should not have changed"
+        );
+    }
+
+    #[test]
+    fn extend_simple() {
+        let span = dummy_span();
+        let mut chunk = Chunk::default();
+        chunk.push_op(OpCode::OpAdd, span);
+
+        let mut other = Chunk::default();
+        other.push_op(OpCode::OpSub, span);
+        other.push_op(OpCode::OpMul, span);
+
+        let expected_code = vec![OpCode::OpAdd, OpCode::OpSub, OpCode::OpMul];
+
+        chunk.extend(other);
+
+        assert_eq!(chunk.code, expected_code, "code should have been extended");
+    }
+
+    #[test]
+    fn extend_with_constant() {
+        let span = dummy_span();
+        let mut chunk = Chunk::default();
+        chunk.push_op(OpCode::OpAdd, span);
+        let cidx = chunk.push_constant(Value::Integer(0));
+        assert_eq!(
+            cidx.0, 0,
+            "first constant in main chunk should have index 0"
+        );
+        chunk.push_op(OpCode::OpConstant(cidx), span);
+
+        let mut other = Chunk::default();
+        other.push_op(OpCode::OpSub, span);
+        let other_cidx = other.push_constant(Value::Integer(1));
+        assert_eq!(
+            other_cidx.0, 0,
+            "first constant in other chunk should have index 0"
+        );
+        other.push_op(OpCode::OpConstant(other_cidx), span);
+
+        chunk.extend(other);
+
+        let expected_code = vec![
+            OpCode::OpAdd,
+            OpCode::OpConstant(ConstantIdx(0)),
+            OpCode::OpSub,
+            OpCode::OpConstant(ConstantIdx(1)), // <- note: this was rewritten
+        ];
+
+        assert_eq!(
+            chunk.code, expected_code,
+            "code should have been extended and rewritten"
+        );
+
+        assert_eq!(chunk.constants.len(), 2);
+        assert!(matches!(chunk.constants[0], Value::Integer(0)));
+        assert!(matches!(chunk.constants[1], Value::Integer(1)));
     }
 }
diff --git a/tvix/eval/src/compiler.rs b/tvix/eval/src/compiler.rs
deleted file mode 100644
index 5b6f748dc7..0000000000
--- a/tvix/eval/src/compiler.rs
+++ /dev/null
@@ -1,267 +0,0 @@
-//! This module implements a compiler for compiling the rnix AST
-//! representation to Tvix bytecode.
-
-use crate::chunk::Chunk;
-use crate::errors::EvalResult;
-use crate::opcode::OpCode;
-use crate::value::{NixString, Value};
-
-use rnix;
-use rnix::types::{EntryHolder, TokenWrapper, TypedNode, Wrapper};
-
-struct Compiler {
-    chunk: Chunk,
-}
-
-impl Compiler {
-    fn compile(&mut self, node: rnix::SyntaxNode) -> EvalResult<()> {
-        match node.kind() {
-            // Root of a file contains no content, it's just a marker
-            // type.
-            rnix::SyntaxKind::NODE_ROOT => self.compile(node.first_child().expect("TODO")),
-
-            // Literals contain a single token comprising of the
-            // literal itself.
-            rnix::SyntaxKind::NODE_LITERAL => {
-                let value = rnix::types::Value::cast(node).unwrap();
-                self.compile_literal(value.to_value().expect("TODO"))
-            }
-
-            rnix::SyntaxKind::NODE_STRING => {
-                let op = rnix::types::Str::cast(node).unwrap();
-                self.compile_string(op)
-            }
-
-            // The interpolation node is just a wrapper around the
-            // inner value of a fragment, it only requires unwrapping.
-            rnix::SyntaxKind::NODE_STRING_INTERPOL => {
-                self.compile(node.first_child().expect("TODO (should not be possible)"))
-            }
-
-            rnix::SyntaxKind::NODE_BIN_OP => {
-                let op = rnix::types::BinOp::cast(node).expect("TODO (should not be possible)");
-                self.compile_binop(op)
-            }
-
-            rnix::SyntaxKind::NODE_UNARY_OP => {
-                let op = rnix::types::UnaryOp::cast(node).expect("TODO: (should not be possible)");
-                self.compile_unary_op(op)
-            }
-
-            rnix::SyntaxKind::NODE_PAREN => {
-                let node = rnix::types::Paren::cast(node).unwrap();
-                self.compile(node.inner().unwrap())
-            }
-
-            rnix::SyntaxKind::NODE_IDENT => {
-                let node = rnix::types::Ident::cast(node).unwrap();
-                self.compile_ident(node)
-            }
-
-            rnix::SyntaxKind::NODE_ATTR_SET => {
-                let node = rnix::types::AttrSet::cast(node).unwrap();
-                self.compile_attr_set(node)
-            }
-
-            rnix::SyntaxKind::NODE_LIST => {
-                let node = rnix::types::List::cast(node).unwrap();
-                self.compile_list(node)
-            }
-
-            kind => {
-                println!("visiting unsupported node: {:?}", kind);
-                Ok(())
-            }
-        }
-    }
-
-    fn compile_literal(&mut self, value: rnix::value::Value) -> EvalResult<()> {
-        match value {
-            rnix::NixValue::Float(f) => {
-                let idx = self.chunk.add_constant(Value::Float(f));
-                self.chunk.add_op(OpCode::OpConstant(idx));
-                Ok(())
-            }
-
-            rnix::NixValue::Integer(i) => {
-                let idx = self.chunk.add_constant(Value::Integer(i));
-                self.chunk.add_op(OpCode::OpConstant(idx));
-                Ok(())
-            }
-
-            rnix::NixValue::String(_) => todo!(),
-            rnix::NixValue::Path(_, _) => todo!(),
-        }
-    }
-
-    fn compile_string(&mut self, string: rnix::types::Str) -> EvalResult<()> {
-        let mut count = 0;
-
-        // The string parts are produced in literal order, however
-        // they need to be reversed on the stack in order to
-        // efficiently create the real string in case of
-        // interpolation.
-        for part in string.parts().into_iter().rev() {
-            count += 1;
-
-            match part {
-                // Interpolated expressions are compiled as normal and
-                // dealt with by the VM before being assembled into
-                // the final string.
-                rnix::StrPart::Ast(node) => self.compile(node)?,
-
-                rnix::StrPart::Literal(lit) => {
-                    let idx = self.chunk.add_constant(Value::String(NixString(lit)));
-                    self.chunk.add_op(OpCode::OpConstant(idx));
-                }
-            }
-        }
-
-        if count != 1 {
-            self.chunk.add_op(OpCode::OpInterpolate(count));
-        }
-
-        Ok(())
-    }
-
-    fn compile_binop(&mut self, op: rnix::types::BinOp) -> EvalResult<()> {
-        self.compile(op.lhs().unwrap())?;
-        self.compile(op.rhs().unwrap())?;
-
-        use rnix::types::BinOpKind;
-
-        let opcode = match op.operator().unwrap() {
-            BinOpKind::Add => OpCode::OpAdd,
-            BinOpKind::Sub => OpCode::OpSub,
-            BinOpKind::Mul => OpCode::OpMul,
-            BinOpKind::Div => OpCode::OpDiv,
-            BinOpKind::Equal => OpCode::OpEqual,
-            _ => todo!(),
-        };
-
-        self.chunk.add_op(opcode);
-        Ok(())
-    }
-
-    fn compile_unary_op(&mut self, op: rnix::types::UnaryOp) -> EvalResult<()> {
-        self.compile(op.value().unwrap())?;
-
-        use rnix::types::UnaryOpKind;
-        let opcode = match op.operator() {
-            UnaryOpKind::Invert => OpCode::OpInvert,
-            UnaryOpKind::Negate => OpCode::OpNegate,
-        };
-
-        self.chunk.add_op(opcode);
-        Ok(())
-    }
-
-    fn compile_ident(&mut self, node: rnix::types::Ident) -> EvalResult<()> {
-        match node.as_str() {
-            // TODO(tazjin): Nix technically allows code like
-            //
-            //   let null = 1; in null
-            //   => 1
-            //
-            // which we do *not* want to check at runtime. Once
-            // scoping is introduced, the compiler should carry some
-            // optimised information about any "weird" stuff that's
-            // happened to the scope (such as overrides of these
-            // literals, or builtins).
-            "true" => self.chunk.add_op(OpCode::OpTrue),
-            "false" => self.chunk.add_op(OpCode::OpFalse),
-            "null" => self.chunk.add_op(OpCode::OpNull),
-
-            _ => todo!("identifier access"),
-        };
-
-        Ok(())
-    }
-
-    // Compile attribute set literals into equivalent bytecode.
-    //
-    // This is complicated by a number of features specific to Nix
-    // attribute sets, most importantly:
-    //
-    // 1. Keys can be dynamically constructed through interpolation.
-    // 2. Keys can refer to nested attribute sets.
-    // 3. Attribute sets can (optionally) be recursive.
-    fn compile_attr_set(&mut self, node: rnix::types::AttrSet) -> EvalResult<()> {
-        let mut count = 0;
-
-        for kv in node.entries() {
-            count += 1;
-
-            // Because attribute set literals can contain nested keys,
-            // there is potentially more than one key fragment. If
-            // this is the case, a special operation to construct a
-            // runtime value representing the attribute path is
-            // emitted.
-            let mut key_count = 0;
-            for fragment in kv.key().unwrap().path() {
-                key_count += 1;
-
-                match fragment.kind() {
-                    rnix::SyntaxKind::NODE_IDENT => {
-                        let ident = rnix::types::Ident::cast(fragment).unwrap();
-
-                        // TODO(tazjin): intern!
-                        let idx = self
-                            .chunk
-                            .add_constant(Value::String(NixString(ident.as_str().to_string())));
-                        self.chunk.add_op(OpCode::OpConstant(idx));
-                    }
-
-                    // For all other expression types, we simply
-                    // compile them as normal. The operation should
-                    // result in a string value, which is checked at
-                    // runtime on construction.
-                    _ => self.compile(fragment)?,
-                }
-            }
-
-            // We're done with the key if there was only one fragment,
-            // otherwise we need to emit an instruction to construct
-            // the attribute path.
-            if key_count > 1 {
-                self.chunk.add_op(OpCode::OpAttrPath(2));
-            }
-
-            // The value is just compiled as normal so that its
-            // resulting value is on the stack when the attribute set
-            // is constructed at runtime.
-            self.compile(kv.value().unwrap())?;
-        }
-
-        self.chunk.add_op(OpCode::OpAttrs(count));
-        Ok(())
-    }
-
-    // Compile list literals into equivalent bytecode. List
-    // construction is fairly simple, composing of pushing code for
-    // each literal element and an instruction with the element count.
-    //
-    // The VM, after evaluating the code for each element, simply
-    // constructs the list from the given number of elements.
-    fn compile_list(&mut self, node: rnix::types::List) -> EvalResult<()> {
-        let mut count = 0;
-
-        for item in node.items() {
-            count += 1;
-            self.compile(item)?;
-        }
-
-        self.chunk.add_op(OpCode::OpList(count));
-        Ok(())
-    }
-}
-
-pub fn compile(ast: rnix::AST) -> EvalResult<Chunk> {
-    let mut c = Compiler {
-        chunk: Chunk::default(),
-    };
-
-    c.compile(ast.node())?;
-
-    Ok(c.chunk)
-}
diff --git a/tvix/eval/src/compiler/bindings.rs b/tvix/eval/src/compiler/bindings.rs
new file mode 100644
index 0000000000..634cc54022
--- /dev/null
+++ b/tvix/eval/src/compiler/bindings.rs
@@ -0,0 +1,826 @@
+//! This module implements compiler logic related to name/value binding
+//! definitions (that is, attribute sets and let-expressions).
+//!
+//! In the case of recursive scopes these cases share almost all of their
+//! (fairly complex) logic.
+
+use std::iter::Peekable;
+
+use rnix::ast::HasEntry;
+use rowan::ast::AstChildren;
+
+use super::*;
+
+type PeekableAttrs = Peekable<AstChildren<ast::Attr>>;
+
+/// What kind of bindings scope is being compiled?
+#[derive(Clone, Copy, PartialEq)]
+enum BindingsKind {
+    /// Standard `let ... in ...`-expression.
+    LetIn,
+
+    /// Non-recursive attribute set.
+    Attrs,
+
+    /// Recursive attribute set.
+    RecAttrs,
+}
+
+impl BindingsKind {
+    fn is_attrs(&self) -> bool {
+        matches!(self, BindingsKind::Attrs | BindingsKind::RecAttrs)
+    }
+}
+
+// Internal representation of an attribute set used for merging sets, or
+// inserting nested keys.
+#[derive(Clone)]
+struct AttributeSet {
+    /// Original span at which this set was first encountered.
+    span: Span,
+
+    /// Tracks the kind of set (rec or not).
+    kind: BindingsKind,
+
+    /// All inherited entries
+    inherits: Vec<ast::Inherit>,
+
+    /// All internal entries
+    entries: Vec<(Span, PeekableAttrs, ast::Expr)>,
+}
+
+impl ToSpan for AttributeSet {
+    fn span_for(&self, _: &codemap::File) -> Span {
+        self.span
+    }
+}
+
+impl AttributeSet {
+    fn from_ast(c: &Compiler, node: &ast::AttrSet) -> Self {
+        AttributeSet {
+            span: c.span_for(node),
+
+            // Kind of the attrs depends on the first time it is
+            // encountered. We actually believe this to be a Nix
+            // bug: https://github.com/NixOS/nix/issues/7111
+            kind: if node.rec_token().is_some() {
+                BindingsKind::RecAttrs
+            } else {
+                BindingsKind::Attrs
+            },
+
+            inherits: ast::HasEntry::inherits(node).collect(),
+
+            entries: ast::HasEntry::attrpath_values(node)
+                .map(|entry| {
+                    let span = c.span_for(&entry);
+                    (
+                        span,
+                        entry.attrpath().unwrap().attrs().peekable(),
+                        entry.value().unwrap(),
+                    )
+                })
+                .collect(),
+        }
+    }
+}
+
+// Data structures to track the bindings observed in the second pass, and
+// forward the information needed to compile their value.
+enum Binding {
+    InheritFrom {
+        namespace: ast::Expr,
+        name: SmolStr,
+        span: Span,
+    },
+
+    Plain {
+        expr: ast::Expr,
+    },
+
+    Set(AttributeSet),
+}
+
+impl Binding {
+    /// Merge the provided value into the current binding, or emit an
+    /// error if this turns out to be impossible.
+    fn merge(
+        &mut self,
+        c: &mut Compiler,
+        span: Span,
+        mut remaining_path: PeekableAttrs,
+        value: ast::Expr,
+    ) {
+        match self {
+            Binding::InheritFrom { name, ref span, .. } => {
+                c.emit_error(span, ErrorKind::UnmergeableInherit { name: name.clone() })
+            }
+
+            // If the value is not yet a nested binding, flip the representation
+            // and recurse.
+            Binding::Plain { expr } => match expr {
+                ast::Expr::AttrSet(existing) => {
+                    let nested = AttributeSet::from_ast(c, existing);
+                    *self = Binding::Set(nested);
+                    self.merge(c, span, remaining_path, value);
+                }
+
+                _ => c.emit_error(&value, ErrorKind::UnmergeableValue),
+            },
+
+            // If the value is nested further, it is simply inserted into the
+            // bindings with its full path and resolved recursively further
+            // down.
+            Binding::Set(existing) if remaining_path.peek().is_some() => {
+                existing.entries.push((span, remaining_path, value))
+            }
+
+            Binding::Set(existing) => {
+                if let ast::Expr::AttrSet(new) = value {
+                    existing.inherits.extend(ast::HasEntry::inherits(&new));
+                    existing
+                        .entries
+                        .extend(ast::HasEntry::attrpath_values(&new).map(|entry| {
+                            let span = c.span_for(&entry);
+                            (
+                                span,
+                                entry.attrpath().unwrap().attrs().peekable(),
+                                entry.value().unwrap(),
+                            )
+                        }));
+                } else {
+                    // This branch is unreachable because in cases where the
+                    // path is empty (i.e. there is no further nesting), the
+                    // previous try_merge function already verified that the
+                    // expression is an attribute set.
+
+                    // TODO(tazjin): Consider making this branch live by
+                    // shuffling that check around and emitting a static error
+                    // here instead of a runtime error.
+                    unreachable!()
+                }
+            }
+        }
+    }
+}
+
+enum KeySlot {
+    /// There is no key slot (`let`-expressions do not emit their key).
+    None { name: SmolStr },
+
+    /// The key is statically known and has a slot.
+    Static { slot: LocalIdx, name: SmolStr },
+
+    /// The key is dynamic, i.e. only known at runtime, and must be compiled
+    /// into its slot.
+    Dynamic { slot: LocalIdx, attr: ast::Attr },
+}
+
+struct TrackedBinding {
+    key_slot: KeySlot,
+    value_slot: LocalIdx,
+    binding: Binding,
+}
+
+impl TrackedBinding {
+    /// Does this binding match the given key?
+    ///
+    /// Used to determine which binding to merge another one into.
+    fn matches(&self, key: &str) -> bool {
+        match &self.key_slot {
+            KeySlot::None { name } => name == key,
+            KeySlot::Static { name, .. } => name == key,
+            KeySlot::Dynamic { .. } => false,
+        }
+    }
+}
+
+struct TrackedBindings {
+    bindings: Vec<TrackedBinding>,
+}
+
+impl TrackedBindings {
+    fn new() -> Self {
+        TrackedBindings { bindings: vec![] }
+    }
+
+    /// Attempt to merge an entry into an existing matching binding, assuming
+    /// that the provided binding is mergable (i.e. either a nested key or an
+    /// attribute set literal).
+    ///
+    /// Returns true if the binding was merged, false if it needs to be compiled
+    /// separately as a new binding.
+    fn try_merge(
+        &mut self,
+        c: &mut Compiler,
+        span: Span,
+        name: &ast::Attr,
+        mut remaining_path: PeekableAttrs,
+        value: ast::Expr,
+    ) -> bool {
+        // If the path has no more entries, and if the entry is not an
+        // attribute set literal, the entry can not be merged.
+        if remaining_path.peek().is_none() && !matches!(value, ast::Expr::AttrSet(_)) {
+            return false;
+        }
+
+        // If the first element of the path is not statically known, the entry
+        // can not be merged.
+        let name = match expr_static_attr_str(name) {
+            Some(name) => name,
+            None => return false,
+        };
+
+        // If there is no existing binding with this key, the entry can not be
+        // merged.
+        // TODO: benchmark whether using a map or something is useful over the
+        // `find` here
+        let binding = match self.bindings.iter_mut().find(|b| b.matches(&name)) {
+            Some(b) => b,
+            None => return false,
+        };
+
+        // No more excuses ... the binding can be merged!
+        binding.binding.merge(c, span, remaining_path, value);
+
+        true
+    }
+
+    /// Add a completely new binding to the tracked bindings.
+    fn track_new(&mut self, key_slot: KeySlot, value_slot: LocalIdx, binding: Binding) {
+        self.bindings.push(TrackedBinding {
+            key_slot,
+            value_slot,
+            binding,
+        });
+    }
+}
+
+/// Wrapper around the `ast::HasEntry` trait as that trait can not be
+/// implemented for custom types.
+trait HasEntryProxy {
+    fn inherits(&self) -> Box<dyn Iterator<Item = ast::Inherit>>;
+
+    fn attributes<'a>(
+        &self,
+        file: &'a codemap::File,
+    ) -> Box<dyn Iterator<Item = (Span, PeekableAttrs, ast::Expr)> + 'a>;
+}
+
+impl<N: HasEntry> HasEntryProxy for N {
+    fn inherits(&self) -> Box<dyn Iterator<Item = ast::Inherit>> {
+        Box::new(ast::HasEntry::inherits(self))
+    }
+
+    fn attributes<'a>(
+        &self,
+        file: &'a codemap::File,
+    ) -> Box<dyn Iterator<Item = (Span, PeekableAttrs, ast::Expr)> + 'a> {
+        Box::new(ast::HasEntry::attrpath_values(self).map(move |entry| {
+            (
+                entry.span_for(file),
+                entry.attrpath().unwrap().attrs().peekable(),
+                entry.value().unwrap(),
+            )
+        }))
+    }
+}
+
+impl HasEntryProxy for AttributeSet {
+    fn inherits(&self) -> Box<dyn Iterator<Item = ast::Inherit>> {
+        Box::new(self.inherits.clone().into_iter())
+    }
+
+    fn attributes<'a>(
+        &self,
+        _: &'a codemap::File,
+    ) -> Box<dyn Iterator<Item = (Span, PeekableAttrs, ast::Expr)> + 'a> {
+        Box::new(self.entries.clone().into_iter())
+    }
+}
+
+/// AST-traversing functions related to bindings.
+impl Compiler<'_, '_> {
+    /// Compile all inherits of a node with entries that do *not* have a
+    /// namespace to inherit from, and return the remaining ones that do.
+    fn compile_plain_inherits<N>(
+        &mut self,
+        slot: LocalIdx,
+        kind: BindingsKind,
+        count: &mut usize,
+        node: &N,
+    ) -> Vec<(ast::Expr, SmolStr, Span)>
+    where
+        N: ToSpan + HasEntryProxy,
+    {
+        // Pass over all inherits, resolving only those without namespaces.
+        // Since they always resolve in a higher scope, we can just compile and
+        // declare them immediately.
+        //
+        // Inherits with namespaces are returned to the caller.
+        let mut inherit_froms: Vec<(ast::Expr, SmolStr, Span)> = vec![];
+
+        for inherit in node.inherits() {
+            if inherit.attrs().peekable().peek().is_none() {
+                self.emit_warning(&inherit, WarningKind::EmptyInherit);
+                continue;
+            }
+
+            match inherit.from() {
+                // Within a `let` binding, inheriting from the outer scope is a
+                // no-op *if* there are no dynamic bindings.
+                None if !kind.is_attrs() && !self.has_dynamic_ancestor() => {
+                    self.emit_warning(&inherit, WarningKind::UselessInherit);
+                    continue;
+                }
+
+                None => {
+                    for attr in inherit.attrs() {
+                        let name = match expr_static_attr_str(&attr) {
+                            Some(name) => name,
+                            None => {
+                                self.emit_error(&attr, ErrorKind::DynamicKeyInScope("inherit"));
+                                continue;
+                            }
+                        };
+
+                        // If the identifier resolves statically in a `let`, it
+                        // has precedence over dynamic bindings, and the inherit
+                        // is useless.
+                        if kind == BindingsKind::LetIn
+                            && matches!(
+                                self.scope_mut().resolve_local(&name),
+                                LocalPosition::Known(_)
+                            )
+                        {
+                            self.emit_warning(&attr, WarningKind::UselessInherit);
+                            continue;
+                        }
+
+                        *count += 1;
+
+                        // Place key on the stack when compiling attribute sets.
+                        if kind.is_attrs() {
+                            self.emit_constant(name.as_str().into(), &attr);
+                            let span = self.span_for(&attr);
+                            self.scope_mut().declare_phantom(span, true);
+                        }
+
+                        // Place the value on the stack. Note that because plain
+                        // inherits are always in the outer scope, the slot of
+                        // *this* scope itself is used.
+                        self.compile_identifier_access(slot, &name, &attr);
+
+                        // In non-recursive attribute sets, the key slot must be
+                        // a phantom (i.e. the identifier can not be resolved in
+                        // this scope).
+                        let idx = if kind == BindingsKind::Attrs {
+                            let span = self.span_for(&attr);
+                            self.scope_mut().declare_phantom(span, false)
+                        } else {
+                            self.declare_local(&attr, name)
+                        };
+
+                        self.scope_mut().mark_initialised(idx);
+                    }
+                }
+
+                Some(from) => {
+                    for attr in inherit.attrs() {
+                        let name = match expr_static_attr_str(&attr) {
+                            Some(name) => name,
+                            None => {
+                                self.emit_error(&attr, ErrorKind::DynamicKeyInScope("inherit"));
+                                continue;
+                            }
+                        };
+
+                        *count += 1;
+                        inherit_froms.push((from.expr().unwrap(), name, self.span_for(&attr)));
+                    }
+                }
+            }
+        }
+
+        inherit_froms
+    }
+
+    /// Declare all namespaced inherits, that is inherits which are inheriting
+    /// values from an attribute set.
+    ///
+    /// This only ensures that the locals stack is aware of the inherits, it
+    /// does not yet emit bytecode that places them on the stack. This is up to
+    /// the owner of the `bindings` vector, which this function will populate.
+    fn declare_namespaced_inherits(
+        &mut self,
+        kind: BindingsKind,
+        inherit_froms: Vec<(ast::Expr, SmolStr, Span)>,
+        bindings: &mut TrackedBindings,
+    ) {
+        for (from, name, span) in inherit_froms {
+            let key_slot = if kind.is_attrs() {
+                // In an attribute set, the keys themselves are placed on the
+                // stack but their stack slot is inaccessible (it is only
+                // consumed by `OpAttrs`).
+                KeySlot::Static {
+                    slot: self.scope_mut().declare_phantom(span, false),
+                    name: name.clone(),
+                }
+            } else {
+                KeySlot::None { name: name.clone() }
+            };
+
+            let value_slot = match kind {
+                // In recursive scopes, the value needs to be accessible on the
+                // stack.
+                BindingsKind::LetIn | BindingsKind::RecAttrs => {
+                    self.declare_local(&span, name.clone())
+                }
+
+                // In non-recursive attribute sets, the value is inaccessible
+                // (only consumed by `OpAttrs`).
+                BindingsKind::Attrs => self.scope_mut().declare_phantom(span, false),
+            };
+
+            bindings.track_new(
+                key_slot,
+                value_slot,
+                Binding::InheritFrom {
+                    namespace: from,
+                    name,
+                    span,
+                },
+            );
+        }
+    }
+
+    /// Declare all regular bindings (i.e. `key = value;`) in a bindings scope,
+    /// but do not yet compile their values.
+    fn declare_bindings<N>(
+        &mut self,
+        kind: BindingsKind,
+        count: &mut usize,
+        bindings: &mut TrackedBindings,
+        node: &N,
+    ) where
+        N: ToSpan + HasEntryProxy,
+    {
+        for (span, mut path, value) in node.attributes(self.file) {
+            let key = path.next().unwrap();
+
+            if bindings.try_merge(self, span, &key, path.clone(), value.clone()) {
+                // Binding is nested, or already exists and was merged, move on.
+                continue;
+            }
+
+            *count += 1;
+
+            let key_span = self.span_for(&key);
+            let key_slot = match expr_static_attr_str(&key) {
+                Some(name) if kind.is_attrs() => KeySlot::Static {
+                    name,
+                    slot: self.scope_mut().declare_phantom(key_span, false),
+                },
+
+                Some(name) => KeySlot::None { name },
+
+                None if kind.is_attrs() => KeySlot::Dynamic {
+                    attr: key,
+                    slot: self.scope_mut().declare_phantom(key_span, false),
+                },
+
+                None => {
+                    self.emit_error(&key, ErrorKind::DynamicKeyInScope("let-expression"));
+                    continue;
+                }
+            };
+
+            let value_slot = match kind {
+                BindingsKind::LetIn | BindingsKind::RecAttrs => match &key_slot {
+                    // In recursive scopes, the value needs to be accessible on the
+                    // stack if it is statically known
+                    KeySlot::None { name } | KeySlot::Static { name, .. } => {
+                        self.declare_local(&key_span, name.as_str())
+                    }
+
+                    // Dynamic values are never resolvable (as their names are
+                    // of course only known at runtime).
+                    //
+                    // Note: This branch is unreachable in `let`-expressions.
+                    KeySlot::Dynamic { .. } => self.scope_mut().declare_phantom(key_span, false),
+                },
+
+                // In non-recursive attribute sets, the value is inaccessible
+                // (only consumed by `OpAttrs`).
+                BindingsKind::Attrs => self.scope_mut().declare_phantom(key_span, false),
+            };
+
+            let binding = if path.peek().is_some() {
+                Binding::Set(AttributeSet {
+                    span,
+                    kind: BindingsKind::Attrs,
+                    inherits: vec![],
+                    entries: vec![(span, path, value)],
+                })
+            } else {
+                Binding::Plain { expr: value }
+            };
+
+            bindings.track_new(key_slot, value_slot, binding);
+        }
+    }
+
+    /// Compile attribute set literals into equivalent bytecode.
+    ///
+    /// This is complicated by a number of features specific to Nix attribute
+    /// sets, most importantly:
+    ///
+    /// 1. Keys can be dynamically constructed through interpolation.
+    /// 2. Keys can refer to nested attribute sets.
+    /// 3. Attribute sets can (optionally) be recursive.
+    pub(super) fn compile_attr_set(&mut self, slot: LocalIdx, node: &ast::AttrSet) {
+        // Open a scope to track the positions of the temporaries used by the
+        // `OpAttrs` instruction.
+        self.scope_mut().begin_scope();
+
+        let kind = if node.rec_token().is_some() {
+            BindingsKind::RecAttrs
+        } else {
+            BindingsKind::Attrs
+        };
+
+        self.compile_bindings(slot, kind, node);
+
+        // Remove the temporary scope, but do not emit any additional cleanup
+        // (OpAttrs consumes all of these locals).
+        self.scope_mut().end_scope();
+    }
+
+    /// Actually binds all tracked bindings by emitting the bytecode that places
+    /// them in their stack slots.
+    fn bind_values(&mut self, bindings: TrackedBindings) {
+        let mut value_indices: Vec<LocalIdx> = vec![];
+
+        for binding in bindings.bindings.into_iter() {
+            value_indices.push(binding.value_slot);
+
+            match binding.key_slot {
+                KeySlot::None { .. } => {} // nothing to do here
+
+                KeySlot::Static { slot, name } => {
+                    let span = self.scope()[slot].span;
+                    self.emit_constant(name.as_str().into(), &span);
+                    self.scope_mut().mark_initialised(slot);
+                }
+
+                KeySlot::Dynamic { slot, attr } => {
+                    self.compile_attr(slot, &attr);
+                    self.scope_mut().mark_initialised(slot);
+                }
+            }
+
+            match binding.binding {
+                // This entry is an inherit (from) expr. The value is placed on
+                // the stack by selecting an attribute.
+                Binding::InheritFrom {
+                    namespace,
+                    name,
+                    span,
+                } => {
+                    // Create a thunk wrapping value (which may be one as well)
+                    // to avoid forcing the from expr too early.
+                    self.thunk(binding.value_slot, &namespace, |c, s| {
+                        c.compile(s, namespace.clone());
+                        c.emit_force(&namespace);
+
+                        c.emit_constant(name.as_str().into(), &span);
+                        c.push_op(OpCode::OpAttrsSelect, &span);
+                    })
+                }
+
+                // Binding is "just" a plain expression that needs to be
+                // compiled.
+                Binding::Plain { expr } => self.compile(binding.value_slot, expr),
+
+                // Binding is a merged or nested attribute set, and needs to be
+                // recursively compiled as another binding.
+                Binding::Set(set) => self.thunk(binding.value_slot, &set, |c, _| {
+                    c.scope_mut().begin_scope();
+                    c.compile_bindings(binding.value_slot, set.kind, &set);
+                    c.scope_mut().end_scope();
+                }),
+            }
+
+            // Any code after this point will observe the value in the right
+            // stack slot, so mark it as initialised.
+            self.scope_mut().mark_initialised(binding.value_slot);
+        }
+
+        // Final pass to emit finaliser instructions if necessary.
+        for idx in value_indices {
+            if self.scope()[idx].needs_finaliser {
+                let stack_idx = self.scope().stack_index(idx);
+                let span = self.scope()[idx].span;
+                self.push_op(OpCode::OpFinalise(stack_idx), &span);
+            }
+        }
+    }
+
+    fn compile_bindings<N>(&mut self, slot: LocalIdx, kind: BindingsKind, node: &N)
+    where
+        N: ToSpan + HasEntryProxy,
+    {
+        let mut count = 0;
+        self.scope_mut().begin_scope();
+
+        // Vector to track all observed bindings.
+        let mut bindings = TrackedBindings::new();
+
+        let inherit_froms = self.compile_plain_inherits(slot, kind, &mut count, node);
+        self.declare_namespaced_inherits(kind, inherit_froms, &mut bindings);
+        self.declare_bindings(kind, &mut count, &mut bindings, node);
+
+        // Check if we can bail out on empty bindings
+        if count == 0 {
+            // still need an attrset to exist, but it is empty.
+            if kind.is_attrs() {
+                self.emit_constant(Value::Attrs(Box::new(NixAttrs::empty())), node);
+                return;
+            }
+
+            self.emit_warning(node, WarningKind::EmptyLet);
+            return;
+        }
+
+        // Actually bind values and ensure they are on the stack.
+        self.bind_values(bindings);
+
+        if kind.is_attrs() {
+            self.push_op(OpCode::OpAttrs(Count(count)), node);
+        }
+
+        if count == 0 {
+            self.unthunk();
+        }
+    }
+
+    /// Compile a standard `let ...; in ...` expression.
+    ///
+    /// Unless in a non-standard scope, the encountered values are simply pushed
+    /// on the stack and their indices noted in the entries vector.
+    pub(super) fn compile_let_in(&mut self, slot: LocalIdx, node: &ast::LetIn) {
+        self.compile_bindings(slot, BindingsKind::LetIn, node);
+
+        // Deal with the body, then clean up the locals afterwards.
+        self.compile(slot, node.body().unwrap());
+        self.cleanup_scope(node);
+    }
+
+    pub(super) fn compile_legacy_let(&mut self, slot: LocalIdx, node: &ast::LegacyLet) {
+        self.emit_warning(node, WarningKind::DeprecatedLegacyLet);
+        self.scope_mut().begin_scope();
+        self.compile_bindings(slot, BindingsKind::RecAttrs, node);
+
+        // Remove the temporary scope, but do not emit any additional cleanup
+        // (OpAttrs consumes all of these locals).
+        self.scope_mut().end_scope();
+
+        self.emit_constant("body".into(), node);
+        self.push_op(OpCode::OpAttrsSelect, node);
+    }
+
+    /// Is the given identifier defined *by the user* in any current scope?
+    pub(super) fn is_user_defined(&mut self, ident: &str) -> bool {
+        matches!(
+            self.scope_mut().resolve_local(ident),
+            LocalPosition::Known(_) | LocalPosition::Recursive(_)
+        )
+    }
+
+    /// Resolve and compile access to an identifier in the scope.
+    fn compile_identifier_access<N: ToSpan + Clone>(
+        &mut self,
+        slot: LocalIdx,
+        ident: &str,
+        node: &N,
+    ) {
+        match self.scope_mut().resolve_local(ident) {
+            LocalPosition::Unknown => {
+                // Are we possibly dealing with an upvalue?
+                if let Some(idx) = self.resolve_upvalue(self.contexts.len() - 1, ident, node) {
+                    self.push_op(OpCode::OpGetUpvalue(idx), node);
+                    return;
+                }
+
+                // Globals are the "upmost upvalues": they behave
+                // exactly like a `let ... in` prepended to the
+                // program's text, and the global scope is nothing
+                // more than the parent scope of the root scope.
+                if let Some(global) = self.globals.get(ident) {
+                    self.emit_constant(global.clone(), &self.span_for(node));
+                    return;
+                }
+
+                // If there is a non-empty `with`-stack (or a parent context
+                // with one), emit a runtime dynamic resolution instruction.
+                //
+                // Since it is possible for users to e.g. assign a variable to a
+                // dynamic resolution without actually using it, this operation
+                // is wrapped in an extra thunk.
+                if self.has_dynamic_ancestor() {
+                    self.thunk(slot, node, |c, _| {
+                        c.context_mut().captures_with_stack = true;
+                        c.emit_constant(ident.into(), node);
+                        c.push_op(OpCode::OpResolveWith, node);
+                    });
+                    return;
+                }
+
+                // Otherwise, this variable is missing.
+                self.emit_error(node, ErrorKind::UnknownStaticVariable);
+            }
+
+            LocalPosition::Known(idx) => {
+                let stack_idx = self.scope().stack_index(idx);
+                self.push_op(OpCode::OpGetLocal(stack_idx), node);
+            }
+
+            // This identifier is referring to a value from the same scope which
+            // is not yet defined. This identifier access must be thunked.
+            LocalPosition::Recursive(idx) => self.thunk(slot, node, move |compiler, _| {
+                let upvalue_idx = compiler.add_upvalue(
+                    compiler.contexts.len() - 1,
+                    node,
+                    UpvalueKind::Local(idx),
+                );
+                compiler.push_op(OpCode::OpGetUpvalue(upvalue_idx), node);
+            }),
+        };
+    }
+
+    pub(super) fn compile_ident(&mut self, slot: LocalIdx, node: &ast::Ident) {
+        let ident = node.ident_token().unwrap();
+        self.compile_identifier_access(slot, ident.text(), node);
+    }
+}
+
+/// Private compiler helpers related to bindings.
+impl Compiler<'_, '_> {
+    fn resolve_upvalue<N: ToSpan>(
+        &mut self,
+        ctx_idx: usize,
+        name: &str,
+        node: &N,
+    ) -> Option<UpvalueIdx> {
+        if ctx_idx == 0 {
+            // There can not be any upvalue at the outermost context.
+            return None;
+        }
+
+        // Determine whether the upvalue is a local in the enclosing context.
+        match self.contexts[ctx_idx - 1].scope.resolve_local(name) {
+            // recursive upvalues are dealt with the same way as standard known
+            // ones, as thunks and closures are guaranteed to be placed on the
+            // stack (i.e. in the right position) *during* their runtime
+            // construction
+            LocalPosition::Known(idx) | LocalPosition::Recursive(idx) => {
+                return Some(self.add_upvalue(ctx_idx, node, UpvalueKind::Local(idx)))
+            }
+
+            LocalPosition::Unknown => { /* continue below */ }
+        };
+
+        // If the upvalue comes from even further up, we need to recurse to make
+        // sure that the upvalues are created at each level.
+        if let Some(idx) = self.resolve_upvalue(ctx_idx - 1, name, node) {
+            return Some(self.add_upvalue(ctx_idx, node, UpvalueKind::Upvalue(idx)));
+        }
+
+        None
+    }
+
+    fn add_upvalue<N: ToSpan>(
+        &mut self,
+        ctx_idx: usize,
+        node: &N,
+        kind: UpvalueKind,
+    ) -> UpvalueIdx {
+        // If there is already an upvalue closing over the specified index,
+        // retrieve that instead.
+        for (idx, existing) in self.contexts[ctx_idx].scope.upvalues.iter().enumerate() {
+            if existing.kind == kind {
+                return UpvalueIdx(idx);
+            }
+        }
+
+        let span = self.span_for(node);
+        self.contexts[ctx_idx]
+            .scope
+            .upvalues
+            .push(Upvalue { kind, span });
+
+        let idx = UpvalueIdx(self.contexts[ctx_idx].lambda.upvalue_count);
+        self.contexts[ctx_idx].lambda.upvalue_count += 1;
+        idx
+    }
+}
diff --git a/tvix/eval/src/compiler/import.rs b/tvix/eval/src/compiler/import.rs
new file mode 100644
index 0000000000..9036eec817
--- /dev/null
+++ b/tvix/eval/src/compiler/import.rs
@@ -0,0 +1,120 @@
+//! This module implements the Nix language's `import` feature, which
+//! is exposed as a builtin in the Nix language.
+//!
+//! This is not a typical builtin, as it needs access to internal
+//! compiler and VM state (such as the [`crate::SourceCode`]
+//! instance, or observers).
+
+use super::GlobalsMap;
+use genawaiter::rc::Gen;
+use std::rc::Weak;
+
+use crate::{
+    builtins::coerce_value_to_path,
+    generators::pin_generator,
+    observer::NoOpObserver,
+    value::{Builtin, Thunk},
+    vm::generators::{self, GenCo},
+    ErrorKind, SourceCode, Value,
+};
+
+async fn import_impl(
+    co: GenCo,
+    globals: Weak<GlobalsMap>,
+    source: SourceCode,
+    mut args: Vec<Value>,
+) -> Result<Value, ErrorKind> {
+    // TODO(sterni): canon_path()?
+    let mut path = match coerce_value_to_path(&co, args.pop().unwrap()).await? {
+        Err(cek) => return Ok(Value::Catchable(Box::new(cek))),
+        Ok(path) => path,
+    };
+
+    if path.is_dir() {
+        path.push("default.nix");
+    }
+
+    if let Some(cached) = generators::request_import_cache_lookup(&co, path.clone()).await {
+        return Ok(cached);
+    }
+
+    let mut reader = generators::request_open_file(&co, path.clone()).await;
+    // We read to a String instead of a Vec<u8> because rnix only supports
+    // string source files.
+    let mut contents = String::new();
+    reader.read_to_string(&mut contents)?;
+
+    let parsed = rnix::ast::Root::parse(&contents);
+    let errors = parsed.errors();
+    let file = source.add_file(path.to_string_lossy().to_string(), contents.to_owned());
+
+    if !errors.is_empty() {
+        return Err(ErrorKind::ImportParseError {
+            path,
+            file,
+            errors: errors.to_vec(),
+        });
+    }
+
+    let result = crate::compiler::compile(
+        &parsed.tree().expr().unwrap(),
+        Some(path.clone()),
+        // The VM must ensure that a strong reference to the globals outlives
+        // any self-references (which are weak) embedded within the globals. If
+        // the expect() below panics, it means that did not happen.
+        globals
+            .upgrade()
+            .expect("globals dropped while still in use"),
+        &source,
+        &file,
+        &mut NoOpObserver::default(),
+    )
+    .map_err(|err| ErrorKind::ImportCompilerError {
+        path: path.clone(),
+        errors: vec![err],
+    })?;
+
+    if !result.errors.is_empty() {
+        return Err(ErrorKind::ImportCompilerError {
+            path,
+            errors: result.errors,
+        });
+    }
+
+    for warning in result.warnings {
+        generators::emit_warning(&co, warning).await;
+    }
+
+    // Compilation succeeded, we can construct a thunk from whatever it spat
+    // out and return that.
+    let res = Value::Thunk(Thunk::new_suspended(
+        result.lambda,
+        generators::request_span(&co).await,
+    ));
+
+    generators::request_import_cache_put(&co, path, res.clone()).await;
+
+    Ok(res)
+}
+
+/// Constructs the `import` builtin. This builtin is special in that
+/// it needs to capture the [crate::SourceCode] structure to correctly
+/// track source code locations while invoking a compiler.
+// TODO: need to be able to pass through a CompilationObserver, too.
+// TODO: can the `SourceCode` come from the compiler?
+pub(super) fn builtins_import(globals: &Weak<GlobalsMap>, source: SourceCode) -> Builtin {
+    // This (very cheap, once-per-compiler-startup) clone exists
+    // solely in order to keep the borrow checker happy.  It
+    // resolves the tension between the requirements of
+    // Rc::new_cyclic() and Builtin::new()
+    let globals = globals.clone();
+
+    Builtin::new(
+        "import",
+        Some("Import the given file and return the Nix value it evaluates to"),
+        1,
+        move |args| {
+            Gen::new(|co| pin_generator(import_impl(co, globals.clone(), source.clone(), args)))
+        },
+    )
+}
diff --git a/tvix/eval/src/compiler/mod.rs b/tvix/eval/src/compiler/mod.rs
new file mode 100644
index 0000000000..60c55dda27
--- /dev/null
+++ b/tvix/eval/src/compiler/mod.rs
@@ -0,0 +1,1684 @@
+//! This module implements a compiler for compiling the rnix AST
+//! representation to Tvix bytecode.
+//!
+//! A note on `unwrap()`: This module contains a lot of calls to
+//! `unwrap()` or `expect(...)` on data structures returned by `rnix`.
+//! The reason for this is that rnix uses the same data structures to
+//! represent broken and correct ASTs, so all typed AST variants have
+//! the ability to represent an incorrect node.
+//!
+//! However, at the time that the AST is passed to the compiler we
+//! have verified that `rnix` considers the code to be correct, so all
+//! variants are fulfilled. In cases where the invariant is guaranteed
+//! by the code in this module, `debug_assert!` has been used to catch
+//! mistakes early during development.
+
+mod bindings;
+mod import;
+mod optimiser;
+mod scope;
+
+use codemap::Span;
+use rnix::ast::{self, AstToken};
+use smol_str::SmolStr;
+use std::collections::{BTreeMap, HashMap};
+use std::path::{Path, PathBuf};
+use std::rc::{Rc, Weak};
+
+use crate::chunk::Chunk;
+use crate::errors::{CatchableErrorKind, Error, ErrorKind, EvalResult};
+use crate::observer::CompilerObserver;
+use crate::opcode::{CodeIdx, ConstantIdx, Count, JumpOffset, OpCode, UpvalueIdx};
+use crate::spans::LightSpan;
+use crate::spans::ToSpan;
+use crate::value::{Closure, Formals, Lambda, NixAttrs, Thunk, Value};
+use crate::warnings::{EvalWarning, WarningKind};
+use crate::CoercionKind;
+use crate::SourceCode;
+
+use self::scope::{LocalIdx, LocalPosition, Scope, Upvalue, UpvalueKind};
+
+/// Represents the result of compiling a piece of Nix code. If
+/// compilation was successful, the resulting bytecode can be passed
+/// to the VM.
+pub struct CompilationOutput {
+    pub lambda: Rc<Lambda>,
+    pub warnings: Vec<EvalWarning>,
+    pub errors: Vec<Error>,
+
+    // This field must outlive the rc::Weak reference which breaks the
+    // builtins -> import -> builtins reference cycle. For this
+    // reason, it must be passed to the VM.
+    pub globals: Rc<GlobalsMap>,
+}
+
+/// Represents the lambda currently being compiled.
+struct LambdaCtx {
+    lambda: Lambda,
+    scope: Scope,
+    captures_with_stack: bool,
+    unthunk: bool,
+}
+
+impl LambdaCtx {
+    fn new() -> Self {
+        LambdaCtx {
+            lambda: Lambda::default(),
+            scope: Default::default(),
+            captures_with_stack: false,
+            unthunk: false,
+        }
+    }
+
+    fn inherit(&self) -> Self {
+        LambdaCtx {
+            lambda: Lambda::default(),
+            scope: self.scope.inherit(),
+            captures_with_stack: false,
+            unthunk: false,
+        }
+    }
+}
+
+/// When compiling functions with an argument attribute set destructuring pattern,
+/// we need to do multiple passes over the declared formal arguments when setting
+/// up their local bindings (similarly to `let … in` expressions and recursive
+/// attribute sets. For this purpose, this struct is used to represent the two
+/// kinds of formal arguments:
+///
+/// - `TrackedFormal::NoDefault` is always required and causes an evaluation error
+///   if the corresponding attribute is missing in a function call.
+/// - `TrackedFormal::WithDefault` may be missing in the passed attribute set—
+///   in which case a `default_expr` will be evaluated and placed in the formal
+///   argument's local variable slot.
+enum TrackedFormal {
+    NoDefault {
+        local_idx: LocalIdx,
+        pattern_entry: ast::PatEntry,
+    },
+    WithDefault {
+        local_idx: LocalIdx,
+        /// Extra phantom local used for coordinating runtime dispatching not observable to
+        /// the language user. Detailed description in `compile_param_pattern()`.
+        finalise_request_idx: LocalIdx,
+        default_expr: ast::Expr,
+        pattern_entry: ast::PatEntry,
+    },
+}
+
+impl TrackedFormal {
+    fn pattern_entry(&self) -> &ast::PatEntry {
+        match self {
+            TrackedFormal::NoDefault { pattern_entry, .. } => pattern_entry,
+            TrackedFormal::WithDefault { pattern_entry, .. } => pattern_entry,
+        }
+    }
+    fn local_idx(&self) -> LocalIdx {
+        match self {
+            TrackedFormal::NoDefault { local_idx, .. } => *local_idx,
+            TrackedFormal::WithDefault { local_idx, .. } => *local_idx,
+        }
+    }
+}
+
+/// The map of globally available functions and other values that
+/// should implicitly be resolvable in the global scope.
+pub(crate) type GlobalsMap = HashMap<&'static str, Value>;
+
+/// Set of builtins that (if they exist) should be made available in
+/// the global scope, meaning that they can be accessed not just
+/// through `builtins.<name>`, but directly as `<name>`. This is not
+/// configurable, it is based on what Nix 2.3 exposed.
+const GLOBAL_BUILTINS: &[&str] = &[
+    "abort",
+    "baseNameOf",
+    "derivation",
+    "derivationStrict",
+    "dirOf",
+    "fetchGit",
+    "fetchMercurial",
+    "fetchTarball",
+    "fromTOML",
+    "import",
+    "isNull",
+    "map",
+    "placeholder",
+    "removeAttrs",
+    "scopedImport",
+    "throw",
+    "toString",
+    "__curPos",
+];
+
+pub struct Compiler<'source, 'observer> {
+    contexts: Vec<LambdaCtx>,
+    warnings: Vec<EvalWarning>,
+    errors: Vec<Error>,
+    root_dir: PathBuf,
+
+    /// Carries all known global tokens; the full set of which is
+    /// created when the compiler is invoked.
+    ///
+    /// Each global has an associated token, which when encountered as
+    /// an identifier is resolved against the scope poisoning logic,
+    /// and a function that should emit code for the token.
+    globals: Rc<GlobalsMap>,
+
+    /// Reference to the struct holding all of the source code, which
+    /// is used for error creation.
+    source: &'source SourceCode,
+
+    /// File reference in the source map for the current file, which
+    /// is used for creating spans.
+    file: &'source codemap::File,
+
+    /// Carry an observer for the compilation process, which is called
+    /// whenever a chunk is emitted.
+    observer: &'observer mut dyn CompilerObserver,
+
+    /// Carry a count of nested scopes which have requested the
+    /// compiler not to emit anything. This used for compiling dead
+    /// code branches to catch errors & warnings in them.
+    dead_scope: usize,
+}
+
+impl Compiler<'_, '_> {
+    pub(super) fn span_for<S: ToSpan>(&self, to_span: &S) -> Span {
+        to_span.span_for(self.file)
+    }
+}
+
+/// Compiler construction
+impl<'source, 'observer> Compiler<'source, 'observer> {
+    pub(crate) fn new(
+        location: Option<PathBuf>,
+        globals: Rc<GlobalsMap>,
+        source: &'source SourceCode,
+        file: &'source codemap::File,
+        observer: &'observer mut dyn CompilerObserver,
+    ) -> EvalResult<Self> {
+        let mut root_dir = match location {
+            Some(dir) if cfg!(target_arch = "wasm32") || dir.is_absolute() => Ok(dir),
+            _ => {
+                let current_dir = std::env::current_dir().map_err(|e| {
+                    Error::new(
+                        ErrorKind::RelativePathResolution(format!(
+                            "could not determine current directory: {}",
+                            e
+                        )),
+                        file.span,
+                        source.clone(),
+                    )
+                })?;
+                if let Some(dir) = location {
+                    Ok(current_dir.join(dir))
+                } else {
+                    Ok(current_dir)
+                }
+            }
+        }?;
+
+        // If the path passed from the caller points to a file, the
+        // filename itself needs to be truncated as this must point to a
+        // directory.
+        if root_dir.is_file() {
+            root_dir.pop();
+        }
+
+        #[cfg(not(target_arch = "wasm32"))]
+        debug_assert!(root_dir.is_absolute());
+
+        Ok(Self {
+            root_dir,
+            source,
+            file,
+            observer,
+            globals,
+            contexts: vec![LambdaCtx::new()],
+            warnings: vec![],
+            errors: vec![],
+            dead_scope: 0,
+        })
+    }
+}
+
+// Helper functions for emitting code and metadata to the internal
+// structures of the compiler.
+impl Compiler<'_, '_> {
+    fn context(&self) -> &LambdaCtx {
+        &self.contexts[self.contexts.len() - 1]
+    }
+
+    fn context_mut(&mut self) -> &mut LambdaCtx {
+        let idx = self.contexts.len() - 1;
+        &mut self.contexts[idx]
+    }
+
+    fn chunk(&mut self) -> &mut Chunk {
+        &mut self.context_mut().lambda.chunk
+    }
+
+    fn scope(&self) -> &Scope {
+        &self.context().scope
+    }
+
+    fn scope_mut(&mut self) -> &mut Scope {
+        &mut self.context_mut().scope
+    }
+
+    /// Push a single instruction to the current bytecode chunk and
+    /// track the source span from which it was compiled.
+    fn push_op<T: ToSpan>(&mut self, data: OpCode, node: &T) -> CodeIdx {
+        if self.dead_scope > 0 {
+            return CodeIdx(0);
+        }
+
+        let span = self.span_for(node);
+        self.chunk().push_op(data, span)
+    }
+
+    /// Emit a single constant to the current bytecode chunk and track
+    /// the source span from which it was compiled.
+    pub(super) fn emit_constant<T: ToSpan>(&mut self, value: Value, node: &T) {
+        if self.dead_scope > 0 {
+            return;
+        }
+
+        let idx = self.chunk().push_constant(value);
+        self.push_op(OpCode::OpConstant(idx), node);
+    }
+}
+
+// Actual code-emitting AST traversal methods.
+impl Compiler<'_, '_> {
+    fn compile(&mut self, slot: LocalIdx, expr: ast::Expr) {
+        let expr = optimiser::optimise_expr(self, slot, expr);
+
+        match &expr {
+            ast::Expr::Literal(literal) => self.compile_literal(literal),
+            ast::Expr::Path(path) => self.compile_path(slot, path),
+            ast::Expr::Str(s) => self.compile_str(slot, s),
+
+            ast::Expr::UnaryOp(op) => self.thunk(slot, op, move |c, s| c.compile_unary_op(s, op)),
+
+            ast::Expr::BinOp(binop) => {
+                self.thunk(slot, binop, move |c, s| c.compile_binop(s, binop))
+            }
+
+            ast::Expr::HasAttr(has_attr) => {
+                self.thunk(slot, has_attr, move |c, s| c.compile_has_attr(s, has_attr))
+            }
+
+            ast::Expr::List(list) => self.thunk(slot, list, move |c, s| c.compile_list(s, list)),
+
+            ast::Expr::AttrSet(attrs) => {
+                self.thunk(slot, attrs, move |c, s| c.compile_attr_set(s, attrs))
+            }
+
+            ast::Expr::Select(select) => {
+                self.thunk(slot, select, move |c, s| c.compile_select(s, select))
+            }
+
+            ast::Expr::Assert(assert) => {
+                self.thunk(slot, assert, move |c, s| c.compile_assert(s, assert))
+            }
+            ast::Expr::IfElse(if_else) => {
+                self.thunk(slot, if_else, move |c, s| c.compile_if_else(s, if_else))
+            }
+
+            ast::Expr::LetIn(let_in) => {
+                self.thunk(slot, let_in, move |c, s| c.compile_let_in(s, let_in))
+            }
+
+            ast::Expr::Ident(ident) => self.compile_ident(slot, ident),
+            ast::Expr::With(with) => self.thunk(slot, with, |c, s| c.compile_with(s, with)),
+            ast::Expr::Lambda(lambda) => self.thunk(slot, lambda, move |c, s| {
+                c.compile_lambda_or_thunk(false, s, lambda, |c, s| c.compile_lambda(s, lambda))
+            }),
+            ast::Expr::Apply(apply) => {
+                self.thunk(slot, apply, move |c, s| c.compile_apply(s, apply))
+            }
+
+            // Parenthesized expressions are simply unwrapped, leaving
+            // their value on the stack.
+            ast::Expr::Paren(paren) => self.compile(slot, paren.expr().unwrap()),
+
+            ast::Expr::LegacyLet(legacy_let) => self.thunk(slot, legacy_let, move |c, s| {
+                c.compile_legacy_let(s, legacy_let)
+            }),
+
+            ast::Expr::Root(_) => unreachable!("there cannot be more than one root"),
+            ast::Expr::Error(_) => unreachable!("compile is only called on validated trees"),
+        }
+    }
+
+    /// Compiles an expression, but does not emit any code for it as
+    /// it is considered dead. This will still catch errors and
+    /// warnings in that expression.
+    ///
+    /// A warning about the that code being dead is assumed to already be
+    /// emitted by the caller of this.
+    fn compile_dead_code(&mut self, slot: LocalIdx, node: ast::Expr) {
+        self.dead_scope += 1;
+        self.compile(slot, node);
+        self.dead_scope -= 1;
+    }
+
+    fn compile_literal(&mut self, node: &ast::Literal) {
+        let value = match node.kind() {
+            ast::LiteralKind::Float(f) => Value::Float(f.value().unwrap()),
+            ast::LiteralKind::Integer(i) => match i.value() {
+                Ok(v) => Value::Integer(v),
+                Err(err) => return self.emit_error(node, err.into()),
+            },
+
+            ast::LiteralKind::Uri(u) => {
+                self.emit_warning(node, WarningKind::DeprecatedLiteralURL);
+                Value::from(u.syntax().text())
+            }
+        };
+
+        self.emit_constant(value, node);
+    }
+
+    fn compile_path(&mut self, slot: LocalIdx, node: &ast::Path) {
+        // TODO(tazjin): placeholder implementation while waiting for
+        // https://github.com/nix-community/rnix-parser/pull/96
+
+        let raw_path = node.to_string();
+        let path = if raw_path.starts_with('/') {
+            Path::new(&raw_path).to_owned()
+        } else if raw_path.starts_with('~') {
+            // We assume that home paths start with ~/ or fail to parse
+            // TODO: this should be checked using a parse-fail test.
+            debug_assert!(raw_path.len() > 2 && raw_path.starts_with("~/"));
+
+            let home_relative_path = &raw_path[2..(raw_path.len())];
+            self.emit_constant(
+                Value::UnresolvedPath(Box::new(home_relative_path.into())),
+                node,
+            );
+            self.push_op(OpCode::OpResolveHomePath, node);
+            return;
+        } else if raw_path.starts_with('<') {
+            // TODO: decide what to do with findFile
+            if raw_path.len() == 2 {
+                return self.emit_constant(
+                    Value::Catchable(Box::new(CatchableErrorKind::NixPathResolution(
+                        "Empty <> path not allowed".into(),
+                    ))),
+                    node,
+                );
+            }
+            let path = &raw_path[1..(raw_path.len() - 1)];
+            // Make a thunk to resolve the path (without using `findFile`, at least for now?)
+            return self.thunk(slot, node, move |c, _| {
+                c.emit_constant(Value::UnresolvedPath(Box::new(path.into())), node);
+                c.push_op(OpCode::OpFindFile, node);
+            });
+        } else {
+            let mut buf = self.root_dir.clone();
+            buf.push(&raw_path);
+            buf
+        };
+
+        // TODO: Use https://github.com/rust-lang/rfcs/issues/2208
+        // once it is available
+        let value = Value::Path(Box::new(crate::value::canon_path(path)));
+        self.emit_constant(value, node);
+    }
+
+    /// Helper that compiles the given string parts strictly. The caller
+    /// (`compile_str`) needs to figure out if the result of compiling this
+    /// needs to be thunked or not.
+    fn compile_str_parts(
+        &mut self,
+        slot: LocalIdx,
+        parent_node: &ast::Str,
+        parts: Vec<ast::InterpolPart<String>>,
+    ) {
+        // The string parts are produced in literal order, however
+        // they need to be reversed on the stack in order to
+        // efficiently create the real string in case of
+        // interpolation.
+        for part in parts.iter().rev() {
+            match part {
+                // Interpolated expressions are compiled as normal and
+                // dealt with by the VM before being assembled into
+                // the final string. We need to coerce them here,
+                // so OpInterpolate definitely has a string to consume.
+                ast::InterpolPart::Interpolation(ipol) => {
+                    self.compile(slot, ipol.expr().unwrap());
+                    // implicitly forces as well
+                    self.push_op(
+                        OpCode::OpCoerceToString(CoercionKind {
+                            strong: false,
+                            import_paths: true,
+                        }),
+                        ipol,
+                    );
+                }
+
+                ast::InterpolPart::Literal(lit) => {
+                    self.emit_constant(Value::from(lit.as_str()), parent_node);
+                }
+            }
+        }
+
+        if parts.len() != 1 {
+            self.push_op(OpCode::OpInterpolate(Count(parts.len())), parent_node);
+        }
+    }
+
+    fn compile_str(&mut self, slot: LocalIdx, node: &ast::Str) {
+        let parts = node.normalized_parts();
+
+        // We need to thunk string expressions if they are the result of
+        // interpolation. A string that only consists of a single part (`"${foo}"`)
+        // can't desugar to the enclosed expression (`foo`) because we need to
+        // coerce the result to a string value. This would require forcing the
+        // value of the inner expression, so we need to wrap it in another thunk.
+        if parts.len() != 1 || matches!(&parts[0], ast::InterpolPart::Interpolation(_)) {
+            self.thunk(slot, node, move |c, s| {
+                c.compile_str_parts(s, node, parts);
+            });
+        } else {
+            self.compile_str_parts(slot, node, parts);
+        }
+    }
+
+    fn compile_unary_op(&mut self, slot: LocalIdx, op: &ast::UnaryOp) {
+        self.compile(slot, op.expr().unwrap());
+        self.emit_force(op);
+
+        let opcode = match op.operator().unwrap() {
+            ast::UnaryOpKind::Invert => OpCode::OpInvert,
+            ast::UnaryOpKind::Negate => OpCode::OpNegate,
+        };
+
+        self.push_op(opcode, op);
+    }
+
+    fn compile_binop(&mut self, slot: LocalIdx, op: &ast::BinOp) {
+        use ast::BinOpKind;
+
+        // Short-circuiting and other strange operators, which are
+        // under the same node type as NODE_BIN_OP, but need to be
+        // handled separately (i.e. before compiling the expressions
+        // used for standard binary operators).
+
+        match op.operator().unwrap() {
+            BinOpKind::And => return self.compile_and(slot, op),
+            BinOpKind::Or => return self.compile_or(slot, op),
+            BinOpKind::Implication => return self.compile_implication(slot, op),
+            _ => {}
+        };
+
+        // For all other operators, the two values need to be left on
+        // the stack in the correct order before pushing the
+        // instruction for the operation itself.
+        self.compile(slot, op.lhs().unwrap());
+        self.emit_force(&op.lhs().unwrap());
+
+        self.compile(slot, op.rhs().unwrap());
+        self.emit_force(&op.rhs().unwrap());
+
+        match op.operator().unwrap() {
+            BinOpKind::Add => self.push_op(OpCode::OpAdd, op),
+            BinOpKind::Sub => self.push_op(OpCode::OpSub, op),
+            BinOpKind::Mul => self.push_op(OpCode::OpMul, op),
+            BinOpKind::Div => self.push_op(OpCode::OpDiv, op),
+            BinOpKind::Update => self.push_op(OpCode::OpAttrsUpdate, op),
+            BinOpKind::Equal => self.push_op(OpCode::OpEqual, op),
+            BinOpKind::Less => self.push_op(OpCode::OpLess, op),
+            BinOpKind::LessOrEq => self.push_op(OpCode::OpLessOrEq, op),
+            BinOpKind::More => self.push_op(OpCode::OpMore, op),
+            BinOpKind::MoreOrEq => self.push_op(OpCode::OpMoreOrEq, op),
+            BinOpKind::Concat => self.push_op(OpCode::OpConcat, op),
+
+            BinOpKind::NotEqual => {
+                self.push_op(OpCode::OpEqual, op);
+                self.push_op(OpCode::OpInvert, op)
+            }
+
+            // Handled by separate branch above.
+            BinOpKind::And | BinOpKind::Implication | BinOpKind::Or => {
+                unreachable!()
+            }
+        };
+    }
+
+    fn compile_and(&mut self, slot: LocalIdx, node: &ast::BinOp) {
+        debug_assert!(
+            matches!(node.operator(), Some(ast::BinOpKind::And)),
+            "compile_and called with wrong operator kind: {:?}",
+            node.operator(),
+        );
+
+        // Leave left-hand side value on the stack.
+        self.compile(slot, node.lhs().unwrap());
+        self.emit_force(&node.lhs().unwrap());
+
+        let throw_idx = self.push_op(OpCode::OpJumpIfCatchable(JumpOffset(0)), node);
+        // If this value is false, jump over the right-hand side - the
+        // whole expression is false.
+        let end_idx = self.push_op(OpCode::OpJumpIfFalse(JumpOffset(0)), node);
+
+        // Otherwise, remove the previous value and leave the
+        // right-hand side on the stack. Its result is now the value
+        // of the whole expression.
+        self.push_op(OpCode::OpPop, node);
+        self.compile(slot, node.rhs().unwrap());
+        self.emit_force(&node.rhs().unwrap());
+
+        self.patch_jump(end_idx);
+        self.push_op(OpCode::OpAssertBool, node);
+        self.patch_jump(throw_idx);
+    }
+
+    fn compile_or(&mut self, slot: LocalIdx, node: &ast::BinOp) {
+        debug_assert!(
+            matches!(node.operator(), Some(ast::BinOpKind::Or)),
+            "compile_or called with wrong operator kind: {:?}",
+            node.operator(),
+        );
+
+        // Leave left-hand side value on the stack
+        self.compile(slot, node.lhs().unwrap());
+        self.emit_force(&node.lhs().unwrap());
+
+        let throw_idx = self.push_op(OpCode::OpJumpIfCatchable(JumpOffset(0)), node);
+        // Opposite of above: If this value is **true**, we can
+        // short-circuit the right-hand side.
+        let end_idx = self.push_op(OpCode::OpJumpIfTrue(JumpOffset(0)), node);
+        self.push_op(OpCode::OpPop, node);
+        self.compile(slot, node.rhs().unwrap());
+        self.emit_force(&node.rhs().unwrap());
+
+        self.patch_jump(end_idx);
+        self.push_op(OpCode::OpAssertBool, node);
+        self.patch_jump(throw_idx);
+    }
+
+    fn compile_implication(&mut self, slot: LocalIdx, node: &ast::BinOp) {
+        debug_assert!(
+            matches!(node.operator(), Some(ast::BinOpKind::Implication)),
+            "compile_implication called with wrong operator kind: {:?}",
+            node.operator(),
+        );
+
+        // Leave left-hand side value on the stack and invert it.
+        self.compile(slot, node.lhs().unwrap());
+        self.emit_force(&node.lhs().unwrap());
+        let throw_idx = self.push_op(OpCode::OpJumpIfCatchable(JumpOffset(0)), node);
+        self.push_op(OpCode::OpInvert, node);
+
+        // Exactly as `||` (because `a -> b` = `!a || b`).
+        let end_idx = self.push_op(OpCode::OpJumpIfTrue(JumpOffset(0)), node);
+        self.push_op(OpCode::OpPop, node);
+        self.compile(slot, node.rhs().unwrap());
+        self.emit_force(&node.rhs().unwrap());
+
+        self.patch_jump(end_idx);
+        self.push_op(OpCode::OpAssertBool, node);
+        self.patch_jump(throw_idx);
+    }
+
+    /// Compile list literals into equivalent bytecode. List
+    /// construction is fairly simple, consisting of pushing code for
+    /// each literal element and an instruction with the element
+    /// count.
+    ///
+    /// The VM, after evaluating the code for each element, simply
+    /// constructs the list from the given number of elements.
+    fn compile_list(&mut self, slot: LocalIdx, node: &ast::List) {
+        let mut count = 0;
+
+        // Open a temporary scope to correctly account for stack items
+        // that exist during the construction.
+        self.scope_mut().begin_scope();
+
+        for item in node.items() {
+            // Start tracing new stack slots from the second list
+            // element onwards. The first list element is located in
+            // the stack slot of the list itself.
+            let item_slot = match count {
+                0 => slot,
+                _ => {
+                    let item_span = self.span_for(&item);
+                    self.scope_mut().declare_phantom(item_span, false)
+                }
+            };
+
+            count += 1;
+            self.compile(item_slot, item);
+            self.scope_mut().mark_initialised(item_slot);
+        }
+
+        if count == 0 {
+            self.unthunk();
+        }
+
+        self.push_op(OpCode::OpList(Count(count)), node);
+        self.scope_mut().end_scope();
+    }
+
+    fn compile_attr(&mut self, slot: LocalIdx, node: &ast::Attr) {
+        match node {
+            ast::Attr::Dynamic(dynamic) => {
+                self.compile(slot, dynamic.expr().unwrap());
+                self.emit_force(&dynamic.expr().unwrap());
+            }
+
+            ast::Attr::Str(s) => {
+                self.compile_str(slot, s);
+                self.emit_force(s);
+            }
+
+            ast::Attr::Ident(ident) => self.emit_literal_ident(ident),
+        }
+    }
+
+    fn compile_has_attr(&mut self, slot: LocalIdx, node: &ast::HasAttr) {
+        // Put the attribute set on the stack.
+        self.compile(slot, node.expr().unwrap());
+        self.emit_force(node);
+
+        // Push all path fragments with an operation for fetching the
+        // next nested element, for all fragments except the last one.
+        for (count, fragment) in node.attrpath().unwrap().attrs().enumerate() {
+            if count > 0 {
+                self.push_op(OpCode::OpAttrsTrySelect, &fragment);
+                self.emit_force(&fragment);
+            }
+
+            self.compile_attr(slot, &fragment);
+        }
+
+        // After the last fragment, emit the actual instruction that
+        // leaves a boolean on the stack.
+        self.push_op(OpCode::OpHasAttr, node);
+    }
+
+    /// When compiling select or select_or expressions, an optimisation is
+    /// possible of compiling the set emitted a constant attribute set by
+    /// immediately replacing it with the actual value.
+    ///
+    /// We take care not to emit an error here, as that would interfere with
+    /// thunking behaviour (there can be perfectly valid Nix code that accesses
+    /// a statically known attribute set that is lacking a key, because that
+    /// thunk is never evaluated). If anything is missing, just inform the
+    /// caller that the optimisation did not take place and move on. We may want
+    /// to emit warnings here in the future.
+    fn optimise_select(&mut self, path: &ast::Attrpath) -> bool {
+        // If compiling the set emitted a constant attribute set, the
+        // associated constant can immediately be replaced with the
+        // actual value.
+        //
+        // We take care not to emit an error here, as that would
+        // interfere with thunking behaviour (there can be perfectly
+        // valid Nix code that accesses a statically known attribute
+        // set that is lacking a key, because that thunk is never
+        // evaluated). If anything is missing, just move on. We may
+        // want to emit warnings here in the future.
+        if let Some(OpCode::OpConstant(ConstantIdx(idx))) = self.chunk().code.last().cloned() {
+            let constant = &mut self.chunk().constants[idx];
+            if let Value::Attrs(attrs) = constant {
+                let mut path_iter = path.attrs();
+
+                // Only do this optimisation if there is a *single*
+                // element in the attribute path. It is extremely
+                // unlikely that we'd have a static nested set.
+                if let (Some(attr), None) = (path_iter.next(), path_iter.next()) {
+                    // Only do this optimisation for statically known attrs.
+                    if let Some(ident) = expr_static_attr_str(&attr) {
+                        if let Some(selected_value) = attrs.select(ident.as_bytes()) {
+                            *constant = selected_value.clone();
+
+                            // If this worked, we can unthunk the current thunk.
+                            self.unthunk();
+
+                            return true;
+                        }
+                    }
+                }
+            }
+        }
+
+        false
+    }
+
+    fn compile_select(&mut self, slot: LocalIdx, node: &ast::Select) {
+        let set = node.expr().unwrap();
+        let path = node.attrpath().unwrap();
+
+        if node.or_token().is_some() {
+            return self.compile_select_or(slot, set, path, node.default_expr().unwrap());
+        }
+
+        // Push the set onto the stack
+        self.compile(slot, set.clone());
+        if self.optimise_select(&path) {
+            return;
+        }
+
+        // Compile each key fragment and emit access instructions.
+        //
+        // TODO: multi-select instruction to avoid re-pushing attrs on
+        // nested selects.
+        for fragment in path.attrs() {
+            // Force the current set value.
+            self.emit_force(&set);
+
+            self.compile_attr(slot, &fragment);
+            self.push_op(OpCode::OpAttrsSelect, &fragment);
+        }
+    }
+
+    /// Compile an `or` expression into a chunk of conditional jumps.
+    ///
+    /// If at any point during attribute set traversal a key is
+    /// missing, the `OpAttrOrNotFound` instruction will leave a
+    /// special sentinel value on the stack.
+    ///
+    /// After each access, a conditional jump evaluates the top of the
+    /// stack and short-circuits to the default value if it sees the
+    /// sentinel.
+    ///
+    /// Code like `{ a.b = 1; }.a.c or 42` yields this bytecode and
+    /// runtime stack:
+    ///
+    /// ```notrust
+    ///            Bytecode                     Runtime stack
+    ///  ┌────────────────────────────┐   ┌─────────────────────────┐
+    ///  │    ...                     │   │ ...                     │
+    ///  │ 5  OP_ATTRS(1)             │ → │ 5  [ { a.b = 1; }     ] │
+    ///  │ 6  OP_CONSTANT("a")        │ → │ 6  [ { a.b = 1; } "a" ] │
+    ///  │ 7  OP_ATTR_OR_NOT_FOUND    │ → │ 7  [ { b = 1; }       ] │
+    ///  │ 8  JUMP_IF_NOT_FOUND(13)   │ → │ 8  [ { b = 1; }       ] │
+    ///  │ 9  OP_CONSTANT("C")        │ → │ 9  [ { b = 1; } "c"   ] │
+    ///  │ 10 OP_ATTR_OR_NOT_FOUND    │ → │ 10 [ NOT_FOUND        ] │
+    ///  │ 11 JUMP_IF_NOT_FOUND(13)   │ → │ 11 [                  ] │
+    ///  │ 12 JUMP(14)                │   │ ..     jumped over      │
+    ///  │ 13 CONSTANT(42)            │ → │ 12 [ 42 ]               │
+    ///  │ 14 ...                     │   │ ..   ....               │
+    ///  └────────────────────────────┘   └─────────────────────────┘
+    /// ```
+    fn compile_select_or(
+        &mut self,
+        slot: LocalIdx,
+        set: ast::Expr,
+        path: ast::Attrpath,
+        default: ast::Expr,
+    ) {
+        self.compile(slot, set);
+        if self.optimise_select(&path) {
+            return;
+        }
+
+        let mut jumps = vec![];
+
+        for fragment in path.attrs() {
+            self.emit_force(&fragment);
+            self.compile_attr(slot, &fragment.clone());
+            self.push_op(OpCode::OpAttrsTrySelect, &fragment);
+            jumps.push(self.push_op(OpCode::OpJumpIfNotFound(JumpOffset(0)), &fragment));
+        }
+
+        let final_jump = self.push_op(OpCode::OpJump(JumpOffset(0)), &path);
+
+        for jump in jumps {
+            self.patch_jump(jump);
+        }
+
+        // Compile the default value expression and patch the final
+        // jump to point *beyond* it.
+        self.compile(slot, default);
+        self.patch_jump(final_jump);
+    }
+
+    /// Compile `assert` expressions using jumping instructions in the VM.
+    ///
+    /// ```notrust
+    ///                        ┌─────────────────────┐
+    ///                        │ 0  [ conditional ]  │
+    ///                        │ 1   JUMP_IF_FALSE  →┼─┐
+    ///                        │ 2  [  main body  ]  │ │ Jump to else body if
+    ///                       ┌┼─3─←     JUMP        │ │ condition is false.
+    ///  Jump over else body  ││ 4   OP_ASSERT_FAIL ←┼─┘
+    ///  if condition is true.└┼─5─→     ...         │
+    ///                        └─────────────────────┘
+    /// ```
+    fn compile_assert(&mut self, slot: LocalIdx, node: &ast::Assert) {
+        // Compile the assertion condition to leave its value on the stack.
+        self.compile(slot, node.condition().unwrap());
+        self.emit_force(&node.condition().unwrap());
+        let throw_idx = self.push_op(OpCode::OpJumpIfCatchable(JumpOffset(0)), node);
+        let then_idx = self.push_op(OpCode::OpJumpIfFalse(JumpOffset(0)), node);
+
+        self.push_op(OpCode::OpPop, node);
+        self.compile(slot, node.body().unwrap());
+
+        let else_idx = self.push_op(OpCode::OpJump(JumpOffset(0)), node);
+
+        self.patch_jump(then_idx);
+        self.push_op(OpCode::OpPop, node);
+        self.push_op(OpCode::OpAssertFail, &node.condition().unwrap());
+
+        self.patch_jump(else_idx);
+        self.patch_jump(throw_idx);
+    }
+
+    /// Compile conditional expressions using jumping instructions in the VM.
+    ///
+    /// ```notrust
+    ///                        ┌────────────────────┐
+    ///                        │ 0  [ conditional ] │
+    ///                        │ 1   JUMP_IF_FALSE →┼─┐
+    ///                        │ 2  [  main body  ] │ │ Jump to else body if
+    ///                       ┌┼─3─←     JUMP       │ │ condition is false.
+    ///  Jump over else body  ││ 4  [  else body  ]←┼─┘
+    ///  if condition is true.└┼─5─→     ...        │
+    ///                        └────────────────────┘
+    /// ```
+    fn compile_if_else(&mut self, slot: LocalIdx, node: &ast::IfElse) {
+        self.compile(slot, node.condition().unwrap());
+        self.emit_force(&node.condition().unwrap());
+
+        let throw_idx = self.push_op(
+            OpCode::OpJumpIfCatchable(JumpOffset(0)),
+            &node.condition().unwrap(),
+        );
+        let then_idx = self.push_op(
+            OpCode::OpJumpIfFalse(JumpOffset(0)),
+            &node.condition().unwrap(),
+        );
+
+        self.push_op(OpCode::OpPop, node); // discard condition value
+        self.compile(slot, node.body().unwrap());
+
+        let else_idx = self.push_op(OpCode::OpJump(JumpOffset(0)), node);
+
+        self.patch_jump(then_idx); // patch jump *to* else_body
+        self.push_op(OpCode::OpPop, node); // discard condition value
+        self.compile(slot, node.else_body().unwrap());
+
+        self.patch_jump(else_idx); // patch jump *over* else body
+        self.patch_jump(throw_idx); // patch jump *over* else body
+    }
+
+    /// Compile `with` expressions by emitting instructions that
+    /// pop/remove the indices of attribute sets that are implicitly
+    /// in scope through `with` on the "with-stack".
+    fn compile_with(&mut self, slot: LocalIdx, node: &ast::With) {
+        self.scope_mut().begin_scope();
+        // TODO: Detect if the namespace is just an identifier, and
+        // resolve that directly (thus avoiding duplication on the
+        // stack).
+        self.compile(slot, node.namespace().unwrap());
+
+        let span = self.span_for(&node.namespace().unwrap());
+
+        // The attribute set from which `with` inherits values
+        // occupies a slot on the stack, but this stack slot is not
+        // directly accessible. As it must be accounted for to
+        // calculate correct offsets, what we call a "phantom" local
+        // is declared here.
+        let local_idx = self.scope_mut().declare_phantom(span, true);
+        let with_idx = self.scope().stack_index(local_idx);
+
+        self.scope_mut().push_with();
+
+        self.push_op(OpCode::OpPushWith(with_idx), &node.namespace().unwrap());
+
+        self.compile(slot, node.body().unwrap());
+
+        self.push_op(OpCode::OpPopWith, node);
+        self.scope_mut().pop_with();
+        self.cleanup_scope(node);
+    }
+
+    /// Compiles pattern function arguments, such as `{ a, b }: ...`.
+    ///
+    /// These patterns are treated as a special case of locals binding
+    /// where the attribute set itself is placed on the first stack
+    /// slot of the call frame (either as a phantom, or named in case
+    /// of an `@` binding), and the function call sets up the rest of
+    /// the stack as if the parameters were rewritten into a `let`
+    /// binding.
+    ///
+    /// For example:
+    ///
+    /// ```nix
+    /// ({ a, b ? 2, c ? a * b, ... }@args: <body>)  { a = 10; }
+    /// ```
+    ///
+    /// would be compiled similarly to a binding such as
+    ///
+    /// ```nix
+    /// let args = { a = 10; };
+    /// in let a = args.a;
+    ///        b = args.a or 2;
+    ///        c = args.c or a * b;
+    ///    in <body>
+    /// ```
+    ///
+    /// However, there are two properties of pattern function arguments that can
+    /// not be compiled by desugaring in this way:
+    ///
+    /// 1. Bindings have to fail if too many arguments are provided. This is
+    ///    done by emitting a special instruction that checks the set of keys
+    ///    from a constant containing the expected keys.
+    /// 2. Formal arguments with a default expression are (as an optimization and
+    ///    because it is simpler) not wrapped in another thunk, instead compiled
+    ///    and accessed separately. This means that the default expression may
+    ///    never make it into the local's stack slot if the argument is provided
+    ///    by the caller. We need to take this into account and skip any
+    ///    operations specific to the expression like thunk finalisation in such
+    ///    cases.
+    fn compile_param_pattern(&mut self, pattern: &ast::Pattern) -> (Formals, CodeIdx) {
+        let span = self.span_for(pattern);
+
+        let (set_idx, pat_bind_name) = match pattern.pat_bind() {
+            Some(name) => {
+                let pat_bind_name = name.ident().unwrap().to_string();
+                (
+                    self.declare_local(&name, pat_bind_name.clone()),
+                    Some(pat_bind_name),
+                )
+            }
+            None => (self.scope_mut().declare_phantom(span, true), None),
+        };
+
+        // At call time, the attribute set is already at the top of the stack.
+        self.scope_mut().mark_initialised(set_idx);
+        self.emit_force(pattern);
+        let throw_idx = self.push_op(OpCode::OpJumpIfCatchable(JumpOffset(0)), pattern);
+        // Evaluation fails on a type error, even if the argument(s) are unused.
+        self.push_op(OpCode::OpAssertAttrs, pattern);
+
+        let ellipsis = pattern.ellipsis_token().is_some();
+        if !ellipsis {
+            self.push_op(OpCode::OpValidateClosedFormals, pattern);
+        }
+
+        // Similar to `let ... in ...`, we now do multiple passes over
+        // the bindings to first declare them, then populate them, and
+        // then finalise any necessary recursion into the scope.
+        let mut entries: Vec<TrackedFormal> = vec![];
+        let mut arguments = BTreeMap::default();
+
+        for entry in pattern.pat_entries() {
+            let ident = entry.ident().unwrap();
+            let idx = self.declare_local(&ident, ident.to_string());
+
+            arguments.insert(ident.into(), entry.default().is_some());
+
+            if let Some(default_expr) = entry.default() {
+                entries.push(TrackedFormal::WithDefault {
+                    local_idx: idx,
+                    // This phantom is used to track at runtime (!) whether we need to
+                    // finalise the local's stack slot or not. The relevant instructions are
+                    // emitted in the second pass where the mechanism is explained as well.
+                    finalise_request_idx: {
+                        let span = self.span_for(&default_expr);
+                        self.scope_mut().declare_phantom(span, false)
+                    },
+                    default_expr,
+                    pattern_entry: entry,
+                });
+            } else {
+                entries.push(TrackedFormal::NoDefault {
+                    local_idx: idx,
+                    pattern_entry: entry,
+                });
+            }
+        }
+
+        // For each of the bindings, push the set on the stack and
+        // attempt to select from it.
+        let stack_idx = self.scope().stack_index(set_idx);
+        for tracked_formal in entries.iter() {
+            self.push_op(OpCode::OpGetLocal(stack_idx), pattern);
+            self.emit_literal_ident(&tracked_formal.pattern_entry().ident().unwrap());
+
+            let idx = tracked_formal.local_idx();
+
+            // Use the same mechanism as `compile_select_or` if a
+            // default value was provided, or simply select otherwise.
+            match tracked_formal {
+                TrackedFormal::WithDefault {
+                    default_expr,
+                    pattern_entry,
+                    ..
+                } => {
+                    // The tricky bit about compiling a formal argument with a default value
+                    // is that the default may be a thunk that may depend on the value of
+                    // other formal arguments, i.e. may need to be finalised. This
+                    // finalisation can only happen if we are actually using the default
+                    // value—otherwise OpFinalise will crash on an already finalised (or
+                    // non-thunk) value.
+                    //
+                    // Thus we use an additional local to track whether we wound up
+                    // defaulting or not. `FinaliseRequest(false)` indicates that we should
+                    // not finalise, as we did not default.
+                    //
+                    // We are being wasteful with VM stack space in case of default
+                    // expressions that don't end up needing to be finalised. Unfortunately
+                    // we only know better after compiling the default expression, so
+                    // avoiding unnecessary locals would mean we'd need to modify the chunk
+                    // after the fact.
+                    self.push_op(OpCode::OpAttrsTrySelect, &pattern_entry.ident().unwrap());
+                    let jump_to_default =
+                        self.push_op(OpCode::OpJumpIfNotFound(JumpOffset(0)), default_expr);
+
+                    self.emit_constant(Value::FinaliseRequest(false), default_expr);
+
+                    let jump_over_default =
+                        self.push_op(OpCode::OpJump(JumpOffset(0)), default_expr);
+
+                    self.patch_jump(jump_to_default);
+
+                    // Does not need to thunked since compile() already does so when necessary
+                    self.compile(idx, default_expr.clone());
+
+                    self.emit_constant(Value::FinaliseRequest(true), default_expr);
+
+                    self.patch_jump(jump_over_default);
+                }
+                TrackedFormal::NoDefault { pattern_entry, .. } => {
+                    self.push_op(OpCode::OpAttrsSelect, &pattern_entry.ident().unwrap());
+                }
+            }
+
+            self.scope_mut().mark_initialised(idx);
+            if let TrackedFormal::WithDefault {
+                finalise_request_idx,
+                ..
+            } = tracked_formal
+            {
+                self.scope_mut().mark_initialised(*finalise_request_idx);
+            }
+        }
+
+        for tracked_formal in entries.iter() {
+            if self.scope()[tracked_formal.local_idx()].needs_finaliser {
+                let stack_idx = self.scope().stack_index(tracked_formal.local_idx());
+                match tracked_formal {
+                    TrackedFormal::NoDefault { .. } =>
+                        panic!("Tvix bug: local for pattern formal needs finaliser, but has no default expr"),
+                    TrackedFormal::WithDefault { finalise_request_idx, .. } => {
+                        let finalise_request_stack_idx = self.scope().stack_index(*finalise_request_idx);
+
+                        // TODO(sterni): better spans
+                        self.push_op(
+                            OpCode::OpGetLocal(finalise_request_stack_idx),
+                            pattern
+                        );
+                        let jump_over_finalise =
+                            self.push_op(
+                                OpCode::OpJumpIfNoFinaliseRequest(
+                                    JumpOffset(0)),
+                                pattern
+                            );
+                        self.push_op(
+                            OpCode::OpFinalise(stack_idx),
+                            pattern,
+                        );
+                        self.patch_jump(jump_over_finalise);
+                        // Get rid of finaliser request value on the stack
+                        self.push_op(OpCode::OpPop, pattern);
+                    }
+                }
+            }
+        }
+
+        (
+            (Formals {
+                arguments,
+                ellipsis,
+                span,
+                name: pat_bind_name,
+            }),
+            throw_idx,
+        )
+    }
+
+    fn compile_lambda(&mut self, slot: LocalIdx, node: &ast::Lambda) -> Option<CodeIdx> {
+        // Compile the function itself, recording its formal arguments (if any)
+        // for later use
+        let formals = match node.param().unwrap() {
+            ast::Param::Pattern(pat) => Some(self.compile_param_pattern(&pat)),
+
+            ast::Param::IdentParam(param) => {
+                let name = param
+                    .ident()
+                    .unwrap()
+                    .ident_token()
+                    .unwrap()
+                    .text()
+                    .to_string();
+
+                let idx = self.declare_local(&param, &name);
+                self.scope_mut().mark_initialised(idx);
+                None
+            }
+        };
+
+        self.compile(slot, node.body().unwrap());
+        if let Some((formals, throw_idx)) = formals {
+            self.context_mut().lambda.formals = Some(formals);
+            Some(throw_idx)
+        } else {
+            self.context_mut().lambda.formals = None;
+            None
+        }
+    }
+
+    fn thunk<N, F>(&mut self, outer_slot: LocalIdx, node: &N, content: F)
+    where
+        N: ToSpan,
+        F: FnOnce(&mut Compiler, LocalIdx),
+    {
+        self.compile_lambda_or_thunk(true, outer_slot, node, |comp, idx| {
+            content(comp, idx);
+            None
+        })
+    }
+
+    /// Mark the current thunk as redundant, i.e. possible to merge directly
+    /// into its parent lambda context without affecting runtime behaviour.
+    fn unthunk(&mut self) {
+        self.context_mut().unthunk = true;
+    }
+
+    /// Compile an expression into a runtime closure or thunk
+    fn compile_lambda_or_thunk<N, F>(
+        &mut self,
+        is_suspended_thunk: bool,
+        outer_slot: LocalIdx,
+        node: &N,
+        content: F,
+    ) where
+        N: ToSpan,
+        F: FnOnce(&mut Compiler, LocalIdx) -> Option<CodeIdx>,
+    {
+        let name = self.scope()[outer_slot].name();
+        self.new_context();
+
+        // Set the (optional) name of the current slot on the lambda that is
+        // being compiled.
+        self.context_mut().lambda.name = name;
+
+        let span = self.span_for(node);
+        let slot = self.scope_mut().declare_phantom(span, false);
+        self.scope_mut().begin_scope();
+
+        let throw_idx = content(self, slot);
+        self.cleanup_scope(node);
+        if let Some(throw_idx) = throw_idx {
+            self.patch_jump(throw_idx);
+        }
+
+        // TODO: determine and insert enclosing name, if available.
+
+        // Pop the lambda context back off, and emit the finished
+        // lambda as a constant.
+        let mut compiled = self.contexts.pop().unwrap();
+
+        // The compiler might have decided to unthunk, i.e. raise the compiled
+        // code to the parent context. In that case we do so and return right
+        // away.
+        if compiled.unthunk && is_suspended_thunk {
+            self.chunk().extend(compiled.lambda.chunk);
+            return;
+        }
+
+        // Emit an instruction to inform the VM that the chunk has ended.
+        compiled
+            .lambda
+            .chunk
+            .push_op(OpCode::OpReturn, self.span_for(node));
+
+        // Capturing the with stack counts as an upvalue, as it is
+        // emitted as an upvalue data instruction.
+        if compiled.captures_with_stack {
+            compiled.lambda.upvalue_count += 1;
+        }
+
+        let lambda = Rc::new(compiled.lambda);
+        if is_suspended_thunk {
+            self.observer.observe_compiled_thunk(&lambda);
+        } else {
+            self.observer.observe_compiled_lambda(&lambda);
+        }
+
+        // If no upvalues are captured, emit directly and move on.
+        if lambda.upvalue_count == 0 {
+            self.emit_constant(
+                if is_suspended_thunk {
+                    Value::Thunk(Thunk::new_suspended(lambda, LightSpan::new_actual(span)))
+                } else {
+                    Value::Closure(Rc::new(Closure::new(lambda)))
+                },
+                node,
+            );
+            return;
+        }
+
+        // Otherwise, we need to emit the variable number of
+        // operands that allow the runtime to close over the
+        // upvalues and leave a blueprint in the constant index from
+        // which the result can be constructed.
+        let blueprint_idx = self.chunk().push_constant(Value::Blueprint(lambda));
+
+        let code_idx = self.push_op(
+            if is_suspended_thunk {
+                OpCode::OpThunkSuspended(blueprint_idx)
+            } else {
+                OpCode::OpThunkClosure(blueprint_idx)
+            },
+            node,
+        );
+
+        self.emit_upvalue_data(
+            outer_slot,
+            node,
+            compiled.scope.upvalues,
+            compiled.captures_with_stack,
+        );
+
+        if !is_suspended_thunk && !self.scope()[outer_slot].needs_finaliser {
+            if !self.scope()[outer_slot].must_thunk {
+                // The closure has upvalues, but is not recursive.  Therefore no thunk is required,
+                // which saves us the overhead of Rc<RefCell<>>
+                self.chunk()[code_idx] = OpCode::OpClosure(blueprint_idx);
+            } else {
+                // This case occurs when a closure has upvalue-references to itself but does not need a
+                // finaliser.  Since no OpFinalise will be emitted later on we synthesize one here.
+                // It is needed here only to set [`Closure::is_finalised`] which is used for sanity checks.
+                #[cfg(debug_assertions)]
+                self.push_op(
+                    OpCode::OpFinalise(self.scope().stack_index(outer_slot)),
+                    &self.span_for(node),
+                );
+            }
+        }
+    }
+
+    fn compile_apply(&mut self, slot: LocalIdx, node: &ast::Apply) {
+        // To call a function, we leave its arguments on the stack,
+        // followed by the function expression itself, and then emit a
+        // call instruction. This way, the stack is perfectly laid out
+        // to enter the function call straight away.
+        self.compile(slot, node.argument().unwrap());
+        self.compile(slot, node.lambda().unwrap());
+        self.emit_force(&node.lambda().unwrap());
+        self.push_op(OpCode::OpCall, node);
+    }
+
+    /// Emit the data instructions that the runtime needs to correctly
+    /// assemble the upvalues struct.
+    fn emit_upvalue_data<T: ToSpan>(
+        &mut self,
+        slot: LocalIdx,
+        node: &T,
+        upvalues: Vec<Upvalue>,
+        capture_with: bool,
+    ) {
+        for upvalue in upvalues {
+            match upvalue.kind {
+                UpvalueKind::Local(idx) => {
+                    let target = &self.scope()[idx];
+                    let stack_idx = self.scope().stack_index(idx);
+
+                    // If the target is not yet initialised, we need to defer
+                    // the local access
+                    if !target.initialised {
+                        self.push_op(OpCode::DataDeferredLocal(stack_idx), &upvalue.span);
+                        self.scope_mut().mark_needs_finaliser(slot);
+                    } else {
+                        // a self-reference
+                        if slot == idx {
+                            self.scope_mut().mark_must_thunk(slot);
+                        }
+                        self.push_op(OpCode::DataStackIdx(stack_idx), &upvalue.span);
+                    }
+                }
+
+                UpvalueKind::Upvalue(idx) => {
+                    self.push_op(OpCode::DataUpvalueIdx(idx), &upvalue.span);
+                }
+            };
+        }
+
+        if capture_with {
+            // TODO(tazjin): probably better to emit span for the ident that caused this
+            self.push_op(OpCode::DataCaptureWith, node);
+        }
+    }
+
+    /// Emit the literal string value of an identifier. Required for
+    /// several operations related to attribute sets, where
+    /// identifiers are used as string keys.
+    fn emit_literal_ident(&mut self, ident: &ast::Ident) {
+        self.emit_constant(Value::String(ident.clone().into()), ident);
+    }
+
+    /// Patch the jump instruction at the given index, setting its
+    /// jump offset from the placeholder to the current code position.
+    ///
+    /// This is required because the actual target offset of jumps is
+    /// not known at the time when the jump operation itself is
+    /// emitted.
+    fn patch_jump(&mut self, idx: CodeIdx) {
+        let offset = JumpOffset(self.chunk().code.len() - 1 - idx.0);
+
+        match &mut self.chunk().code[idx.0] {
+            OpCode::OpJump(n)
+            | OpCode::OpJumpIfFalse(n)
+            | OpCode::OpJumpIfTrue(n)
+            | OpCode::OpJumpIfCatchable(n)
+            | OpCode::OpJumpIfNotFound(n)
+            | OpCode::OpJumpIfNoFinaliseRequest(n) => {
+                *n = offset;
+            }
+
+            op => panic!("attempted to patch unsupported op: {:?}", op),
+        }
+    }
+
+    /// Decrease scope depth of the current function and emit
+    /// instructions to clean up the stack at runtime.
+    fn cleanup_scope<N: ToSpan>(&mut self, node: &N) {
+        // When ending a scope, all corresponding locals need to be
+        // removed, but the value of the body needs to remain on the
+        // stack. This is implemented by a separate instruction.
+        let (popcount, unused_spans) = self.scope_mut().end_scope();
+
+        for span in &unused_spans {
+            self.emit_warning(span, WarningKind::UnusedBinding);
+        }
+
+        if popcount > 0 {
+            self.push_op(OpCode::OpCloseScope(Count(popcount)), node);
+        }
+    }
+
+    /// Open a new lambda context within which to compile a function,
+    /// closure or thunk.
+    fn new_context(&mut self) {
+        self.contexts.push(self.context().inherit());
+    }
+
+    /// Declare a local variable known in the scope that is being
+    /// compiled by pushing it to the locals. This is used to
+    /// determine the stack offset of variables.
+    fn declare_local<S: Into<String>, N: ToSpan>(&mut self, node: &N, name: S) -> LocalIdx {
+        let name = name.into();
+        let depth = self.scope().scope_depth();
+
+        // Do this little dance to turn name:&'a str into the same
+        // string with &'static lifetime, as required by WarningKind
+        if let Some((global_ident, _)) = self.globals.get_key_value(name.as_str()) {
+            self.emit_warning(node, WarningKind::ShadowedGlobal(global_ident));
+        }
+
+        let span = self.span_for(node);
+        let (idx, shadowed) = self.scope_mut().declare_local(name, span);
+
+        if let Some(shadow_idx) = shadowed {
+            let other = &self.scope()[shadow_idx];
+            if other.depth == depth {
+                self.emit_error(node, ErrorKind::VariableAlreadyDefined(other.span));
+            }
+        }
+
+        idx
+    }
+
+    /// Determine whether the current lambda context has any ancestors
+    /// that use dynamic scope resolution, and mark contexts as
+    /// needing to capture their enclosing `with`-stack in their
+    /// upvalues.
+    fn has_dynamic_ancestor(&mut self) -> bool {
+        let mut ancestor_has_with = false;
+
+        for ctx in self.contexts.iter_mut() {
+            if ancestor_has_with {
+                // If the ancestor has an active with stack, mark this
+                // lambda context as needing to capture it.
+                ctx.captures_with_stack = true;
+            } else {
+                // otherwise, check this context and move on
+                ancestor_has_with = ctx.scope.has_with();
+            }
+        }
+
+        ancestor_has_with
+    }
+
+    fn emit_force<N: ToSpan>(&mut self, node: &N) {
+        if let Some(&OpCode::OpConstant(c)) = self.chunk().last_op() {
+            if !self.chunk().get_constant(c).unwrap().is_thunk() {
+                // Optimization: Don't emit a force op for non-thunk constants, since they don't
+                // need one!
+                // TODO: this is probably doable for more ops (?)
+                return;
+            }
+        }
+
+        self.push_op(OpCode::OpForce, node);
+    }
+
+    fn emit_warning<N: ToSpan>(&mut self, node: &N, kind: WarningKind) {
+        let span = self.span_for(node);
+        self.warnings.push(EvalWarning { kind, span })
+    }
+
+    fn emit_error<N: ToSpan>(&mut self, node: &N, kind: ErrorKind) {
+        let span = self.span_for(node);
+        self.errors
+            .push(Error::new(kind, span, self.source.clone()))
+    }
+}
+
+/// Convert a non-dynamic string expression to a string if possible.
+fn expr_static_str(node: &ast::Str) -> Option<SmolStr> {
+    let mut parts = node.normalized_parts();
+
+    if parts.len() != 1 {
+        return None;
+    }
+
+    if let Some(ast::InterpolPart::Literal(lit)) = parts.pop() {
+        return Some(SmolStr::new(lit));
+    }
+
+    None
+}
+
+/// Convert the provided `ast::Attr` into a statically known string if
+/// possible.
+fn expr_static_attr_str(node: &ast::Attr) -> Option<SmolStr> {
+    match node {
+        ast::Attr::Ident(ident) => Some(ident.ident_token().unwrap().text().into()),
+        ast::Attr::Str(s) => expr_static_str(s),
+
+        // The dynamic node type is just a wrapper. C++ Nix does not care
+        // about the dynamic wrapper when determining whether the node
+        // itself is dynamic, it depends solely on the expression inside
+        // (i.e. `let ${"a"} = 1; in a` is valid).
+        ast::Attr::Dynamic(ref dynamic) => match dynamic.expr().unwrap() {
+            ast::Expr::Str(s) => expr_static_str(&s),
+            _ => None,
+        },
+    }
+}
+
+/// Create a delayed source-only builtin compilation, for a builtin
+/// which is written in Nix code.
+///
+/// **Important:** tvix *panics* if a builtin with invalid source code
+/// is supplied. This is because there is no user-friendly way to
+/// thread the errors out of this function right now.
+fn compile_src_builtin(
+    name: &'static str,
+    code: &str,
+    source: SourceCode,
+    weak: &Weak<GlobalsMap>,
+) -> Value {
+    use std::fmt::Write;
+
+    let parsed = rnix::ast::Root::parse(code);
+
+    if !parsed.errors().is_empty() {
+        let mut out = format!("BUG: code for source-builtin '{}' had parser errors", name);
+        for error in parsed.errors() {
+            writeln!(out, "{}", error).unwrap();
+        }
+
+        panic!("{}", out);
+    }
+
+    let file = source.add_file(format!("<src-builtins/{}.nix>", name), code.to_string());
+    let weak = weak.clone();
+
+    Value::Thunk(Thunk::new_suspended_native(Box::new(move || {
+        let result = compile(
+            &parsed.tree().expr().unwrap(),
+            None,
+            weak.upgrade().unwrap(),
+            &source,
+            &file,
+            &mut crate::observer::NoOpObserver {},
+        )
+        .map_err(|e| ErrorKind::NativeError {
+            gen_type: "derivation",
+            err: Box::new(e),
+        })?;
+
+        if !result.errors.is_empty() {
+            return Err(ErrorKind::ImportCompilerError {
+                path: format!("src-builtins/{}.nix", name).into(),
+                errors: result.errors,
+            });
+        }
+
+        Ok(Value::Thunk(Thunk::new_suspended(
+            result.lambda,
+            LightSpan::Actual { span: file.span },
+        )))
+    })))
+}
+
+/// Prepare the full set of globals available in evaluated code. These
+/// are constructed from the set of builtins supplied by the caller,
+/// which are made available globally under the `builtins` identifier.
+///
+/// A subset of builtins (specified by [`GLOBAL_BUILTINS`]) is
+/// available globally *iff* they are set.
+///
+/// Optionally adds the `import` feature if desired by the caller.
+pub fn prepare_globals(
+    builtins: Vec<(&'static str, Value)>,
+    src_builtins: Vec<(&'static str, &'static str)>,
+    source: SourceCode,
+    enable_import: bool,
+) -> Rc<GlobalsMap> {
+    Rc::new_cyclic(Box::new(move |weak: &Weak<GlobalsMap>| {
+        // First step is to construct the builtins themselves as
+        // `NixAttrs`.
+        let mut builtins: GlobalsMap = HashMap::from_iter(builtins);
+
+        // At this point, optionally insert `import` if enabled. To
+        // "tie the knot" of `import` needing the full set of globals
+        // to instantiate its compiler, the `Weak` reference is passed
+        // here.
+        if enable_import {
+            let import = Value::Builtin(import::builtins_import(weak, source.clone()));
+            builtins.insert("import", import);
+        }
+
+        // Next, the actual map of globals which the compiler will use
+        // to resolve identifiers is constructed.
+        let mut globals: GlobalsMap = HashMap::new();
+
+        // builtins contain themselves (`builtins.builtins`), which we
+        // can resolve by manually constructing a suspended thunk that
+        // dereferences the same weak pointer as above.
+        let weak_globals = weak.clone();
+        builtins.insert(
+            "builtins",
+            Value::Thunk(Thunk::new_suspended_native(Box::new(move || {
+                Ok(weak_globals
+                    .upgrade()
+                    .unwrap()
+                    .get("builtins")
+                    .cloned()
+                    .unwrap())
+            }))),
+        );
+
+        // Insert top-level static value builtins.
+        globals.insert("true", Value::Bool(true));
+        globals.insert("false", Value::Bool(false));
+        globals.insert("null", Value::Null);
+
+        // If "source builtins" were supplied, compile them and insert
+        // them.
+        builtins.extend(src_builtins.into_iter().map(move |(name, code)| {
+            let compiled = compile_src_builtin(name, code, source.clone(), weak);
+            (name, compiled)
+        }));
+
+        // Construct the actual `builtins` attribute set and insert it
+        // in the global scope.
+        globals.insert(
+            "builtins",
+            Value::attrs(NixAttrs::from_iter(builtins.clone())),
+        );
+
+        // Finally, the builtins that should be globally available are
+        // "elevated" to the outer scope.
+        for global in GLOBAL_BUILTINS {
+            if let Some(builtin) = builtins.get(global).cloned() {
+                globals.insert(global, builtin);
+            }
+        }
+
+        globals
+    }))
+}
+
+pub fn compile(
+    expr: &ast::Expr,
+    location: Option<PathBuf>,
+    globals: Rc<GlobalsMap>,
+    source: &SourceCode,
+    file: &codemap::File,
+    observer: &mut dyn CompilerObserver,
+) -> EvalResult<CompilationOutput> {
+    let mut c = Compiler::new(location, globals.clone(), source, file, observer)?;
+
+    let root_span = c.span_for(expr);
+    let root_slot = c.scope_mut().declare_phantom(root_span, false);
+    c.compile(root_slot, expr.clone());
+
+    // The final operation of any top-level Nix program must always be
+    // `OpForce`. A thunk should not be returned to the user in an
+    // unevaluated state (though in practice, a value *containing* a
+    // thunk might be returned).
+    c.emit_force(expr);
+    c.push_op(OpCode::OpReturn, &root_span);
+
+    let lambda = Rc::new(c.contexts.pop().unwrap().lambda);
+    c.observer.observe_compiled_toplevel(&lambda);
+
+    Ok(CompilationOutput {
+        lambda,
+        warnings: c.warnings,
+        errors: c.errors,
+        globals,
+    })
+}
diff --git a/tvix/eval/src/compiler/optimiser.rs b/tvix/eval/src/compiler/optimiser.rs
new file mode 100644
index 0000000000..48960d355c
--- /dev/null
+++ b/tvix/eval/src/compiler/optimiser.rs
@@ -0,0 +1,125 @@
+//! Helper functions for extending the compiler with more linter-like
+//! functionality while compiling (i.e. smarter warnings).
+
+use super::*;
+
+use ast::Expr;
+
+/// Optimise the given expression where possible.
+pub(super) fn optimise_expr(c: &mut Compiler, slot: LocalIdx, expr: ast::Expr) -> ast::Expr {
+    match expr {
+        Expr::BinOp(_) => optimise_bin_op(c, slot, expr),
+        _ => expr,
+    }
+}
+
+enum LitBool {
+    Expr(Expr),
+    True(Expr),
+    False(Expr),
+}
+
+/// Is this a literal boolean, or something else?
+fn is_lit_bool(expr: ast::Expr) -> LitBool {
+    if let ast::Expr::Ident(ident) = &expr {
+        match ident.ident_token().unwrap().text() {
+            "true" => LitBool::True(expr),
+            "false" => LitBool::False(expr),
+            _ => LitBool::Expr(expr),
+        }
+    } else {
+        LitBool::Expr(expr)
+    }
+}
+
+/// Detect useless binary operations (i.e. useless bool comparisons).
+fn optimise_bin_op(c: &mut Compiler, slot: LocalIdx, expr: ast::Expr) -> ast::Expr {
+    use ast::BinOpKind;
+
+    // bail out of this check if the user has overridden either `true`
+    // or `false` identifiers. Note that they will have received a
+    // separate warning about this for shadowing the global(s).
+    if c.is_user_defined("true") || c.is_user_defined("false") {
+        return expr;
+    }
+
+    if let Expr::BinOp(op) = &expr {
+        let lhs = is_lit_bool(op.lhs().unwrap());
+        let rhs = is_lit_bool(op.rhs().unwrap());
+
+        match (op.operator().unwrap(), lhs, rhs) {
+            // useless `false` arm in `||` expression
+            (BinOpKind::Or, LitBool::False(f), LitBool::Expr(other))
+            | (BinOpKind::Or, LitBool::Expr(other), LitBool::False(f)) => {
+                c.emit_warning(
+                    &f,
+                    WarningKind::UselessBoolOperation(
+                        "this `false` has no effect on the result of the comparison",
+                    ),
+                );
+
+                return other;
+            }
+
+            // useless `true` arm in `&&` expression
+            (BinOpKind::And, LitBool::True(t), LitBool::Expr(other))
+            | (BinOpKind::And, LitBool::Expr(other), LitBool::True(t)) => {
+                c.emit_warning(
+                    &t,
+                    WarningKind::UselessBoolOperation(
+                        "this `true` has no effect on the result of the comparison",
+                    ),
+                );
+
+                return other;
+            }
+
+            // useless `||` expression (one arm is `true`), return
+            // `true` directly (and warn about dead code on the right)
+            (BinOpKind::Or, LitBool::True(t), LitBool::Expr(other)) => {
+                c.emit_warning(
+                    op,
+                    WarningKind::UselessBoolOperation("this expression is always true"),
+                );
+
+                c.compile_dead_code(slot, other);
+
+                return t;
+            }
+
+            (BinOpKind::Or, _, LitBool::True(t)) | (BinOpKind::Or, LitBool::True(t), _) => {
+                c.emit_warning(
+                    op,
+                    WarningKind::UselessBoolOperation("this expression is always true"),
+                );
+
+                return t;
+            }
+
+            // useless `&&` expression (one arm is `false), same as above
+            (BinOpKind::And, LitBool::False(f), LitBool::Expr(other)) => {
+                c.emit_warning(
+                    op,
+                    WarningKind::UselessBoolOperation("this expression is always false"),
+                );
+
+                c.compile_dead_code(slot, other);
+
+                return f;
+            }
+
+            (BinOpKind::And, _, LitBool::False(f)) | (BinOpKind::Or, LitBool::False(f), _) => {
+                c.emit_warning(
+                    op,
+                    WarningKind::UselessBoolOperation("this expression is always false"),
+                );
+
+                return f;
+            }
+
+            _ => { /* nothing to optimise */ }
+        }
+    }
+
+    expr
+}
diff --git a/tvix/eval/src/compiler/scope.rs b/tvix/eval/src/compiler/scope.rs
new file mode 100644
index 0000000000..892727c107
--- /dev/null
+++ b/tvix/eval/src/compiler/scope.rs
@@ -0,0 +1,378 @@
+//! This module implements the scope-tracking logic of the Tvix
+//! compiler.
+//!
+//! Scoping in Nix is fairly complicated, there are features like
+//! mutually recursive bindings, `with`, upvalue capturing, and so
+//! on that introduce a fair bit of complexity.
+//!
+//! Tvix attempts to do as much of the heavy lifting of this at
+//! compile time, and leave the runtime to mostly deal with known
+//! stack indices. To do this, the compiler simulates where locals
+//! will be at runtime using the data structures implemented here.
+
+use std::{
+    collections::{hash_map, HashMap},
+    ops::Index,
+};
+
+use smol_str::SmolStr;
+
+use crate::opcode::{StackIdx, UpvalueIdx};
+
+#[derive(Debug)]
+enum LocalName {
+    /// Normally declared local with a statically known name.
+    Ident(String),
+
+    /// Phantom stack value (e.g. attribute set used for `with`) that
+    /// must be accounted for to calculate correct stack offsets.
+    Phantom,
+}
+
+/// Represents a single local already known to the compiler.
+#[derive(Debug)]
+pub struct Local {
+    /// Identifier of this local. This is always a statically known
+    /// value (Nix does not allow dynamic identifier names in locals),
+    /// or a "phantom" value not accessible by users.
+    name: LocalName,
+
+    /// Source span at which this local was declared.
+    pub span: codemap::Span,
+
+    /// Scope depth of this local.
+    pub depth: usize,
+
+    /// Is this local initialised?
+    pub initialised: bool,
+
+    /// Is this local known to have been used at all?
+    pub used: bool,
+
+    /// Does this local need to be finalised after the enclosing scope
+    /// is completely constructed?
+    pub needs_finaliser: bool,
+
+    /// Does this local's upvalues contain a reference to itself?
+    pub must_thunk: bool,
+}
+
+impl Local {
+    /// Retrieve the name of the given local (if available).
+    pub fn name(&self) -> Option<SmolStr> {
+        match &self.name {
+            LocalName::Phantom => None,
+            LocalName::Ident(name) => Some(SmolStr::new(name)),
+        }
+    }
+
+    /// Is this local intentionally ignored? (i.e. name starts with `_`)
+    pub fn is_ignored(&self) -> bool {
+        match &self.name {
+            LocalName::Ident(name) => name.starts_with('_'),
+            LocalName::Phantom => false,
+        }
+    }
+}
+
+/// Represents the current position of an identifier as resolved in a scope.
+pub enum LocalPosition {
+    /// Local is not known in this scope.
+    Unknown,
+
+    /// Local is known at the given local index.
+    Known(LocalIdx),
+
+    /// Local is known, but is being accessed recursively within its
+    /// own initialisation. Depending on context, this is either an
+    /// error or forcing a closure/thunk.
+    Recursive(LocalIdx),
+}
+
+/// Represents the different ways in which upvalues can be captured in
+/// closures or thunks.
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub enum UpvalueKind {
+    /// This upvalue captures a local from the stack.
+    Local(LocalIdx),
+
+    /// This upvalue captures an enclosing upvalue.
+    Upvalue(UpvalueIdx),
+}
+
+#[derive(Clone, Debug)]
+pub struct Upvalue {
+    pub kind: UpvalueKind,
+    pub span: codemap::Span,
+}
+
+/// The index of a local in the scope's local array at compile time.
+#[repr(transparent)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd)]
+pub struct LocalIdx(usize);
+
+/// Helper struct for indexing over `Scope::locals` by name.
+#[derive(Debug)]
+enum ByName {
+    Single(LocalIdx),
+    Shadowed(Vec<LocalIdx>),
+}
+
+impl ByName {
+    /// Add an additional index for this name.
+    fn add_idx(&mut self, new: LocalIdx) {
+        match self {
+            ByName::Shadowed(indices) => indices.push(new),
+            ByName::Single(idx) => {
+                *self = ByName::Shadowed(vec![*idx, new]);
+            }
+        }
+    }
+
+    /// Remove the most recent index for this name, unless it is a
+    /// single. Returns `true` if an entry was removed.
+    fn remove_idx(&mut self) -> bool {
+        match self {
+            ByName::Single(_) => false,
+            ByName::Shadowed(indices) => match indices[..] {
+                [fst, _snd] => {
+                    *self = ByName::Single(fst);
+                    true
+                }
+                _ => {
+                    indices.pop();
+                    true
+                }
+            },
+        }
+    }
+
+    /// Return the most recent index.
+    pub fn index(&self) -> LocalIdx {
+        match self {
+            ByName::Single(idx) => *idx,
+            ByName::Shadowed(vec) => *vec.last().unwrap(),
+        }
+    }
+}
+
+/// Represents a scope known during compilation, which can be resolved
+/// directly to stack indices.
+#[derive(Debug, Default)]
+pub struct Scope {
+    locals: Vec<Local>,
+    pub upvalues: Vec<Upvalue>,
+
+    /// Secondary by-name index over locals.
+    by_name: HashMap<String, ByName>,
+
+    /// How many scopes "deep" are these locals?
+    scope_depth: usize,
+
+    /// Current size of the `with`-stack at runtime.
+    with_stack_size: usize,
+}
+
+impl Index<LocalIdx> for Scope {
+    type Output = Local;
+
+    fn index(&self, index: LocalIdx) -> &Self::Output {
+        &self.locals[index.0]
+    }
+}
+
+impl Scope {
+    /// Inherit scope details from a parent scope (required for
+    /// correctly nesting scopes in lambdas and thunks when special
+    /// scope features like dynamic resolution are present).
+    pub fn inherit(&self) -> Self {
+        Self {
+            scope_depth: self.scope_depth + 1,
+            with_stack_size: self.with_stack_size,
+            ..Default::default()
+        }
+    }
+
+    /// Increase the `with`-stack size of this scope.
+    pub fn push_with(&mut self) {
+        self.with_stack_size += 1;
+    }
+
+    /// Decrease the `with`-stack size of this scope.
+    pub fn pop_with(&mut self) {
+        self.with_stack_size -= 1;
+    }
+
+    /// Does this scope currently require dynamic runtime resolution
+    /// of identifiers that could not be found?
+    pub fn has_with(&self) -> bool {
+        self.with_stack_size > 0
+    }
+
+    /// Resolve the stack index of a statically known local.
+    pub fn resolve_local(&mut self, name: &str) -> LocalPosition {
+        if let Some(by_name) = self.by_name.get(name) {
+            let idx = by_name.index();
+            let local = self
+                .locals
+                .get_mut(idx.0)
+                .expect("invalid compiler state: indexed local missing");
+
+            local.used = true;
+
+            // This local is still being initialised, meaning that
+            // we know its final runtime stack position, but it is
+            // not yet on the stack.
+            if !local.initialised {
+                return LocalPosition::Recursive(idx);
+            }
+
+            return LocalPosition::Known(idx);
+        }
+
+        LocalPosition::Unknown
+    }
+
+    /// Declare a local variable that occupies a stack slot and should
+    /// be accounted for, but is not directly accessible by users
+    /// (e.g. attribute sets used for `with`).
+    pub fn declare_phantom(&mut self, span: codemap::Span, initialised: bool) -> LocalIdx {
+        let idx = self.locals.len();
+        self.locals.push(Local {
+            initialised,
+            span,
+            name: LocalName::Phantom,
+            depth: self.scope_depth,
+            needs_finaliser: false,
+            must_thunk: false,
+            used: true,
+        });
+
+        LocalIdx(idx)
+    }
+
+    /// Declare an uninitialised, named local variable.
+    ///
+    /// Returns the `LocalIdx` of the new local, and optionally the
+    /// index of a previous local shadowed by this one.
+    pub fn declare_local(
+        &mut self,
+        name: String,
+        span: codemap::Span,
+    ) -> (LocalIdx, Option<LocalIdx>) {
+        let idx = LocalIdx(self.locals.len());
+        self.locals.push(Local {
+            name: LocalName::Ident(name.clone()),
+            span,
+            depth: self.scope_depth,
+            initialised: false,
+            needs_finaliser: false,
+            must_thunk: false,
+            used: false,
+        });
+
+        let mut shadowed = None;
+        match self.by_name.entry(name) {
+            hash_map::Entry::Occupied(mut entry) => {
+                let existing = entry.get_mut();
+                shadowed = Some(existing.index());
+                existing.add_idx(idx);
+            }
+            hash_map::Entry::Vacant(entry) => {
+                entry.insert(ByName::Single(idx));
+            }
+        }
+
+        (idx, shadowed)
+    }
+
+    /// Mark local as initialised after compiling its expression.
+    pub fn mark_initialised(&mut self, idx: LocalIdx) {
+        self.locals[idx.0].initialised = true;
+    }
+
+    /// Mark local as needing a finaliser.
+    pub fn mark_needs_finaliser(&mut self, idx: LocalIdx) {
+        self.locals[idx.0].needs_finaliser = true;
+    }
+
+    /// Mark local as must be wrapped in a thunk.  This happens if
+    /// the local has a reference to itself in its upvalues.
+    pub fn mark_must_thunk(&mut self, idx: LocalIdx) {
+        self.locals[idx.0].must_thunk = true;
+    }
+
+    /// Compute the runtime stack index for a given local by
+    /// accounting for uninitialised variables at scopes below this
+    /// one.
+    pub fn stack_index(&self, idx: LocalIdx) -> StackIdx {
+        let uninitialised_count = self.locals[..(idx.0)]
+            .iter()
+            .filter(|l| !l.initialised && self[idx].depth > l.depth)
+            .count();
+
+        StackIdx(idx.0 - uninitialised_count)
+    }
+
+    /// Increase the current scope depth (e.g. within a new bindings
+    /// block, or `with`-scope).
+    pub fn begin_scope(&mut self) {
+        self.scope_depth += 1;
+    }
+
+    /// Decrease the scope depth and remove all locals still tracked
+    /// for the current scope.
+    ///
+    /// Returns the count of locals that were dropped while marked as
+    /// initialised (used by the compiler to determine whether to emit
+    /// scope cleanup operations), as well as the spans of the
+    /// definitions of unused locals (used by the compiler to emit
+    /// unused binding warnings).
+    pub fn end_scope(&mut self) -> (usize, Vec<codemap::Span>) {
+        debug_assert!(self.scope_depth != 0, "can not end top scope");
+
+        let mut pops = 0;
+        let mut unused_spans = vec![];
+
+        // TL;DR - iterate from the back while things belonging to the
+        // ended scope still exist.
+        while self.locals.last().unwrap().depth == self.scope_depth {
+            if let Some(local) = self.locals.pop() {
+                // pop the local from the stack if it was actually
+                // initialised
+                if local.initialised {
+                    pops += 1;
+                }
+
+                // analyse whether the local was accessed during its
+                // lifetime, and emit a warning otherwise (unless the
+                // user explicitly chose to ignore it by prefixing the
+                // identifier with `_`)
+                if !local.used && !local.is_ignored() {
+                    unused_spans.push(local.span);
+                }
+
+                // remove the by-name index if this was a named local
+                if let LocalName::Ident(name) = local.name {
+                    if let hash_map::Entry::Occupied(mut entry) = self.by_name.entry(name) {
+                        // If no removal occured through `remove_idx`
+                        // (i.e. there was no shadowing going on),
+                        // nuke the whole entry.
+                        if !entry.get_mut().remove_idx() {
+                            entry.remove();
+                        }
+                    }
+                }
+            }
+        }
+
+        self.scope_depth -= 1;
+
+        (pops, unused_spans)
+    }
+
+    /// Access the current scope depth.
+    pub fn scope_depth(&self) -> usize {
+        self.scope_depth
+    }
+}
diff --git a/tvix/eval/src/errors.rs b/tvix/eval/src/errors.rs
index fb9f3b6ec5..652252dadf 100644
--- a/tvix/eval/src/errors.rs
+++ b/tvix/eval/src/errors.rs
@@ -1,25 +1,1109 @@
-use std::fmt::Display;
+use std::error;
+use std::io;
+use std::path::PathBuf;
+use std::rc::Rc;
+use std::str::Utf8Error;
+use std::string::FromUtf8Error;
+use std::sync::Arc;
+use std::{fmt::Debug, fmt::Display, num::ParseIntError};
+
+use codemap::{File, Span};
+use codemap_diagnostic::{ColorConfig, Diagnostic, Emitter, Level, SpanLabel, SpanStyle};
+use smol_str::SmolStr;
+use xml::writer::Error as XmlError;
+
+use crate::spans::ToSpan;
+use crate::value::{CoercionKind, NixString};
+use crate::{SourceCode, Value};
+
+/// "CatchableErrorKind" errors -- those which can be detected by
+/// `builtins.tryEval`.
+///
+/// Note: this type is deliberately *not* incorporated as a variant
+/// of ErrorKind, because then Result<Value,ErrorKind> would have
+/// redundant representations for catchable errors, which would make
+/// it too easy to handle errors incorrectly:
+///
+///   - Ok(Value::Catchable(cek))
+///   - Err(ErrorKind::ThisVariantDoesNotExist(cek))
+///
+/// Because CatchableErrorKind is not a variant of ErrorKind, you
+/// will often see functions which return a type like:
+///
+///   Result<Result<T,CatchableErrorKind>,ErrorKind>
+///
+/// ... where T is any type other than Value.  This is unfortunate,
+/// because Rust's magic `?`-syntax does not work on nested Result
+/// values like this.
+// TODO(amjoseph): investigate result<T,Either<CatchableErrorKind,ErrorKind>>
+#[derive(Clone, Debug)]
+pub enum CatchableErrorKind {
+    Throw(Box<str>),
+    AssertionFailed,
+    UnimplementedFeature(Box<str>),
+    /// Resolving a user-supplied angle brackets path literal failed in some way.
+    NixPathResolution(Box<str>),
+}
+
+impl Display for CatchableErrorKind {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            CatchableErrorKind::Throw(s) => write!(f, "error thrown: {}", s),
+            CatchableErrorKind::AssertionFailed => write!(f, "assertion failed"),
+            CatchableErrorKind::UnimplementedFeature(s) => {
+                write!(f, "feature {} is not implemented yet", s)
+            }
+            CatchableErrorKind::NixPathResolution(s) => {
+                write!(f, "Nix path entry could not be resolved: {}", s)
+            }
+        }
+    }
+}
+
+#[derive(Clone, Debug)]
+pub enum ErrorKind {
+    /// These are user-generated errors through builtins.
+    Abort(String),
+
+    DivisionByZero,
 
-#[derive(Debug)]
-pub enum Error {
     DuplicateAttrsKey {
         key: String,
     },
 
-    InvalidKeyType {
-        given: &'static str,
+    /// Attempted to specify an invalid key type (e.g. integer) in a
+    /// dynamic attribute name.
+    InvalidAttributeName(Value),
+
+    AttributeNotFound {
+        name: String,
     },
 
+    /// Attempted to index into a list beyond its boundaries.
+    IndexOutOfBounds {
+        index: i64,
+    },
+
+    /// Attempted to call `builtins.tail` on an empty list.
+    TailEmptyList,
+
     TypeError {
         expected: &'static str,
         actual: &'static str,
     },
+
+    Incomparable {
+        lhs: &'static str,
+        rhs: &'static str,
+    },
+
+    /// Resolving a user-supplied relative or home-relative path literal failed in some way.
+    RelativePathResolution(String),
+
+    /// Dynamic keys are not allowed in some scopes.
+    DynamicKeyInScope(&'static str),
+
+    /// Unknown variable in statically known scope.
+    UnknownStaticVariable,
+
+    /// Unknown variable in dynamic scope (with, rec, ...).
+    UnknownDynamicVariable(String),
+
+    /// User is defining the same variable twice at the same depth.
+    VariableAlreadyDefined(Span),
+
+    /// Attempt to call something that is not callable.
+    NotCallable(&'static str),
+
+    /// Infinite recursion encountered while forcing thunks.
+    InfiniteRecursion {
+        first_force: Span,
+        suspended_at: Option<Span>,
+        content_span: Option<Span>,
+    },
+
+    ParseErrors(Vec<rnix::parser::ParseError>),
+
+    /// An error occured while executing some native code (e.g. a
+    /// builtin), and needs to be chained up.
+    NativeError {
+        gen_type: &'static str,
+        err: Box<Error>,
+    },
+
+    /// An error occured while executing Tvix bytecode, but needs to
+    /// be chained up.
+    BytecodeError(Box<Error>),
+
+    /// Given type can't be coerced to a string in the respective context
+    NotCoercibleToString {
+        from: &'static str,
+        kind: CoercionKind,
+    },
+
+    /// The given string doesn't represent an absolute path
+    NotAnAbsolutePath(PathBuf),
+
+    /// An error occurred when parsing an integer
+    ParseIntError(ParseIntError),
+
+    // Errors specific to nested attribute sets and merges thereof.
+    /// Nested attributes can not be merged with an inherited value.
+    UnmergeableInherit {
+        name: SmolStr,
+    },
+
+    /// Nested attributes can not be merged with values that are not
+    /// literal attribute sets.
+    UnmergeableValue,
+
+    /// Parse errors occured while importing a file.
+    ImportParseError {
+        path: PathBuf,
+        file: Arc<File>,
+        errors: Vec<rnix::parser::ParseError>,
+    },
+
+    /// Compilation errors occured while importing a file.
+    ImportCompilerError {
+        path: PathBuf,
+        errors: Vec<Error>,
+    },
+
+    /// I/O errors
+    IO {
+        path: Option<PathBuf>,
+        error: Rc<io::Error>,
+    },
+
+    /// Errors parsing JSON, or serializing as JSON.
+    JsonError(String),
+
+    /// Nix value that can not be serialised to JSON.
+    NotSerialisableToJson(&'static str),
+
+    /// Errors converting TOML to a value
+    FromTomlError(String),
+
+    /// An unexpected argument was supplied to a function that takes formal parameters
+    UnexpectedArgument {
+        arg: NixString,
+        formals_span: Span,
+    },
+
+    /// Invalid UTF-8 was encoutered somewhere
+    Utf8,
+
+    /// Errors while serialising to XML.
+    Xml(Rc<XmlError>),
+
+    /// Variant for errors that bubble up to eval from other Tvix
+    /// components.
+    TvixError(Rc<dyn error::Error>),
+
+    /// Variant for code paths that are known bugs in Tvix (usually
+    /// issues with the compiler/VM interaction).
+    TvixBug {
+        msg: &'static str,
+        metadata: Option<Rc<dyn Debug>>,
+    },
+
+    /// Tvix internal warning for features triggered by users that are
+    /// not actually implemented yet, and without which eval can not
+    /// proceed.
+    NotImplemented(&'static str),
+
+    /// Internal variant which should disappear during error construction.
+    WithContext {
+        context: String,
+        underlying: Box<ErrorKind>,
+    },
+
+    /// Unexpected context string
+    UnexpectedContext,
+
+    /// Top-level evaluation result was a catchable Nix error, and
+    /// should fail the evaluation.
+    ///
+    /// This variant **must** only be used at the top-level of
+    /// tvix-eval when returning a result to the user, never inside of
+    /// eval code.
+    CatchableError(CatchableErrorKind),
+
+    /// Invalid hash type specified, must be one of "md5", "sha1", "sha256"
+    /// or "sha512"
+    UnknownHashType(String),
+}
+
+impl error::Error for Error {
+    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
+        match &self.kind {
+            ErrorKind::NativeError { err, .. } | ErrorKind::BytecodeError(err) => err.source(),
+            ErrorKind::ParseErrors(err) => err.first().map(|e| e as &dyn error::Error),
+            ErrorKind::ParseIntError(err) => Some(err),
+            ErrorKind::ImportParseError { errors, .. } => {
+                errors.first().map(|e| e as &dyn error::Error)
+            }
+            ErrorKind::ImportCompilerError { errors, .. } => {
+                errors.first().map(|e| e as &dyn error::Error)
+            }
+            ErrorKind::IO { error, .. } => Some(error.as_ref()),
+            ErrorKind::Xml(error) => Some(error.as_ref()),
+            ErrorKind::TvixError(error) => Some(error.as_ref()),
+            _ => None,
+        }
+    }
+}
+
+impl From<ParseIntError> for ErrorKind {
+    fn from(e: ParseIntError) -> Self {
+        Self::ParseIntError(e)
+    }
+}
+
+impl From<Utf8Error> for ErrorKind {
+    fn from(_: Utf8Error) -> Self {
+        Self::NotImplemented("FromUtf8Error not handled: https://b.tvl.fyi/issues/189")
+    }
+}
+
+impl From<FromUtf8Error> for ErrorKind {
+    fn from(_: FromUtf8Error) -> Self {
+        Self::NotImplemented("FromUtf8Error not handled: https://b.tvl.fyi/issues/189")
+    }
+}
+
+impl From<bstr::Utf8Error> for ErrorKind {
+    fn from(_: bstr::Utf8Error) -> Self {
+        Self::Utf8
+    }
+}
+
+impl From<bstr::FromUtf8Error> for ErrorKind {
+    fn from(_value: bstr::FromUtf8Error) -> Self {
+        Self::Utf8
+    }
+}
+
+impl From<XmlError> for ErrorKind {
+    fn from(err: XmlError) -> Self {
+        Self::Xml(Rc::new(err))
+    }
+}
+
+impl From<io::Error> for ErrorKind {
+    fn from(e: io::Error) -> Self {
+        ErrorKind::IO {
+            path: None,
+            error: Rc::new(e),
+        }
+    }
+}
+
+impl From<serde_json::Error> for ErrorKind {
+    fn from(err: serde_json::Error) -> Self {
+        // Can't just put the `serde_json::Error` in the ErrorKind since it doesn't impl `Clone`
+        Self::JsonError(err.to_string())
+    }
+}
+
+impl From<toml::de::Error> for ErrorKind {
+    fn from(err: toml::de::Error) -> Self {
+        Self::FromTomlError(format!("error in TOML serialization: {err}"))
+    }
+}
+
+#[derive(Clone, Debug)]
+pub struct Error {
+    pub kind: ErrorKind,
+    pub span: Span,
+    pub contexts: Vec<String>,
+    pub source: SourceCode,
+}
+
+impl Error {
+    pub fn new(mut kind: ErrorKind, span: Span, source: SourceCode) -> Self {
+        let mut contexts = vec![];
+        while let ErrorKind::WithContext {
+            context,
+            underlying,
+        } = kind
+        {
+            kind = *underlying;
+            contexts.push(context);
+        }
+
+        Error {
+            kind,
+            span,
+            contexts,
+            source,
+        }
+    }
+}
+
+impl Display for ErrorKind {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match &self {
+            ErrorKind::Abort(msg) => write!(f, "evaluation aborted: {}", msg),
+
+            ErrorKind::DivisionByZero => write!(f, "division by zero"),
+
+            ErrorKind::DuplicateAttrsKey { key } => {
+                write!(f, "attribute key '{}' already defined", key)
+            }
+
+            ErrorKind::InvalidAttributeName(val) => write!(
+                f,
+                "found attribute name '{}' of type '{}', but attribute names must be strings",
+                val,
+                val.type_of()
+            ),
+
+            ErrorKind::AttributeNotFound { name } => write!(
+                f,
+                "attribute with name '{}' could not be found in the set",
+                name
+            ),
+
+            ErrorKind::IndexOutOfBounds { index } => {
+                write!(f, "list index '{}' is out of bounds", index)
+            }
+
+            ErrorKind::TailEmptyList => write!(f, "'tail' called on an empty list"),
+
+            ErrorKind::TypeError { expected, actual } => write!(
+                f,
+                "expected value of type '{}', but found a '{}'",
+                expected, actual
+            ),
+
+            ErrorKind::Incomparable { lhs, rhs } => {
+                write!(f, "can not compare a {} with a {}", lhs, rhs)
+            }
+
+            ErrorKind::RelativePathResolution(err) => {
+                write!(f, "could not resolve path: {}", err)
+            }
+
+            ErrorKind::DynamicKeyInScope(scope) => {
+                write!(f, "dynamically evaluated keys are not allowed in {}", scope)
+            }
+
+            ErrorKind::UnknownStaticVariable => write!(f, "variable not found"),
+
+            ErrorKind::UnknownDynamicVariable(name) => write!(
+                f,
+                r#"variable '{}' could not be found
+
+Note that this occured within a `with`-expression. The problem may be related
+to a missing value in the attribute set(s) included via `with`."#,
+                name
+            ),
+
+            ErrorKind::VariableAlreadyDefined(_) => write!(f, "variable has already been defined"),
+
+            ErrorKind::NotCallable(other_type) => {
+                write!(
+                    f,
+                    "only functions and builtins can be called, but this is a '{}'",
+                    other_type
+                )
+            }
+
+            ErrorKind::InfiniteRecursion { .. } => write!(f, "infinite recursion encountered"),
+
+            // Errors themselves ignored here & handled in Self::spans instead
+            ErrorKind::ParseErrors(_) => write!(f, "failed to parse Nix code:"),
+
+            ErrorKind::NativeError { gen_type, .. } => {
+                write!(f, "while evaluating this as native code ({})", gen_type)
+            }
+
+            ErrorKind::BytecodeError(_) => write!(f, "while evaluating this Nix code"),
+
+            ErrorKind::NotCoercibleToString { kind, from } => {
+                let kindly = if kind.strong { "strongly" } else { "weakly" };
+
+                let hint = if *from == "set" {
+                    ", missing a `__toString` or `outPath` attribute"
+                } else {
+                    ""
+                };
+
+                write!(f, "cannot ({kindly}) coerce {from} to a string{hint}")
+            }
+
+            ErrorKind::NotAnAbsolutePath(given) => {
+                write!(
+                    f,
+                    "string '{}' does not represent an absolute path",
+                    given.to_string_lossy()
+                )
+            }
+
+            ErrorKind::ParseIntError(err) => {
+                write!(f, "invalid integer: {}", err)
+            }
+
+            ErrorKind::UnmergeableInherit { name } => {
+                write!(
+                    f,
+                    "cannot merge a nested attribute set into the inherited entry '{}'",
+                    name
+                )
+            }
+
+            ErrorKind::UnmergeableValue => {
+                write!(
+                    f,
+                    "nested attribute sets or keys can only be merged with literal attribute sets"
+                )
+            }
+
+            // Errors themselves ignored here & handled in Self::spans instead
+            ErrorKind::ImportParseError { path, .. } => {
+                write!(
+                    f,
+                    "parse errors occured while importing '{}'",
+                    path.to_string_lossy()
+                )
+            }
+
+            ErrorKind::ImportCompilerError { path, .. } => {
+                writeln!(
+                    f,
+                    "compiler errors occured while importing '{}'",
+                    path.to_string_lossy()
+                )
+            }
+
+            ErrorKind::IO { path, error } => {
+                write!(f, "I/O error: ")?;
+                if let Some(path) = path {
+                    write!(f, "{}: ", path.display())?;
+                }
+                write!(f, "{error}")
+            }
+
+            ErrorKind::JsonError(msg) => {
+                write!(f, "Error converting JSON to a Nix value or back: {msg}")
+            }
+
+            ErrorKind::NotSerialisableToJson(_type) => {
+                write!(f, "a {} cannot be converted to JSON", _type)
+            }
+
+            ErrorKind::FromTomlError(msg) => {
+                write!(f, "Error converting TOML to a Nix value: {msg}")
+            }
+
+            ErrorKind::UnexpectedArgument { arg, .. } => {
+                write!(f, "Unexpected argument `{arg}` supplied to function",)
+            }
+
+            ErrorKind::Utf8 => {
+                write!(f, "Invalid UTF-8 in string")
+            }
+
+            ErrorKind::Xml(error) => write!(f, "failed to serialise to XML: {error}"),
+
+            ErrorKind::TvixError(inner_error) => {
+                write!(f, "{inner_error}")
+            }
+
+            ErrorKind::TvixBug { msg, metadata } => {
+                write!(f, "Tvix bug: {}", msg)?;
+
+                if let Some(metadata) = metadata {
+                    write!(f, "; metadata: {:?}", metadata)?;
+                }
+
+                Ok(())
+            }
+
+            ErrorKind::NotImplemented(feature) => {
+                write!(f, "feature not yet implemented in Tvix: {}", feature)
+            }
+
+            ErrorKind::WithContext { .. } => {
+                panic!("internal ErrorKind::WithContext variant leaked")
+            }
+
+            ErrorKind::UnexpectedContext => {
+                write!(f, "unexpected context string")
+            }
+
+            ErrorKind::CatchableError(inner) => {
+                write!(f, "{}", inner)
+            }
+
+            ErrorKind::UnknownHashType(hash_type) => {
+                write!(f, "unknown hash type '{}'", hash_type)
+            }
+        }
+    }
 }
 
 impl Display for Error {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        writeln!(f, "{:?}", self)
+        write!(f, "{}", self.kind)
     }
 }
 
 pub type EvalResult<T> = Result<T, Error>;
+
+/// Human-readable names for rnix syntaxes.
+fn name_for_syntax(syntax: &rnix::SyntaxKind) -> &'static str {
+    match syntax {
+        rnix::SyntaxKind::TOKEN_COMMENT => "a comment",
+        rnix::SyntaxKind::TOKEN_WHITESPACE => "whitespace",
+        rnix::SyntaxKind::TOKEN_ASSERT => "`assert`-keyword",
+        rnix::SyntaxKind::TOKEN_ELSE => "`else`-keyword",
+        rnix::SyntaxKind::TOKEN_IN => "`in`-keyword",
+        rnix::SyntaxKind::TOKEN_IF => "`if`-keyword",
+        rnix::SyntaxKind::TOKEN_INHERIT => "`inherit`-keyword",
+        rnix::SyntaxKind::TOKEN_LET => "`let`-keyword",
+        rnix::SyntaxKind::TOKEN_OR => "`or`-keyword",
+        rnix::SyntaxKind::TOKEN_REC => "`rec`-keyword",
+        rnix::SyntaxKind::TOKEN_THEN => "`then`-keyword",
+        rnix::SyntaxKind::TOKEN_WITH => "`with`-keyword",
+        rnix::SyntaxKind::TOKEN_L_BRACE => "{",
+        rnix::SyntaxKind::TOKEN_R_BRACE => "}",
+        rnix::SyntaxKind::TOKEN_L_BRACK => "[",
+        rnix::SyntaxKind::TOKEN_R_BRACK => "]",
+        rnix::SyntaxKind::TOKEN_ASSIGN => "=",
+        rnix::SyntaxKind::TOKEN_AT => "@",
+        rnix::SyntaxKind::TOKEN_COLON => ":",
+        rnix::SyntaxKind::TOKEN_COMMA => "`,`",
+        rnix::SyntaxKind::TOKEN_DOT => ".",
+        rnix::SyntaxKind::TOKEN_ELLIPSIS => "...",
+        rnix::SyntaxKind::TOKEN_QUESTION => "?",
+        rnix::SyntaxKind::TOKEN_SEMICOLON => ";",
+        rnix::SyntaxKind::TOKEN_L_PAREN => "(",
+        rnix::SyntaxKind::TOKEN_R_PAREN => ")",
+        rnix::SyntaxKind::TOKEN_CONCAT => "++",
+        rnix::SyntaxKind::TOKEN_INVERT => "!",
+        rnix::SyntaxKind::TOKEN_UPDATE => "//",
+        rnix::SyntaxKind::TOKEN_ADD => "+",
+        rnix::SyntaxKind::TOKEN_SUB => "-",
+        rnix::SyntaxKind::TOKEN_MUL => "*",
+        rnix::SyntaxKind::TOKEN_DIV => "/",
+        rnix::SyntaxKind::TOKEN_AND_AND => "&&",
+        rnix::SyntaxKind::TOKEN_EQUAL => "==",
+        rnix::SyntaxKind::TOKEN_IMPLICATION => "->",
+        rnix::SyntaxKind::TOKEN_LESS => "<",
+        rnix::SyntaxKind::TOKEN_LESS_OR_EQ => "<=",
+        rnix::SyntaxKind::TOKEN_MORE => ">",
+        rnix::SyntaxKind::TOKEN_MORE_OR_EQ => ">=",
+        rnix::SyntaxKind::TOKEN_NOT_EQUAL => "!=",
+        rnix::SyntaxKind::TOKEN_OR_OR => "||",
+        rnix::SyntaxKind::TOKEN_FLOAT => "a float",
+        rnix::SyntaxKind::TOKEN_IDENT => "an identifier",
+        rnix::SyntaxKind::TOKEN_INTEGER => "an integer",
+        rnix::SyntaxKind::TOKEN_INTERPOL_END => "}",
+        rnix::SyntaxKind::TOKEN_INTERPOL_START => "${",
+        rnix::SyntaxKind::TOKEN_PATH => "a path",
+        rnix::SyntaxKind::TOKEN_URI => "a literal URI",
+        rnix::SyntaxKind::TOKEN_STRING_CONTENT => "content of a string",
+        rnix::SyntaxKind::TOKEN_STRING_END => "\"",
+        rnix::SyntaxKind::TOKEN_STRING_START => "\"",
+
+        rnix::SyntaxKind::NODE_APPLY => "a function application",
+        rnix::SyntaxKind::NODE_ASSERT => "an assertion",
+        rnix::SyntaxKind::NODE_ATTRPATH => "an attribute path",
+        rnix::SyntaxKind::NODE_DYNAMIC => "a dynamic identifier",
+
+        rnix::SyntaxKind::NODE_IDENT => "an identifier",
+        rnix::SyntaxKind::NODE_IF_ELSE => "an `if`-expression",
+        rnix::SyntaxKind::NODE_SELECT => "a `select`-expression",
+        rnix::SyntaxKind::NODE_INHERIT => "inherited values",
+        rnix::SyntaxKind::NODE_INHERIT_FROM => "inherited values",
+        rnix::SyntaxKind::NODE_STRING => "a string",
+        rnix::SyntaxKind::NODE_INTERPOL => "an interpolation",
+        rnix::SyntaxKind::NODE_LAMBDA => "a function",
+        rnix::SyntaxKind::NODE_IDENT_PARAM => "a function parameter",
+        rnix::SyntaxKind::NODE_LEGACY_LET => "a legacy `let`-expression",
+        rnix::SyntaxKind::NODE_LET_IN => "a `let`-expression",
+        rnix::SyntaxKind::NODE_LIST => "a list",
+        rnix::SyntaxKind::NODE_BIN_OP => "a binary operator",
+        rnix::SyntaxKind::NODE_PAREN => "a parenthesised expression",
+        rnix::SyntaxKind::NODE_PATTERN => "a function argument pattern",
+        rnix::SyntaxKind::NODE_PAT_BIND => "an argument pattern binding",
+        rnix::SyntaxKind::NODE_PAT_ENTRY => "an argument pattern entry",
+        rnix::SyntaxKind::NODE_ROOT => "a Nix expression",
+        rnix::SyntaxKind::NODE_ATTR_SET => "an attribute set",
+        rnix::SyntaxKind::NODE_ATTRPATH_VALUE => "an attribute set entry",
+        rnix::SyntaxKind::NODE_UNARY_OP => "a unary operator",
+        rnix::SyntaxKind::NODE_LITERAL => "a literal value",
+        rnix::SyntaxKind::NODE_WITH => "a `with`-expression",
+        rnix::SyntaxKind::NODE_PATH => "a path",
+        rnix::SyntaxKind::NODE_HAS_ATTR => "`?`-operator",
+
+        // TODO(tazjin): unsure what these variants are, lets crash!
+        rnix::SyntaxKind::NODE_ERROR => todo!("NODE_ERROR found, tell tazjin!"),
+        rnix::SyntaxKind::TOKEN_ERROR => todo!("TOKEN_ERROR found, tell tazjin!"),
+        _ => todo!(),
+    }
+}
+
+/// Construct the string representation for a list of expected parser tokens.
+fn expected_syntax(one_of: &[rnix::SyntaxKind]) -> String {
+    match one_of.len() {
+        0 => "nothing".into(),
+        1 => format!("'{}'", name_for_syntax(&one_of[0])),
+        _ => {
+            let mut out: String = "one of: ".into();
+            let end = one_of.len() - 1;
+
+            for (idx, item) in one_of.iter().enumerate() {
+                if idx != 0 {
+                    out.push_str(", ");
+                } else if idx == end {
+                    out.push_str(", or ");
+                };
+
+                out.push_str(name_for_syntax(item));
+            }
+
+            out
+        }
+    }
+}
+
+/// Process a list of parse errors into a set of span labels, annotating parse
+/// errors.
+fn spans_for_parse_errors(file: &File, errors: &[rnix::parser::ParseError]) -> Vec<SpanLabel> {
+    // rnix has a tendency to emit some identical errors more than once, but
+    // they do not enhance the user experience necessarily, so we filter them
+    // out
+    let mut had_eof = false;
+
+    errors
+        .iter()
+        .enumerate()
+        .filter_map(|(idx, err)| {
+            let (span, label): (Span, String) = match err {
+                rnix::parser::ParseError::Unexpected(range) => (
+                    range.span_for(file),
+                    "found an unexpected syntax element here".into(),
+                ),
+
+                rnix::parser::ParseError::UnexpectedExtra(range) => (
+                    range.span_for(file),
+                    "found unexpected extra elements at the root of the expression".into(),
+                ),
+
+                rnix::parser::ParseError::UnexpectedWanted(found, range, wanted) => {
+                    let span = range.span_for(file);
+                    (
+                        span,
+                        format!(
+                            "found '{}', but expected {}",
+                            name_for_syntax(found),
+                            expected_syntax(wanted),
+                        ),
+                    )
+                }
+
+                rnix::parser::ParseError::UnexpectedEOF => {
+                    if had_eof {
+                        return None;
+                    }
+
+                    had_eof = true;
+
+                    (
+                        file.span,
+                        "code ended unexpectedly while the parser still expected more".into(),
+                    )
+                }
+
+                rnix::parser::ParseError::UnexpectedEOFWanted(wanted) => {
+                    had_eof = true;
+
+                    (
+                        file.span,
+                        format!(
+                            "code ended unexpectedly, but wanted {}",
+                            expected_syntax(wanted)
+                        ),
+                    )
+                }
+
+                rnix::parser::ParseError::DuplicatedArgs(range, name) => (
+                    range.span_for(file),
+                    format!(
+                        "the function argument pattern '{}' was bound more than once",
+                        name
+                    ),
+                ),
+
+                rnix::parser::ParseError::RecursionLimitExceeded => (
+                    file.span,
+                    "this code exceeds the parser's recursion limit, please report a Tvix bug"
+                        .to_string(),
+                ),
+
+                // TODO: can rnix even still throw this? it's semantic!
+                rnix::parser::ParseError::UnexpectedDoubleBind(range) => (
+                    range.span_for(file),
+                    "this pattern was bound more than once".into(),
+                ),
+
+                // The error enum is marked as `#[non_exhaustive]` in rnix,
+                // which disables the compiler error for missing a variant. This
+                // feature makes it possible for users to miss critical updates
+                // of enum variants for a more exciting runtime experience.
+                new => todo!("new parse error variant: {}", new),
+            };
+
+            Some(SpanLabel {
+                span,
+                label: Some(label),
+                style: if idx == 0 {
+                    SpanStyle::Primary
+                } else {
+                    SpanStyle::Secondary
+                },
+            })
+        })
+        .collect()
+}
+
+impl Error {
+    pub fn fancy_format_str(&self) -> String {
+        let mut out = vec![];
+        Emitter::vec(&mut out, Some(&*self.source.codemap())).emit(&self.diagnostics());
+        String::from_utf8_lossy(&out).to_string()
+    }
+
+    /// Render a fancy, human-readable output of this error and print
+    /// it to stderr.
+    pub fn fancy_format_stderr(&self) {
+        Emitter::stderr(ColorConfig::Auto, Some(&*self.source.codemap())).emit(&self.diagnostics());
+    }
+
+    /// Create the optional span label displayed as an annotation on
+    /// the underlined span of the error.
+    fn span_label(&self) -> Option<String> {
+        let label = match &self.kind {
+            ErrorKind::DuplicateAttrsKey { .. } => "in this attribute set",
+            ErrorKind::InvalidAttributeName(_) => "in this attribute set",
+            ErrorKind::RelativePathResolution(_) => "in this path literal",
+            ErrorKind::UnexpectedArgument { .. } => "in this function call",
+            ErrorKind::UnexpectedContext => "in this string",
+
+            // The spans for some errors don't have any more descriptive stuff
+            // in them, or we don't utilise it yet.
+            ErrorKind::Abort(_)
+            | ErrorKind::AttributeNotFound { .. }
+            | ErrorKind::IndexOutOfBounds { .. }
+            | ErrorKind::TailEmptyList
+            | ErrorKind::TypeError { .. }
+            | ErrorKind::Incomparable { .. }
+            | ErrorKind::DivisionByZero
+            | ErrorKind::DynamicKeyInScope(_)
+            | ErrorKind::UnknownStaticVariable
+            | ErrorKind::UnknownDynamicVariable(_)
+            | ErrorKind::VariableAlreadyDefined(_)
+            | ErrorKind::NotCallable(_)
+            | ErrorKind::InfiniteRecursion { .. }
+            | ErrorKind::ParseErrors(_)
+            | ErrorKind::NativeError { .. }
+            | ErrorKind::BytecodeError(_)
+            | ErrorKind::NotCoercibleToString { .. }
+            | ErrorKind::NotAnAbsolutePath(_)
+            | ErrorKind::ParseIntError(_)
+            | ErrorKind::UnmergeableInherit { .. }
+            | ErrorKind::UnmergeableValue
+            | ErrorKind::ImportParseError { .. }
+            | ErrorKind::ImportCompilerError { .. }
+            | ErrorKind::IO { .. }
+            | ErrorKind::JsonError(_)
+            | ErrorKind::NotSerialisableToJson(_)
+            | ErrorKind::FromTomlError(_)
+            | ErrorKind::Xml(_)
+            | ErrorKind::Utf8
+            | ErrorKind::TvixError(_)
+            | ErrorKind::TvixBug { .. }
+            | ErrorKind::NotImplemented(_)
+            | ErrorKind::WithContext { .. }
+            | ErrorKind::UnknownHashType(_)
+            | ErrorKind::CatchableError(_) => return None,
+        };
+
+        Some(label.into())
+    }
+
+    /// Return the unique error code for this variant which can be
+    /// used to refer users to documentation.
+    fn code(&self) -> &'static str {
+        match self.kind {
+            ErrorKind::CatchableError(CatchableErrorKind::Throw(_)) => "E001",
+            ErrorKind::Abort(_) => "E002",
+            ErrorKind::CatchableError(CatchableErrorKind::AssertionFailed) => "E003",
+            ErrorKind::InvalidAttributeName { .. } => "E004",
+            ErrorKind::AttributeNotFound { .. } => "E005",
+            ErrorKind::TypeError { .. } => "E006",
+            ErrorKind::Incomparable { .. } => "E007",
+            ErrorKind::CatchableError(CatchableErrorKind::NixPathResolution(_)) => "E008",
+            ErrorKind::DynamicKeyInScope(_) => "E009",
+            ErrorKind::UnknownStaticVariable => "E010",
+            ErrorKind::UnknownDynamicVariable(_) => "E011",
+            ErrorKind::VariableAlreadyDefined(_) => "E012",
+            ErrorKind::NotCallable(_) => "E013",
+            ErrorKind::InfiniteRecursion { .. } => "E014",
+            ErrorKind::ParseErrors(_) => "E015",
+            ErrorKind::DuplicateAttrsKey { .. } => "E016",
+            ErrorKind::NotCoercibleToString { .. } => "E018",
+            ErrorKind::IndexOutOfBounds { .. } => "E019",
+            ErrorKind::NotAnAbsolutePath(_) => "E020",
+            ErrorKind::ParseIntError(_) => "E021",
+            ErrorKind::TailEmptyList { .. } => "E023",
+            ErrorKind::UnmergeableInherit { .. } => "E024",
+            ErrorKind::UnmergeableValue => "E025",
+            ErrorKind::ImportParseError { .. } => "E027",
+            ErrorKind::ImportCompilerError { .. } => "E028",
+            ErrorKind::IO { .. } => "E029",
+            ErrorKind::JsonError { .. } => "E030",
+            ErrorKind::UnexpectedArgument { .. } => "E031",
+            ErrorKind::RelativePathResolution(_) => "E032",
+            ErrorKind::DivisionByZero => "E033",
+            ErrorKind::Xml(_) => "E034",
+            ErrorKind::FromTomlError(_) => "E035",
+            ErrorKind::NotSerialisableToJson(_) => "E036",
+            ErrorKind::UnexpectedContext => "E037",
+            ErrorKind::Utf8 => "E038",
+            ErrorKind::UnknownHashType(_) => "E039",
+
+            // Special error code for errors from other Tvix
+            // components. We may want to introduce a code namespacing
+            // system to have these errors pass codes through.
+            ErrorKind::TvixError(_) => "E997",
+
+            // Special error code that is not part of the normal
+            // ordering.
+            ErrorKind::TvixBug { .. } => "E998",
+
+            // Placeholder error while Tvix is under construction.
+            ErrorKind::CatchableError(CatchableErrorKind::UnimplementedFeature(_))
+            | ErrorKind::NotImplemented(_) => "E999",
+
+            // Chained errors should yield the code of the innermost
+            // error.
+            ErrorKind::NativeError { ref err, .. } | ErrorKind::BytecodeError(ref err) => {
+                err.code()
+            }
+
+            ErrorKind::WithContext { .. } => {
+                panic!("internal ErrorKind::WithContext variant leaked")
+            }
+        }
+    }
+
+    fn spans(&self) -> Vec<SpanLabel> {
+        let mut spans = match &self.kind {
+            ErrorKind::ImportParseError { errors, file, .. } => {
+                spans_for_parse_errors(file, errors)
+            }
+
+            ErrorKind::ParseErrors(errors) => {
+                let file = self.source.get_file(self.span);
+                spans_for_parse_errors(&file, errors)
+            }
+
+            ErrorKind::UnexpectedArgument { formals_span, .. } => {
+                vec![
+                    SpanLabel {
+                        label: self.span_label(),
+                        span: self.span,
+                        style: SpanStyle::Primary,
+                    },
+                    SpanLabel {
+                        label: Some("the accepted arguments".into()),
+                        span: *formals_span,
+                        style: SpanStyle::Secondary,
+                    },
+                ]
+            }
+
+            ErrorKind::InfiniteRecursion {
+                first_force,
+                suspended_at,
+                content_span,
+            } => {
+                let mut spans = vec![];
+
+                if let Some(content_span) = content_span {
+                    spans.push(SpanLabel {
+                        label: Some("this lazily-evaluated code".into()),
+                        span: *content_span,
+                        style: SpanStyle::Secondary,
+                    })
+                }
+
+                if let Some(suspended_at) = suspended_at {
+                    spans.push(SpanLabel {
+                        label: Some("which was instantiated here".into()),
+                        span: *suspended_at,
+                        style: SpanStyle::Secondary,
+                    })
+                }
+
+                spans.push(SpanLabel {
+                    label: Some("was first requested to be evaluated here".into()),
+                    span: *first_force,
+                    style: SpanStyle::Secondary,
+                });
+
+                spans.push(SpanLabel {
+                    label: Some("but then requested again here during its own evaluation".into()),
+                    span: self.span,
+                    style: SpanStyle::Primary,
+                });
+
+                spans
+            }
+
+            // All other errors pretty much have the same shape.
+            _ => {
+                vec![SpanLabel {
+                    label: self.span_label(),
+                    span: self.span,
+                    style: SpanStyle::Primary,
+                }]
+            }
+        };
+
+        for ctx in &self.contexts {
+            spans.push(SpanLabel {
+                label: Some(format!("while {}", ctx)),
+                span: self.span,
+                style: SpanStyle::Secondary,
+            });
+        }
+
+        spans
+    }
+
+    /// Create the primary diagnostic for a given error.
+    fn diagnostic(&self) -> Diagnostic {
+        Diagnostic {
+            level: Level::Error,
+            message: self.to_string(),
+            spans: self.spans(),
+            code: Some(self.code().into()),
+        }
+    }
+
+    /// Return the primary diagnostic and all further associated diagnostics (if
+    /// any) of an error.
+    fn diagnostics(&self) -> Vec<Diagnostic> {
+        match &self.kind {
+            ErrorKind::ImportCompilerError { errors, .. } => {
+                let mut out = vec![self.diagnostic()];
+                out.extend(errors.iter().map(|e| e.diagnostic()));
+                out
+            }
+
+            // When encountering either of these error kinds, we are dealing
+            // with the top of an error chain.
+            //
+            // An error chain creates a list of diagnostics which provide trace
+            // information.
+            //
+            // We don't know how deep this chain is, so we avoid recursing in
+            // this function while unrolling the chain.
+            ErrorKind::NativeError { err: next, .. } | ErrorKind::BytecodeError(next) => {
+                // Accumulated diagnostics to return.
+                let mut diagnostics: Vec<Diagnostic> = vec![];
+
+                // The next (inner) error to add to the diagnostics, after this
+                // one.
+                let mut next = *next.clone();
+
+                // Diagnostic message for *this* error.
+                let mut this_message = self.to_string();
+
+                // Primary span for *this* error.
+                let mut this_span = self.span;
+
+                // Diagnostic spans for *this* error.
+                let mut this_spans = self.spans();
+
+                loop {
+                    if is_new_span(
+                        this_span,
+                        diagnostics.last().and_then(|last| last.spans.last()),
+                    ) {
+                        diagnostics.push(Diagnostic {
+                            level: Level::Note,
+                            message: this_message,
+                            spans: this_spans,
+                            code: None, // only the top-level error has one
+                        });
+                    }
+
+                    this_message = next.to_string();
+                    this_span = next.span;
+                    this_spans = next.spans();
+
+                    match next.kind {
+                        ErrorKind::NativeError { err: inner, .. }
+                        | ErrorKind::BytecodeError(inner) => {
+                            next = *inner;
+                            continue;
+                        }
+                        _ => {
+                            diagnostics.extend(next.diagnostics());
+                            break;
+                        }
+                    }
+                }
+
+                diagnostics
+            }
+
+            _ => vec![self.diagnostic()],
+        }
+    }
+}
+
+// Check if this error is in a different span from its immediate ancestor.
+fn is_new_span(this_span: Span, parent: Option<&SpanLabel>) -> bool {
+    match parent {
+        None => true,
+        Some(parent) => parent.span != this_span,
+    }
+}
+
+// Convenience methods to add context on other types.
+pub trait AddContext {
+    /// Add context to the error-carrying type.
+    fn context<S: Into<String>>(self, ctx: S) -> Self;
+}
+
+impl AddContext for ErrorKind {
+    fn context<S: Into<String>>(self, ctx: S) -> Self {
+        ErrorKind::WithContext {
+            context: ctx.into(),
+            underlying: Box::new(self),
+        }
+    }
+}
+
+impl<T> AddContext for Result<T, ErrorKind> {
+    fn context<S: Into<String>>(self, ctx: S) -> Self {
+        self.map_err(|kind| kind.context(ctx))
+    }
+}
+
+impl<T> AddContext for Result<T, Error> {
+    fn context<S: Into<String>>(self, ctx: S) -> Self {
+        self.map_err(|err| Error {
+            kind: err.kind.context(ctx),
+            ..err
+        })
+    }
+}
diff --git a/tvix/eval/src/eval.rs b/tvix/eval/src/eval.rs
deleted file mode 100644
index 370aad494d..0000000000
--- a/tvix/eval/src/eval.rs
+++ /dev/null
@@ -1,20 +0,0 @@
-use rnix::{self, types::TypedNode};
-
-use crate::errors::EvalResult;
-
-pub fn interpret(code: String) -> EvalResult<String> {
-    let ast = rnix::parse(&code);
-
-    let errors = ast.errors();
-    if !errors.is_empty() {
-        todo!()
-    }
-
-    println!("{}", ast.root().dump());
-
-    let code = crate::compiler::compile(ast)?;
-    println!("code: {:?}", code);
-
-    let value = crate::vm::run_chunk(code)?;
-    Ok(format!("value: {} :: {}", value, value.type_of()))
-}
diff --git a/tvix/eval/src/io.rs b/tvix/eval/src/io.rs
new file mode 100644
index 0000000000..f775077af8
--- /dev/null
+++ b/tvix/eval/src/io.rs
@@ -0,0 +1,164 @@
+//! Interface for injecting I/O-related functionality into tvix-eval.
+//!
+//! The Nix language contains several builtins (e.g. `builtins.readDir`), as
+//! well as language feature (e.g. string-"coercion" of paths) that interact
+//! with the filesystem.
+//!
+//! The language evaluator implemented by this crate does not depend on any
+//! particular filesystem interaction model. Instead, this module provides a
+//! trait that can be implemented by tvix-eval callers to provide the
+//! functionality they desire.
+//!
+//! In theory this can be used to implement "mocked" filesystem interactions, or
+//! interaction with remote filesystems, etc.
+//!
+//! In the context of Nix builds, callers also use this interface to determine
+//! how store paths are opened and so on.
+
+use std::{
+    fs::File,
+    io,
+    path::{Path, PathBuf},
+};
+
+#[cfg(target_family = "unix")]
+use std::os::unix::ffi::OsStringExt;
+
+/// Types of files as represented by `builtins.readDir` in Nix.
+#[derive(Debug)]
+pub enum FileType {
+    Directory,
+    Regular,
+    Symlink,
+    Unknown,
+}
+
+/// Represents all possible filesystem interactions that exist in the Nix
+/// language, and that need to be executed somehow.
+///
+/// This trait is specifically *only* concerned with what is visible on the
+/// level of the language. All internal implementation details are not part of
+/// this trait.
+pub trait EvalIO {
+    /// Verify whether the file at the specified path exists.
+    ///
+    /// This is used for the following language evaluation cases:
+    ///
+    /// * checking whether a file added to the `NIX_PATH` actually exists when
+    ///   it is referenced in `<...>` brackets.
+    /// * `builtins.pathExists :: path -> bool`
+    fn path_exists(&self, path: &Path) -> io::Result<bool>;
+
+    /// Open the file at the specified path to a `io::Read`.
+    fn open(&self, path: &Path) -> io::Result<Box<dyn io::Read>>;
+
+    /// Read the directory at the specified path and return the names
+    /// of its entries associated with their [`FileType`].
+    ///
+    /// This is used for the following language evaluation cases:
+    ///
+    /// * `builtins.readDir :: path -> attrs<filename, filetype>`
+    fn read_dir(&self, path: &Path) -> io::Result<Vec<(bytes::Bytes, FileType)>>;
+
+    /// Import the given path. What this means depends on the implementation,
+    /// for example for a `std::io`-based implementation this might be a no-op,
+    /// while for a Tvix store this might be a copy of the given files to the
+    /// store.
+    ///
+    /// This is used for the following language evaluation cases:
+    ///
+    /// * string coercion of path literals (e.g. `/foo/bar`), which are expected
+    ///   to return a path
+    /// * `builtins.toJSON` on a path literal, also expected to return a path
+    fn import_path(&self, path: &Path) -> io::Result<PathBuf>;
+
+    /// Returns the root of the store directory, if such a thing
+    /// exists in the evaluation context.
+    ///
+    /// This is used for the following language evaluation cases:
+    ///
+    /// * `builtins.storeDir :: string`
+    fn store_dir(&self) -> Option<String> {
+        None
+    }
+}
+
+/// Implementation of [`EvalIO`] that simply uses the equivalent
+/// standard library functions, i.e. does local file-IO.
+#[cfg(feature = "impure")]
+pub struct StdIO;
+
+// TODO: we might want to make this whole impl to be target_family = "unix".
+#[cfg(feature = "impure")]
+impl EvalIO for StdIO {
+    fn path_exists(&self, path: &Path) -> io::Result<bool> {
+        path.try_exists()
+    }
+
+    fn open(&self, path: &Path) -> io::Result<Box<dyn io::Read>> {
+        Ok(Box::new(File::open(path)?))
+    }
+
+    fn read_dir(&self, path: &Path) -> io::Result<Vec<(bytes::Bytes, FileType)>> {
+        let mut result = vec![];
+
+        for entry in path.read_dir()? {
+            let entry = entry?;
+            let file_type = entry.metadata()?.file_type();
+
+            let val = if file_type.is_dir() {
+                FileType::Directory
+            } else if file_type.is_file() {
+                FileType::Regular
+            } else if file_type.is_symlink() {
+                FileType::Symlink
+            } else {
+                FileType::Unknown
+            };
+
+            result.push((entry.file_name().into_vec().into(), val))
+        }
+
+        Ok(result)
+    }
+
+    // this is a no-op for `std::io`, as the user can already refer to
+    // the path directly
+    fn import_path(&self, path: &Path) -> io::Result<PathBuf> {
+        Ok(path.to_path_buf())
+    }
+}
+
+/// Dummy implementation of [`EvalIO`], can be used in contexts where
+/// IO is not available but code should "pretend" that it is.
+pub struct DummyIO;
+
+impl EvalIO for DummyIO {
+    fn path_exists(&self, _: &Path) -> io::Result<bool> {
+        Err(io::Error::new(
+            io::ErrorKind::Unsupported,
+            "I/O methods are not implemented in DummyIO",
+        ))
+    }
+
+    fn open(&self, _: &Path) -> io::Result<Box<dyn io::Read>> {
+        Err(io::Error::new(
+            io::ErrorKind::Unsupported,
+            "I/O methods are not implemented in DummyIO",
+        ))
+    }
+
+    fn read_dir(&self, _: &Path) -> io::Result<Vec<(bytes::Bytes, FileType)>> {
+        Err(io::Error::new(
+            io::ErrorKind::Unsupported,
+            "I/O methods are not implemented in DummyIO",
+        ))
+    }
+
+    fn import_path(&self, _: &Path) -> io::Result<PathBuf> {
+        Err(io::Error::new(
+            io::ErrorKind::Unsupported,
+            "I/O methods are not implemented in DummyIO",
+        ))
+    }
+}
diff --git a/tvix/eval/src/lib.rs b/tvix/eval/src/lib.rs
new file mode 100644
index 0000000000..845964cb7e
--- /dev/null
+++ b/tvix/eval/src/lib.rs
@@ -0,0 +1,394 @@
+//! `tvix-eval` implements the evaluation of the Nix programming language in
+//! Tvix.
+//!
+//! It is designed to allow users to use Nix as a versatile language for
+//! different use-cases.
+//!
+//! This module exports the high-level functions and types needed for evaluating
+//! Nix code and interacting with the language's data structures.
+//!
+//! Nix has several language features that make use of impurities (such as
+//! reading from the NIX_PATH environment variable, or interacting with files).
+//! These features are optional and the API of this crate exposes functionality
+//! for controlling how they work.
+
+pub mod builtins;
+mod chunk;
+mod compiler;
+mod errors;
+mod io;
+pub mod observer;
+mod opcode;
+mod pretty_ast;
+mod source;
+mod spans;
+mod systems;
+mod upvalues;
+mod value;
+mod vm;
+mod warnings;
+
+mod nix_search_path;
+#[cfg(test)]
+mod properties;
+#[cfg(test)]
+mod test_utils;
+#[cfg(test)]
+mod tests;
+
+use std::path::PathBuf;
+use std::rc::Rc;
+use std::str::FromStr;
+use std::sync::Arc;
+
+use crate::compiler::GlobalsMap;
+use crate::observer::{CompilerObserver, RuntimeObserver};
+use crate::value::Lambda;
+use crate::vm::run_lambda;
+
+// Re-export the public interface used by other crates.
+pub use crate::compiler::{compile, prepare_globals, CompilationOutput};
+pub use crate::errors::{AddContext, CatchableErrorKind, Error, ErrorKind, EvalResult};
+pub use crate::io::{DummyIO, EvalIO, FileType};
+pub use crate::pretty_ast::pretty_print_expr;
+pub use crate::source::SourceCode;
+pub use crate::value::{NixContext, NixContextElement};
+pub use crate::vm::generators;
+pub use crate::warnings::{EvalWarning, WarningKind};
+pub use builtin_macros;
+
+pub use crate::value::{Builtin, CoercionKind, NixAttrs, NixList, NixString, Value};
+
+#[cfg(feature = "impure")]
+pub use crate::io::StdIO;
+
+/// An `Evaluation` represents how a piece of Nix code is evaluated. It can be
+/// instantiated and configured directly, or it can be accessed through the
+/// various simplified helper methods available below.
+///
+/// Public fields are intended to be set by the caller. Setting all
+/// fields is optional.
+pub struct Evaluation<'co, 'ro, IO> {
+    /// Source code map used for error reporting.
+    source_map: SourceCode,
+
+    /// Set of all builtins that should be available during the
+    /// evaluation.
+    ///
+    /// This defaults to all pure builtins. Users might want to add
+    /// the set of impure builtins, or other custom builtins.
+    pub builtins: Vec<(&'static str, Value)>,
+
+    /// Set of builtins that are implemented in Nix itself and should
+    /// be compiled and inserted in the builtins set.
+    pub src_builtins: Vec<(&'static str, &'static str)>,
+
+    /// Implementation of file-IO to use during evaluation, e.g. for
+    /// impure builtins.
+    ///
+    /// Defaults to [`DummyIO`] if not set explicitly.
+    pub io_handle: IO,
+
+    /// Determines whether the `import` builtin should be made
+    /// available. Note that this depends on the `io_handle` being
+    /// able to read the files specified as arguments to `import`.
+    pub enable_import: bool,
+
+    /// Determines whether the returned value should be strictly
+    /// evaluated, that is whether its list and attribute set elements
+    /// should be forced recursively.
+    pub strict: bool,
+
+    /// (optional) Nix search path, e.g. the value of `NIX_PATH` used
+    /// for resolving items on the search path (such as `<nixpkgs>`).
+    pub nix_path: Option<String>,
+
+    /// (optional) compiler observer for reporting on compilation
+    /// details, like the emitted bytecode.
+    pub compiler_observer: Option<&'co mut dyn CompilerObserver>,
+
+    /// (optional) runtime observer, for reporting on execution steps
+    /// of Nix code.
+    pub runtime_observer: Option<&'ro mut dyn RuntimeObserver>,
+}
+
+/// Result of evaluating a piece of Nix code. If evaluation succeeded, a value
+/// will be present (and potentially some warnings!). If evaluation failed,
+/// errors will be present.
+#[derive(Debug, Default)]
+pub struct EvaluationResult {
+    /// Nix value that the code evaluated to.
+    pub value: Option<Value>,
+
+    /// Errors that occured during evaluation (if any).
+    pub errors: Vec<Error>,
+
+    /// Warnings that occured during evaluation. Warnings are not critical, but
+    /// should be addressed either to modernise code or improve performance.
+    pub warnings: Vec<EvalWarning>,
+
+    /// AST node that was parsed from the code (on success only).
+    pub expr: Option<rnix::ast::Expr>,
+}
+
+impl<'co, 'ro, IO> Evaluation<'co, 'ro, IO>
+where
+    IO: AsRef<dyn EvalIO> + 'static,
+{
+    /// Initialize an `Evaluation`.
+    pub fn new(io_handle: IO, enable_import: bool) -> Self {
+        let mut builtins = builtins::pure_builtins();
+        builtins.extend(builtins::placeholders()); // these are temporary
+
+        Self {
+            source_map: SourceCode::default(),
+            enable_import,
+            io_handle,
+            builtins,
+            src_builtins: vec![],
+            strict: false,
+            nix_path: None,
+            compiler_observer: None,
+            runtime_observer: None,
+        }
+    }
+}
+
+impl<'co, 'ro> Evaluation<'co, 'ro, Box<dyn EvalIO>> {
+    /// Initialize an `Evaluation`, without the import statement available, and
+    /// all IO operations stubbed out.
+    pub fn new_pure() -> Self {
+        Self::new(Box::new(DummyIO) as Box<dyn EvalIO>, false)
+    }
+
+    #[cfg(feature = "impure")]
+    /// Configure an `Evaluation` to have impure features available
+    /// with the given I/O implementation.
+    ///
+    /// If no I/O implementation is supplied, [`StdIO`] is used by
+    /// default.
+    pub fn enable_impure(&mut self, io: Option<Box<dyn EvalIO>>) {
+        self.io_handle = io.unwrap_or_else(|| Box::new(StdIO) as Box<dyn EvalIO>);
+        self.enable_import = true;
+        self.builtins.extend(builtins::impure_builtins());
+
+        // Make `NIX_PATH` resolutions work by default, unless the
+        // user already overrode this with something else.
+        if self.nix_path.is_none() {
+            self.nix_path = std::env::var("NIX_PATH").ok();
+        }
+    }
+
+    #[cfg(feature = "impure")]
+    /// Initialise an `Evaluation`, with all impure features turned on by default.
+    pub fn new_impure() -> Self {
+        let mut eval = Self::new_pure();
+        eval.enable_impure(None);
+        eval
+    }
+}
+
+impl<'co, 'ro, IO> Evaluation<'co, 'ro, IO>
+where
+    IO: AsRef<dyn EvalIO> + 'static,
+{
+    /// Clone the reference to the contained source code map. This is used after
+    /// an evaluation for pretty error printing.
+    pub fn source_map(&self) -> SourceCode {
+        self.source_map.clone()
+    }
+
+    /// Only compile the provided source code, at an optional location of the
+    /// source code (i.e. path to the file it was read from; used for error
+    /// reporting, and for resolving relative paths in impure functions)
+    /// This does not *run* the code, it only provides analysis (errors and
+    /// warnings) of the compiler.
+    pub fn compile_only(
+        mut self,
+        code: impl AsRef<str>,
+        location: Option<PathBuf>,
+    ) -> EvaluationResult {
+        let mut result = EvaluationResult::default();
+        let source = self.source_map();
+
+        let location_str = location
+            .as_ref()
+            .map(|p| p.to_string_lossy().to_string())
+            .unwrap_or_else(|| "[code]".into());
+
+        let file = source.add_file(location_str, code.as_ref().to_string());
+
+        let mut noop_observer = observer::NoOpObserver::default();
+        let compiler_observer = self.compiler_observer.take().unwrap_or(&mut noop_observer);
+
+        parse_compile_internal(
+            &mut result,
+            code.as_ref(),
+            file,
+            location,
+            source,
+            self.builtins,
+            self.src_builtins,
+            self.enable_import,
+            compiler_observer,
+        );
+
+        result
+    }
+
+    /// Evaluate the provided source code, at an optional location of the source
+    /// code (i.e. path to the file it was read from; used for error reporting,
+    /// and for resolving relative paths in impure functions)
+    pub fn evaluate(
+        mut self,
+        code: impl AsRef<str>,
+        location: Option<PathBuf>,
+    ) -> EvaluationResult {
+        let mut result = EvaluationResult::default();
+        let source = self.source_map();
+
+        let location_str = location
+            .as_ref()
+            .map(|p| p.to_string_lossy().to_string())
+            .unwrap_or_else(|| "[code]".into());
+
+        let file = source.add_file(location_str, code.as_ref().to_string());
+
+        let mut noop_observer = observer::NoOpObserver::default();
+        let compiler_observer = self.compiler_observer.take().unwrap_or(&mut noop_observer);
+
+        // Insert a storeDir builtin *iff* a store directory is present.
+        if let Some(store_dir) = self.io_handle.as_ref().store_dir() {
+            self.builtins.push(("storeDir", store_dir.into()));
+        }
+
+        let (lambda, globals) = match parse_compile_internal(
+            &mut result,
+            code.as_ref(),
+            file.clone(),
+            location,
+            source.clone(),
+            self.builtins,
+            self.src_builtins,
+            self.enable_import,
+            compiler_observer,
+        ) {
+            None => return result,
+            Some(cr) => cr,
+        };
+
+        // If bytecode was returned, there were no errors and the
+        // code is safe to execute.
+
+        let nix_path = self
+            .nix_path
+            .as_ref()
+            .and_then(|s| match nix_search_path::NixSearchPath::from_str(s) {
+                Ok(path) => Some(path),
+                Err(err) => {
+                    result.warnings.push(EvalWarning {
+                        kind: WarningKind::InvalidNixPath(err.to_string()),
+                        span: file.span,
+                    });
+                    None
+                }
+            })
+            .unwrap_or_default();
+
+        let runtime_observer = self.runtime_observer.take().unwrap_or(&mut noop_observer);
+
+        let vm_result = run_lambda(
+            nix_path,
+            self.io_handle,
+            runtime_observer,
+            source.clone(),
+            globals,
+            lambda,
+            self.strict,
+        );
+
+        match vm_result {
+            Ok(mut runtime_result) => {
+                result.warnings.append(&mut runtime_result.warnings);
+                if let Value::Catchable(inner) = runtime_result.value {
+                    result.errors.push(Error::new(
+                        ErrorKind::CatchableError(*inner),
+                        file.span,
+                        source,
+                    ));
+                    return result;
+                }
+
+                result.value = Some(runtime_result.value);
+            }
+            Err(err) => {
+                result.errors.push(err);
+            }
+        }
+
+        result
+    }
+}
+
+/// Internal helper function for common parsing & compilation logic
+/// between the public functions.
+#[allow(clippy::too_many_arguments)] // internal API, no point making an indirection type
+fn parse_compile_internal(
+    result: &mut EvaluationResult,
+    code: &str,
+    file: Arc<codemap::File>,
+    location: Option<PathBuf>,
+    source: SourceCode,
+    builtins: Vec<(&'static str, Value)>,
+    src_builtins: Vec<(&'static str, &'static str)>,
+    enable_import: bool,
+    compiler_observer: &mut dyn CompilerObserver,
+) -> Option<(Rc<Lambda>, Rc<GlobalsMap>)> {
+    let parsed = rnix::ast::Root::parse(code);
+    let parse_errors = parsed.errors();
+
+    if !parse_errors.is_empty() {
+        result.errors.push(Error::new(
+            ErrorKind::ParseErrors(parse_errors.to_vec()),
+            file.span,
+            source,
+        ));
+        return None;
+    }
+
+    // At this point we know that the code is free of parse errors and
+    // we can continue to compile it. The expression is persisted in
+    // the result, in case the caller needs it for something.
+    result.expr = parsed.tree().expr();
+
+    let builtins =
+        crate::compiler::prepare_globals(builtins, src_builtins, source.clone(), enable_import);
+
+    let compiler_result = match compiler::compile(
+        result.expr.as_ref().unwrap(),
+        location,
+        builtins,
+        &source,
+        &file,
+        compiler_observer,
+    ) {
+        Ok(result) => result,
+        Err(err) => {
+            result.errors.push(err);
+            return None;
+        }
+    };
+
+    result.warnings = compiler_result.warnings;
+    result.errors.extend(compiler_result.errors);
+
+    // Short-circuit if errors exist at this point (do not pass broken
+    // bytecode to the runtime).
+    if !result.errors.is_empty() {
+        return None;
+    }
+
+    // Return the lambda (for execution) and the globals map (to
+    // ensure the invariant that the globals outlive the runtime).
+    Some((compiler_result.lambda, compiler_result.globals))
+}
diff --git a/tvix/eval/src/main.rs b/tvix/eval/src/main.rs
deleted file mode 100644
index 4cfa0a137a..0000000000
--- a/tvix/eval/src/main.rs
+++ /dev/null
@@ -1,54 +0,0 @@
-use std::{
-    env, fs,
-    io::{self, Write},
-    mem, process,
-};
-
-mod chunk;
-mod compiler;
-mod errors;
-mod eval;
-mod opcode;
-mod value;
-mod vm;
-
-fn main() {
-    let mut args = env::args();
-    if args.len() > 2 {
-        println!("Usage: tvix-eval [script]");
-        process::exit(1);
-    }
-
-    if let Some(file) = args.nth(1) {
-        run_file(&file);
-    } else {
-        run_prompt();
-    }
-}
-
-fn run_file(file: &str) {
-    let contents = fs::read_to_string(file).expect("failed to read the input file");
-
-    run(contents);
-}
-
-fn run_prompt() {
-    let mut line = String::new();
-
-    loop {
-        print!("> ");
-        io::stdout().flush().unwrap();
-        io::stdin()
-            .read_line(&mut line)
-            .expect("failed to read user input");
-        run(mem::take(&mut line));
-        line.clear();
-    }
-}
-
-fn run(code: String) {
-    match eval::interpret(code) {
-        Ok(result) => println!("=> {}", result),
-        Err(err) => eprintln!("{}", err),
-    }
-}
diff --git a/tvix/eval/src/nix_search_path.rs b/tvix/eval/src/nix_search_path.rs
new file mode 100644
index 0000000000..566ca12238
--- /dev/null
+++ b/tvix/eval/src/nix_search_path.rs
@@ -0,0 +1,256 @@
+use path_clean::PathClean;
+use std::convert::Infallible;
+use std::path::{Path, PathBuf};
+use std::str::FromStr;
+
+use crate::errors::{CatchableErrorKind, ErrorKind};
+use crate::EvalIO;
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+enum NixSearchPathEntry {
+    /// Resolve subdirectories of this path within `<...>` brackets. This
+    /// corresponds to bare paths within the `NIX_PATH` environment variable
+    ///
+    /// For example, with `NixSearchPathEntry::Path("/example")` and the following
+    /// directory structure:
+    ///
+    /// ```notrust
+    /// example
+    /// └── subdir
+    ///     └── grandchild
+    /// ```
+    ///
+    /// A Nix path literal `<subdir>` would resolve to `/example/subdir`, and a
+    /// Nix path literal `<subdir/grandchild>` would resolve to
+    /// `/example/subdir/grandchild`
+    Path(PathBuf),
+
+    /// Resolve paths starting with `prefix` as subdirectories of `path`. This
+    /// corresponds to `prefix=path` within the `NIX_PATH` environment variable.
+    ///
+    /// For example, with `NixSearchPathEntry::Prefix { prefix: "prefix", path:
+    /// "/example" }` and the following directory structure:
+    ///
+    /// ```notrust
+    /// example
+    /// └── subdir
+    ///     └── grandchild
+    /// ```
+    ///
+    /// A Nix path literal `<prefix/subdir>` would resolve to `/example/subdir`,
+    /// and a Nix path literal `<prefix/subdir/grandchild>` would resolve to
+    /// `/example/subdir/grandchild`
+    Prefix { prefix: PathBuf, path: PathBuf },
+}
+
+fn canonicalise(path: PathBuf) -> Result<PathBuf, ErrorKind> {
+    let absolute = if path.is_absolute() {
+        path
+    } else {
+        // TODO(tazjin): probably panics in wasm?
+        std::env::current_dir()
+            .map_err(|e| ErrorKind::IO {
+                path: Some(path.clone()),
+                error: e.into(),
+            })?
+            .join(path)
+    }
+    .clean();
+
+    Ok(absolute)
+}
+
+impl NixSearchPathEntry {
+    /// Determine whether this path entry matches the given lookup path.
+    ///
+    /// For bare paths, an entry is considered to match if a matching
+    /// file exists under it.
+    ///
+    /// For prefixed path, an entry matches if the prefix does.
+    // TODO(tazjin): verify these rules in the C++ impl, seems fishy.
+    fn resolve<IO>(&self, io: IO, lookup_path: &Path) -> Result<Option<PathBuf>, ErrorKind>
+    where
+        IO: AsRef<dyn EvalIO>,
+    {
+        let path = match self {
+            NixSearchPathEntry::Path(parent) => canonicalise(parent.join(lookup_path))?,
+
+            NixSearchPathEntry::Prefix { prefix, path } => {
+                if let Ok(child_path) = lookup_path.strip_prefix(prefix) {
+                    canonicalise(path.join(child_path))?
+                } else {
+                    return Ok(None);
+                }
+            }
+        };
+
+        if io.as_ref().path_exists(&path).map_err(|e| ErrorKind::IO {
+            path: Some(path.clone()),
+            error: e.into(),
+        })? {
+            Ok(Some(path))
+        } else {
+            Ok(None)
+        }
+    }
+}
+
+impl FromStr for NixSearchPathEntry {
+    type Err = Infallible;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        match s.split_once('=') {
+            Some((prefix, path)) => Ok(Self::Prefix {
+                prefix: prefix.into(),
+                path: path.into(),
+            }),
+            None => Ok(Self::Path(s.into())),
+        }
+    }
+}
+
+/// Struct implementing the format and path resolution rules of the `NIX_PATH`
+/// environment variable.
+///
+/// This struct can be constructed by parsing a string using the [`FromStr`]
+/// impl, or via [`str::parse`]. Nix `<...>` paths can then be resolved using
+/// [`NixSearchPath::resolve`].
+#[derive(Default, Debug, Clone, PartialEq, Eq)]
+pub struct NixSearchPath {
+    entries: Vec<NixSearchPathEntry>,
+}
+
+impl NixSearchPath {
+    /// Attempt to resolve the given `path` within this [`NixSearchPath`] using the
+    /// path resolution rules for `<...>`-style paths
+    pub fn resolve<P, IO>(
+        &self,
+        io: IO,
+        path: P,
+    ) -> Result<Result<PathBuf, CatchableErrorKind>, ErrorKind>
+    where
+        P: AsRef<Path>,
+        IO: AsRef<dyn EvalIO>,
+    {
+        let path = path.as_ref();
+        for entry in &self.entries {
+            if let Some(p) = entry.resolve(&io, path)? {
+                return Ok(Ok(p));
+            }
+        }
+        Ok(Err(CatchableErrorKind::NixPathResolution(
+            format!(
+                "path '{}' was not found in the Nix search path",
+                path.display()
+            )
+            .into_boxed_str(),
+        )))
+    }
+}
+
+impl FromStr for NixSearchPath {
+    type Err = Infallible;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        let entries = s
+            .split(':')
+            .map(|s| s.parse())
+            .collect::<Result<Vec<_>, _>>()?;
+        Ok(NixSearchPath { entries })
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    mod parse {
+        use super::*;
+
+        #[test]
+        fn bare_paths() {
+            assert_eq!(
+                NixSearchPath::from_str("/foo/bar:/baz").unwrap(),
+                NixSearchPath {
+                    entries: vec![
+                        NixSearchPathEntry::Path("/foo/bar".into()),
+                        NixSearchPathEntry::Path("/baz".into())
+                    ],
+                }
+            );
+        }
+
+        #[test]
+        fn mixed_prefix_and_paths() {
+            assert_eq!(
+                NixSearchPath::from_str("nixpkgs=/my/nixpkgs:/etc/nixos").unwrap(),
+                NixSearchPath {
+                    entries: vec![
+                        NixSearchPathEntry::Prefix {
+                            prefix: "nixpkgs".into(),
+                            path: "/my/nixpkgs".into()
+                        },
+                        NixSearchPathEntry::Path("/etc/nixos".into())
+                    ],
+                }
+            );
+        }
+    }
+
+    mod resolve {
+        use crate::StdIO;
+        use path_clean::PathClean;
+        use std::env::current_dir;
+
+        use super::*;
+
+        #[test]
+        fn simple_dir() {
+            let nix_search_path = NixSearchPath::from_str("./.").unwrap();
+            let io = Box::new(StdIO {}) as Box<dyn EvalIO>;
+            let res = nix_search_path.resolve(&io, "src").unwrap();
+            assert_eq!(
+                res.unwrap().to_path_buf(),
+                current_dir().unwrap().join("src").clean()
+            );
+        }
+
+        #[test]
+        fn failed_resolution() {
+            let nix_search_path = NixSearchPath::from_str("./.").unwrap();
+            let io = Box::new(StdIO {}) as Box<dyn EvalIO>;
+            let err = nix_search_path.resolve(&io, "nope").unwrap();
+            assert!(
+                matches!(err, Err(CatchableErrorKind::NixPathResolution(..))),
+                "err = {err:?}"
+            );
+        }
+
+        #[test]
+        fn second_in_path() {
+            let nix_search_path = NixSearchPath::from_str("./.:/").unwrap();
+            let io = Box::new(StdIO {}) as Box<dyn EvalIO>;
+            let res = nix_search_path.resolve(&io, "etc").unwrap();
+            assert_eq!(res.unwrap().to_path_buf(), Path::new("/etc"));
+        }
+
+        #[test]
+        fn prefix() {
+            let nix_search_path = NixSearchPath::from_str("/:tvix=.").unwrap();
+            let io = Box::new(StdIO {}) as Box<dyn EvalIO>;
+            let res = nix_search_path.resolve(&io, "tvix/src").unwrap();
+            assert_eq!(
+                res.unwrap().to_path_buf(),
+                current_dir().unwrap().join("src").clean()
+            );
+        }
+
+        #[test]
+        fn matching_prefix() {
+            let nix_search_path = NixSearchPath::from_str("/:tvix=.").unwrap();
+            let io = Box::new(StdIO {}) as Box<dyn EvalIO>;
+            let res = nix_search_path.resolve(&io, "tvix").unwrap();
+            assert_eq!(res.unwrap().to_path_buf(), current_dir().unwrap().clean());
+        }
+    }
+}
diff --git a/tvix/eval/src/observer.rs b/tvix/eval/src/observer.rs
new file mode 100644
index 0000000000..f5de399315
--- /dev/null
+++ b/tvix/eval/src/observer.rs
@@ -0,0 +1,318 @@
+//! Implements traits for things that wish to observe internal state
+//! changes of tvix-eval.
+//!
+//! This can be used to gain insights from compilation, to trace the
+//! runtime, and so on.
+//!
+//! All methods are optional, that is, observers can implement only
+/// what they are interested in observing.
+use std::io::Write;
+use std::rc::Rc;
+use std::time::Instant;
+use tabwriter::TabWriter;
+
+use crate::chunk::Chunk;
+use crate::generators::VMRequest;
+use crate::opcode::{CodeIdx, OpCode};
+use crate::value::Lambda;
+use crate::SourceCode;
+use crate::Value;
+
+/// Implemented by types that wish to observe internal happenings of
+/// the Tvix compiler.
+pub trait CompilerObserver {
+    /// Called when the compiler finishes compilation of the top-level
+    /// of an expression (usually the root Nix expression of a file).
+    fn observe_compiled_toplevel(&mut self, _: &Rc<Lambda>) {}
+
+    /// Called when the compiler finishes compilation of a
+    /// user-defined function.
+    ///
+    /// Note that in Nix there are only single argument functions, so
+    /// in an expression like `a: b: c: ...` this method will be
+    /// called three times.
+    fn observe_compiled_lambda(&mut self, _: &Rc<Lambda>) {}
+
+    /// Called when the compiler finishes compilation of a thunk.
+    fn observe_compiled_thunk(&mut self, _: &Rc<Lambda>) {}
+}
+
+/// Implemented by types that wish to observe internal happenings of
+/// the Tvix virtual machine at runtime.
+pub trait RuntimeObserver {
+    /// Called when the runtime enters a new call frame.
+    fn observe_enter_call_frame(&mut self, _arg_count: usize, _: &Rc<Lambda>, _call_depth: usize) {}
+
+    /// Called when the runtime exits a call frame.
+    fn observe_exit_call_frame(&mut self, _frame_at: usize, _stack: &[Value]) {}
+
+    /// Called when the runtime suspends a call frame.
+    fn observe_suspend_call_frame(&mut self, _frame_at: usize, _stack: &[Value]) {}
+
+    /// Called when the runtime enters a generator frame.
+    fn observe_enter_generator(&mut self, _frame_at: usize, _name: &str, _stack: &[Value]) {}
+
+    /// Called when the runtime exits a generator frame.
+    fn observe_exit_generator(&mut self, _frame_at: usize, _name: &str, _stack: &[Value]) {}
+
+    /// Called when the runtime suspends a generator frame.
+    fn observe_suspend_generator(&mut self, _frame_at: usize, _name: &str, _stack: &[Value]) {}
+
+    /// Called when a generator requests an action from the VM.
+    fn observe_generator_request(&mut self, _name: &str, _msg: &VMRequest) {}
+
+    /// Called when the runtime replaces the current call frame for a
+    /// tail call.
+    fn observe_tail_call(&mut self, _frame_at: usize, _: &Rc<Lambda>) {}
+
+    /// Called when the runtime enters a builtin.
+    fn observe_enter_builtin(&mut self, _name: &'static str) {}
+
+    /// Called when the runtime exits a builtin.
+    fn observe_exit_builtin(&mut self, _name: &'static str, _stack: &[Value]) {}
+
+    /// Called when the runtime *begins* executing an instruction. The
+    /// provided stack is the state at the beginning of the operation.
+    fn observe_execute_op(&mut self, _ip: CodeIdx, _: &OpCode, _: &[Value]) {}
+}
+
+#[derive(Default)]
+pub struct NoOpObserver {}
+
+impl CompilerObserver for NoOpObserver {}
+impl RuntimeObserver for NoOpObserver {}
+
+/// An observer that prints disassembled chunk information to its
+/// internal writer whenwever the compiler emits a toplevel function,
+/// closure or thunk.
+pub struct DisassemblingObserver<W: Write> {
+    source: SourceCode,
+    writer: TabWriter<W>,
+}
+
+impl<W: Write> DisassemblingObserver<W> {
+    pub fn new(source: SourceCode, writer: W) -> Self {
+        Self {
+            source,
+            writer: TabWriter::new(writer),
+        }
+    }
+
+    fn lambda_header(&mut self, kind: &str, lambda: &Rc<Lambda>) {
+        let _ = writeln!(
+            &mut self.writer,
+            "=== compiled {} @ {:p} ({} ops) ===",
+            kind,
+            *lambda,
+            lambda.chunk.code.len()
+        );
+    }
+
+    fn disassemble_chunk(&mut self, chunk: &Chunk) {
+        // calculate width of the widest address in the chunk
+        let width = format!("{:#x}", chunk.code.len() - 1).len();
+
+        for (idx, _) in chunk.code.iter().enumerate() {
+            let _ = chunk.disassemble_op(&mut self.writer, &self.source, width, CodeIdx(idx));
+        }
+    }
+}
+
+impl<W: Write> CompilerObserver for DisassemblingObserver<W> {
+    fn observe_compiled_toplevel(&mut self, lambda: &Rc<Lambda>) {
+        self.lambda_header("toplevel", lambda);
+        self.disassemble_chunk(&lambda.chunk);
+        let _ = self.writer.flush();
+    }
+
+    fn observe_compiled_lambda(&mut self, lambda: &Rc<Lambda>) {
+        self.lambda_header("lambda", lambda);
+        self.disassemble_chunk(&lambda.chunk);
+        let _ = self.writer.flush();
+    }
+
+    fn observe_compiled_thunk(&mut self, lambda: &Rc<Lambda>) {
+        self.lambda_header("thunk", lambda);
+        self.disassemble_chunk(&lambda.chunk);
+        let _ = self.writer.flush();
+    }
+}
+
+/// An observer that collects a textual representation of an entire
+/// runtime execution.
+pub struct TracingObserver<W: Write> {
+    // If timing is enabled, contains the timestamp of the last-emitted trace event
+    last_event: Option<Instant>,
+    writer: TabWriter<W>,
+}
+
+impl<W: Write> TracingObserver<W> {
+    pub fn new(writer: W) -> Self {
+        Self {
+            last_event: None,
+            writer: TabWriter::new(writer),
+        }
+    }
+
+    /// Write the time of each runtime event, relative to when this method is called
+    pub fn enable_timing(&mut self) {
+        self.last_event = Some(Instant::now());
+    }
+
+    fn maybe_write_time(&mut self) {
+        if let Some(last_event) = &mut self.last_event {
+            let _ = write!(&mut self.writer, "+{}ns\t", last_event.elapsed().as_nanos());
+            *last_event = Instant::now();
+        }
+    }
+
+    fn write_value(&mut self, val: &Value) {
+        let _ = match val {
+            // Potentially large types which we only want to print
+            // the type of (and avoid recursing).
+            Value::List(l) => write!(&mut self.writer, "list[{}] ", l.len()),
+            Value::Attrs(a) => write!(&mut self.writer, "attrs[{}] ", a.len()),
+            Value::Thunk(t) if t.is_evaluated() => {
+                self.write_value(&t.value());
+                Ok(())
+            }
+
+            // For other value types, defer to the standard value printer.
+            _ => write!(&mut self.writer, "{} ", val),
+        };
+    }
+
+    fn write_stack(&mut self, stack: &[Value]) {
+        let _ = write!(&mut self.writer, "[ ");
+
+        // Print out a maximum of 6 values from the top of the stack,
+        // before abbreviating it to `...`.
+        for (i, val) in stack.iter().rev().enumerate() {
+            if i == 6 {
+                let _ = write!(&mut self.writer, "...");
+                break;
+            }
+
+            self.write_value(val);
+        }
+
+        let _ = writeln!(&mut self.writer, "]");
+    }
+}
+
+impl<W: Write> RuntimeObserver for TracingObserver<W> {
+    fn observe_enter_call_frame(
+        &mut self,
+        arg_count: usize,
+        lambda: &Rc<Lambda>,
+        call_depth: usize,
+    ) {
+        self.maybe_write_time();
+
+        let _ = write!(&mut self.writer, "=== entering ");
+
+        let _ = if arg_count == 0 {
+            write!(&mut self.writer, "thunk ")
+        } else {
+            write!(&mut self.writer, "closure ")
+        };
+
+        if let Some(name) = &lambda.name {
+            let _ = write!(&mut self.writer, "'{}' ", name);
+        }
+
+        let _ = writeln!(
+            &mut self.writer,
+            "in frame[{}] @ {:p} ===",
+            call_depth, *lambda
+        );
+    }
+
+    /// Called when the runtime exits a call frame.
+    fn observe_exit_call_frame(&mut self, frame_at: usize, stack: &[Value]) {
+        self.maybe_write_time();
+        let _ = write!(&mut self.writer, "=== exiting frame {} ===\t ", frame_at);
+        self.write_stack(stack);
+    }
+
+    fn observe_suspend_call_frame(&mut self, frame_at: usize, stack: &[Value]) {
+        self.maybe_write_time();
+        let _ = write!(&mut self.writer, "=== suspending frame {} ===\t", frame_at);
+
+        self.write_stack(stack);
+    }
+
+    fn observe_enter_generator(&mut self, frame_at: usize, name: &str, stack: &[Value]) {
+        self.maybe_write_time();
+        let _ = write!(
+            &mut self.writer,
+            "=== entering generator frame '{}' [{}] ===\t",
+            name, frame_at,
+        );
+
+        self.write_stack(stack);
+    }
+
+    fn observe_exit_generator(&mut self, frame_at: usize, name: &str, stack: &[Value]) {
+        self.maybe_write_time();
+        let _ = write!(
+            &mut self.writer,
+            "=== exiting generator '{}' [{}] ===\t",
+            name, frame_at
+        );
+
+        self.write_stack(stack);
+    }
+
+    fn observe_suspend_generator(&mut self, frame_at: usize, name: &str, stack: &[Value]) {
+        self.maybe_write_time();
+        let _ = write!(
+            &mut self.writer,
+            "=== suspending generator '{}' [{}] ===\t",
+            name, frame_at
+        );
+
+        self.write_stack(stack);
+    }
+
+    fn observe_generator_request(&mut self, name: &str, msg: &VMRequest) {
+        self.maybe_write_time();
+        let _ = writeln!(
+            &mut self.writer,
+            "=== generator '{}' requested {} ===",
+            name, msg
+        );
+    }
+
+    fn observe_enter_builtin(&mut self, name: &'static str) {
+        self.maybe_write_time();
+        let _ = writeln!(&mut self.writer, "=== entering builtin {} ===", name);
+    }
+
+    fn observe_exit_builtin(&mut self, name: &'static str, stack: &[Value]) {
+        self.maybe_write_time();
+        let _ = write!(&mut self.writer, "=== exiting builtin {} ===\t", name);
+        self.write_stack(stack);
+    }
+
+    fn observe_tail_call(&mut self, frame_at: usize, lambda: &Rc<Lambda>) {
+        self.maybe_write_time();
+        let _ = writeln!(
+            &mut self.writer,
+            "=== tail-calling {:p} in frame[{}] ===",
+            *lambda, frame_at
+        );
+    }
+
+    fn observe_execute_op(&mut self, ip: CodeIdx, op: &OpCode, stack: &[Value]) {
+        self.maybe_write_time();
+        let _ = write!(&mut self.writer, "{:04} {:?}\t", ip.0, op);
+        self.write_stack(stack);
+    }
+}
+
+impl<W: Write> Drop for TracingObserver<W> {
+    fn drop(&mut self) {
+        let _ = self.writer.flush();
+    }
+}
diff --git a/tvix/eval/src/opcode.rs b/tvix/eval/src/opcode.rs
index 622a02ac85..f89c1c12e7 100644
--- a/tvix/eval/src/opcode.rs
+++ b/tvix/eval/src/opcode.rs
@@ -1,42 +1,284 @@
 //! This module implements the instruction set running on the abstract
 //! machine implemented by tvix.
 
-#[derive(Clone, Copy, Debug)]
+use std::ops::{AddAssign, Sub};
+
+/// Index of a constant in the current code chunk.
+#[repr(transparent)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
 pub struct ConstantIdx(pub usize);
 
+/// Index of an instruction in the current code chunk.
+#[repr(transparent)]
 #[derive(Clone, Copy, Debug)]
 pub struct CodeIdx(pub usize);
 
-#[derive(Clone, Copy, Debug)]
+impl AddAssign<usize> for CodeIdx {
+    fn add_assign(&mut self, rhs: usize) {
+        *self = CodeIdx(self.0 + rhs)
+    }
+}
+
+impl Sub<usize> for CodeIdx {
+    type Output = Self;
+
+    fn sub(self, rhs: usize) -> Self::Output {
+        CodeIdx(self.0 - rhs)
+    }
+}
+
+/// Index of a value in the runtime stack.  This is an offset
+/// *relative to* the VM value stack_base of the CallFrame
+/// containing the opcode which contains this StackIdx.
+#[repr(transparent)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd)]
+pub struct StackIdx(pub usize);
+
+/// Index of an upvalue within a closure's bound-variable upvalue
+/// list.  This is an absolute index into the Upvalues of the
+/// CallFrame containing the opcode which contains this UpvalueIdx.
+#[repr(transparent)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub struct UpvalueIdx(pub usize);
+
+/// Offset by which an instruction pointer should change in a jump.
+#[repr(transparent)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub struct JumpOffset(pub usize);
+
+/// Provided count for an instruction (could represent e.g. a number
+/// of elements).
+#[repr(transparent)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub struct Count(pub usize);
+
+/// All variants of this enum carry a bounded amount of data to
+/// ensure that no heap allocations are needed for an Opcode.
+///
+/// In documentation comments, stack positions are referred to by
+/// indices written in `{}` as such, where required:
+///
+/// ```notrust
+///                             --- top of the stack
+///                            /
+///                           v
+///       [ ... | 3 | 2 | 1 | 0 ]
+///                   ^
+///                  /
+/// 2 values deep ---
+/// ```
+///
+/// Unless otherwise specified, operations leave their result at the
+/// top of the stack.
+#[warn(variant_size_differences)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
 pub enum OpCode {
-    // Push a constant onto the stack.
+    /// Push a constant onto the stack.
     OpConstant(ConstantIdx),
 
-    // Push a literal value.
-    OpNull,
-    OpTrue,
-    OpFalse,
-
     // Unary operators
+    /// Discard a value from the stack.
+    OpPop,
+
+    /// Invert the boolean at the top of the stack.
     OpInvert,
+
+    // Binary operators
+    /// Invert the sign of the number at the top of the stack.
     OpNegate,
 
-    // Arithmetic binary operators
+    /// Sum up the two numbers at the top of the stack.
     OpAdd,
+
+    /// Subtract the number at {1} from the number at {2}.
     OpSub,
+
+    /// Multiply the two numbers at the top of the stack.
     OpMul,
+
+    /// Divide the two numbers at the top of the stack.
     OpDiv,
 
-    // Logical binary operators
+    // Comparison operators
+    /// Check the two values at the top of the stack for Nix-equality.
     OpEqual,
 
+    /// Check whether the value at {2} is less than {1}.
+    OpLess,
+
+    /// Check whether the value at {2} is less than or equal to {1}.
+    OpLessOrEq,
+
+    /// Check whether the value at {2} is greater than {1}.
+    OpMore,
+
+    /// Check whether the value at {2} is greater than or equal to {1}.
+    OpMoreOrEq,
+
+    // Logical operators & generic jumps
+    /// Jump forward in the bytecode specified by the number of
+    /// instructions in its usize operand.
+    OpJump(JumpOffset),
+
+    /// Jump forward in the bytecode specified by the number of
+    /// instructions in its usize operand, *if* the value at the top
+    /// of the stack is `true`.
+    OpJumpIfTrue(JumpOffset),
+
+    /// Jump forward in the bytecode specified by the number of
+    /// instructions in its usize operand, *if* the value at the top
+    /// of the stack is `false`.
+    OpJumpIfFalse(JumpOffset),
+
+    /// Pop one stack item and jump forward in the bytecode
+    /// specified by the number of instructions in its usize
+    /// operand, *if* the value at the top of the stack is a
+    /// Value::Catchable.
+    OpJumpIfCatchable(JumpOffset),
+
+    /// Jump forward in the bytecode specified by the number of
+    /// instructions in its usize operand, *if* the value at the top
+    /// of the stack is the internal value representing a missing
+    /// attribute set key.
+    OpJumpIfNotFound(JumpOffset),
+
+    /// Jump forward in the bytecode specified by the number of
+    /// instructions in its usize operand, *if* the value at the top
+    /// of the stack is *not* the internal value requesting a
+    /// stack value finalisation.
+    OpJumpIfNoFinaliseRequest(JumpOffset),
+
     // Attribute sets
-    OpAttrs(usize),
-    OpAttrPath(usize),
+    /// Construct an attribute set from the given number of key-value pairs on the top of the stack
+    ///
+    /// Note that this takes the count of *pairs*, not the number of *stack values* - the actual
+    /// number of values popped off the stack will be twice the argument to this op
+    OpAttrs(Count),
+    /// Merge the attribute set at {2} into the attribute set at {1},
+    /// and leave the new set at the top of the stack.
+    OpAttrsUpdate,
+
+    /// Select the attribute with the name at {1} from the set at {2}.
+    OpAttrsSelect,
+
+    /// Select the attribute with the name at {1} from the set at {2}, but leave
+    /// a `Value::AttrNotFound` in the stack instead of failing if it is
+    /// missing.
+    OpAttrsTrySelect,
+
+    /// Check for the presence of the attribute with the name at {1} in the set
+    /// at {2}.
+    OpHasAttr,
+
+    /// Throw an error if the attribute set at the top of the stack has any attributes
+    /// other than those listed in the formals of the current lambda
+    ///
+    /// Panics if the current frame is not a lambda with formals
+    OpValidateClosedFormals,
+
+    // `with`-handling
+    /// Push a value onto the runtime `with`-stack to enable dynamic identifier
+    /// resolution. The absolute stack index of the value is supplied as a usize
+    /// operand.
+    OpPushWith(StackIdx),
+
+    /// Pop the last runtime `with`-stack element.
+    OpPopWith,
+
+    /// Dynamically resolve an identifier with the name at {1} from the runtime
+    /// `with`-stack.
+    OpResolveWith,
 
     // Lists
-    OpList(usize),
+    /// Construct a list from the given number of values at the top of the
+    /// stack.
+    OpList(Count),
+
+    /// Concatenate the lists at {2} and {1}.
+    OpConcat,
 
     // Strings
-    OpInterpolate(usize),
+    /// Interpolate the given number of string fragments into a single string.
+    OpInterpolate(Count),
+
+    /// Force the Value on the stack and coerce it to a string
+    OpCoerceToString(crate::CoercionKind),
+
+    // Paths
+    /// Attempt to resolve the Value on the stack using the configured [`NixSearchPath`][]
+    ///
+    /// [`NixSearchPath`]: crate::nix_search_path::NixSearchPath
+    OpFindFile,
+
+    /// Attempt to resolve a path literal relative to the home dir
+    OpResolveHomePath,
+
+    // Type assertion operators
+    /// Assert that the value at {1} is a boolean, and fail with a runtime error
+    /// otherwise.
+    OpAssertBool,
+    OpAssertAttrs,
+
+    /// Access local identifiers with statically known positions.
+    OpGetLocal(StackIdx),
+
+    /// Close scopes while leaving their expression value around.
+    OpCloseScope(Count), // number of locals to pop
+
+    /// Return an error indicating that an `assert` failed
+    OpAssertFail,
+
+    // Lambdas & closures
+    /// Call the value at {1} in a new VM callframe
+    OpCall,
+
+    /// Retrieve the upvalue at the given index from the closure or thunk
+    /// currently under evaluation.
+    OpGetUpvalue(UpvalueIdx),
+
+    /// Construct a closure which has upvalues but no self-references
+    OpClosure(ConstantIdx),
+
+    /// Construct a closure which has self-references (direct or via upvalues)
+    OpThunkClosure(ConstantIdx),
+
+    /// Construct a suspended thunk, used to delay a computation for laziness.
+    OpThunkSuspended(ConstantIdx),
+
+    /// Force the value at {1} until it is a `Thunk::Evaluated`.
+    OpForce,
+
+    /// Finalise initialisation of the upvalues of the value in the given stack
+    /// index (which must be a Value::Thunk) after the scope is fully bound.
+    OpFinalise(StackIdx),
+
+    /// Final instruction emitted in a chunk. Does not have an
+    /// inherent effect, but can simplify VM logic as a marker in some
+    /// cases.
+    ///
+    /// Can be thought of as "returning" the value to the parent
+    /// frame, hence the name.
+    OpReturn,
+
+    // [`OpClosure`], [`OpThunkSuspended`], and [`OpThunkClosure`] have a
+    // variable number of arguments to the instruction, which is
+    // represented here by making their data part of the opcodes.
+    // Each of these two opcodes has a `ConstantIdx`, which must
+    // reference a `Value::Blueprint(Lambda)`.  The `upvalue_count`
+    // field in that `Lambda` indicates the number of arguments it
+    // takes, and the opcode must be followed by exactly this number
+    // of `Data*` opcodes.  The VM skips over these by advancing the
+    // instruction pointer.
+    //
+    // It is illegal for a `Data*` opcode to appear anywhere else.
+    /// Populate a static upvalue by copying from the stack immediately.
+    DataStackIdx(StackIdx),
+    /// Populate a static upvalue of a thunk by copying it the stack, but do
+    /// when the thunk is finalised (by OpFinalise) rather than immediately.
+    DataDeferredLocal(StackIdx),
+    /// Populate a static upvalue by copying it from the upvalues of an
+    /// enclosing scope.
+    DataUpvalueIdx(UpvalueIdx),
+    /// Populate dynamic upvalues by saving a copy of the with-stack.
+    DataCaptureWith,
 }
diff --git a/tvix/eval/src/pretty_ast.rs b/tvix/eval/src/pretty_ast.rs
new file mode 100644
index 0000000000..5ac115e21c
--- /dev/null
+++ b/tvix/eval/src/pretty_ast.rs
@@ -0,0 +1,468 @@
+//! Pretty-printed format for the rnix AST representation.
+//!
+//! The AST is serialised into a JSON structure that can then be
+//! printed in either minimised or well-formatted style.
+
+use rnix::ast::{self, AstToken, HasEntry};
+use serde::{ser::SerializeMap, Serialize, Serializer};
+
+pub fn pretty_print_expr(expr: &ast::Expr) -> String {
+    serde_json::ser::to_string_pretty(&SerializeAST(expr))
+        .expect("serializing AST should always succeed")
+}
+
+#[repr(transparent)]
+struct SerializeAST<S>(S);
+
+impl<'a> Serialize for SerializeAST<&'a ast::Apply> {
+    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+        let mut map = serializer.serialize_map(Some(3))?;
+        map.serialize_entry("kind", "apply")?;
+        map.serialize_entry("fn", &SerializeAST(&self.0.lambda().unwrap()))?;
+        map.serialize_entry("arg", &SerializeAST(&self.0.argument().unwrap()))?;
+        map.end()
+    }
+}
+
+impl<'a> Serialize for SerializeAST<&'a ast::Assert> {
+    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+        let mut map = serializer.serialize_map(Some(3))?;
+        map.serialize_entry("kind", "assert")?;
+        map.serialize_entry("condition", &SerializeAST(&self.0.condition().unwrap()))?;
+        map.serialize_entry("body", &SerializeAST(&self.0.body().unwrap()))?;
+        map.end()
+    }
+}
+
+impl<'a> Serialize for SerializeAST<&'a ast::Error> {
+    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+        let mut map = serializer.serialize_map(Some(2))?;
+        map.serialize_entry("kind", "error")?;
+        map.serialize_entry("node", &self.0.to_string())?;
+        map.end()
+    }
+}
+
+impl<'a> Serialize for SerializeAST<&'a ast::IfElse> {
+    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+        let mut map = serializer.serialize_map(Some(4))?;
+        map.serialize_entry("kind", "if_else")?;
+        map.serialize_entry("condition", &SerializeAST(&self.0.condition().unwrap()))?;
+        map.serialize_entry("then_body", &SerializeAST(&self.0.body().unwrap()))?;
+        map.serialize_entry("else_body", &SerializeAST(&self.0.else_body().unwrap()))?;
+        map.end()
+    }
+}
+
+impl<'a> Serialize for SerializeAST<&'a ast::Select> {
+    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+        let size = match self.0.default_expr() {
+            Some(_) => 4,
+            None => 3,
+        };
+
+        let mut map = serializer.serialize_map(Some(size))?;
+        map.serialize_entry("kind", "select")?;
+        map.serialize_entry("set", &SerializeAST(&self.0.expr().unwrap()))?;
+        map.serialize_entry("path", &SerializeAST(self.0.attrpath().unwrap()))?;
+
+        if let Some(default) = self.0.default_expr() {
+            map.serialize_entry("default", &SerializeAST(&default))?;
+        }
+
+        map.end()
+    }
+}
+
+impl Serialize for SerializeAST<ast::InterpolPart<String>> {
+    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+        match &self.0 {
+            ast::InterpolPart::Literal(s) => Serialize::serialize(s, serializer),
+            ast::InterpolPart::Interpolation(node) => {
+                Serialize::serialize(&SerializeAST(&node.expr().unwrap()), serializer)
+            }
+        }
+    }
+}
+
+impl<'a> Serialize for SerializeAST<&'a ast::Str> {
+    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+        let mut map = serializer.serialize_map(Some(2))?;
+        map.serialize_entry("kind", "string")?;
+
+        map.serialize_entry(
+            "parts",
+            &self
+                .0
+                .normalized_parts()
+                .into_iter()
+                .map(SerializeAST)
+                .collect::<Vec<_>>(),
+        )?;
+
+        map.end()
+    }
+}
+
+impl Serialize for SerializeAST<ast::InterpolPart<ast::PathContent>> {
+    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+        match &self.0 {
+            ast::InterpolPart::Literal(p) => Serialize::serialize(p.syntax().text(), serializer),
+            ast::InterpolPart::Interpolation(node) => {
+                Serialize::serialize(&SerializeAST(&node.expr().unwrap()), serializer)
+            }
+        }
+    }
+}
+
+impl<'a> Serialize for SerializeAST<&'a ast::Path> {
+    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+        let mut map = serializer.serialize_map(Some(2))?;
+        map.serialize_entry("kind", "path")?;
+
+        map.serialize_entry(
+            "parts",
+            &self.0.parts().map(SerializeAST).collect::<Vec<_>>(),
+        )?;
+
+        map.end()
+    }
+}
+
+impl<'a> Serialize for SerializeAST<&'a ast::Literal> {
+    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+        let mut map = serializer.serialize_map(Some(2))?;
+        map.serialize_entry("kind", "literal")?;
+
+        match self.0.kind() {
+            ast::LiteralKind::Float(val) => map.serialize_entry("float", &val.value().unwrap()),
+            ast::LiteralKind::Integer(val) => map.serialize_entry("int", &val.value().unwrap()),
+            ast::LiteralKind::Uri(val) => map.serialize_entry("uri", val.syntax().text()),
+        }?;
+
+        map.end()
+    }
+}
+
+impl Serialize for SerializeAST<ast::PatEntry> {
+    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+        let mut map = serializer.serialize_map(None)?;
+        map.serialize_entry("ident", &SerializeAST(&self.0.ident().unwrap()))?;
+
+        if let Some(default) = self.0.default() {
+            map.serialize_entry("default", &SerializeAST(&default))?;
+        }
+
+        map.end()
+    }
+}
+
+impl Serialize for SerializeAST<ast::Param> {
+    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+        match &self.0 {
+            ast::Param::Pattern(pat) => {
+                let mut map = serializer.serialize_map(None)?;
+                map.serialize_entry("kind", "formals")?;
+
+                map.serialize_entry(
+                    "entries",
+                    &pat.pat_entries().map(SerializeAST).collect::<Vec<_>>(),
+                )?;
+
+                if let Some(bind) = pat.pat_bind() {
+                    map.serialize_entry("bind", &SerializeAST(&bind.ident().unwrap()))?;
+                }
+
+                map.serialize_entry("ellipsis", &pat.ellipsis_token().is_some())?;
+
+                map.end()
+            }
+
+            ast::Param::IdentParam(node) => {
+                Serialize::serialize(&SerializeAST(&node.ident().unwrap()), serializer)
+            }
+        }
+    }
+}
+
+impl<'a> Serialize for SerializeAST<&'a ast::Lambda> {
+    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+        let mut map = serializer.serialize_map(Some(3))?;
+        map.serialize_entry("kind", "lambda")?;
+        map.serialize_entry("param", &SerializeAST(self.0.param().unwrap()))?;
+        map.serialize_entry("body", &SerializeAST(self.0.body().unwrap()))?;
+        map.end()
+    }
+}
+
+impl<'a> Serialize for SerializeAST<&'a ast::LegacyLet> {
+    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+        let mut map = serializer.serialize_map(Some(3))?;
+        map.serialize_entry("kind", "legacy_let")?;
+
+        map.serialize_entry(
+            "entries",
+            &self
+                .0
+                .attrpath_values()
+                .map(SerializeAST)
+                .collect::<Vec<_>>(),
+        )?;
+
+        map.serialize_entry(
+            "inherits",
+            &self.0.inherits().map(SerializeAST).collect::<Vec<_>>(),
+        )?;
+
+        map.end()
+    }
+}
+
+impl<'a> Serialize for SerializeAST<&'a ast::LetIn> {
+    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+        let mut map = serializer.serialize_map(Some(3))?;
+        map.serialize_entry("kind", "let")?;
+
+        map.serialize_entry(
+            "entries",
+            &self
+                .0
+                .attrpath_values()
+                .map(SerializeAST)
+                .collect::<Vec<_>>(),
+        )?;
+
+        map.serialize_entry(
+            "inherits",
+            &self.0.inherits().map(SerializeAST).collect::<Vec<_>>(),
+        )?;
+
+        map.serialize_entry("body", &SerializeAST(&self.0.body().unwrap()))?;
+        map.end()
+    }
+}
+
+impl<'a> Serialize for SerializeAST<&'a ast::List> {
+    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+        let list = self.0.items().map(SerializeAST).collect::<Vec<_>>();
+
+        let mut map = serializer.serialize_map(Some(2))?;
+        map.serialize_entry("kind", "list")?;
+        map.serialize_entry("items", &list)?;
+
+        map.end()
+    }
+}
+
+impl<'a> Serialize for SerializeAST<&'a ast::BinOp> {
+    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+        let mut map = serializer.serialize_map(Some(4))?;
+        map.serialize_entry("kind", "binary_op")?;
+
+        map.serialize_entry(
+            "operator",
+            match self.0.operator().unwrap() {
+                ast::BinOpKind::Concat => "concat",
+                ast::BinOpKind::Update => "update",
+                ast::BinOpKind::Add => "add",
+                ast::BinOpKind::Sub => "sub",
+                ast::BinOpKind::Mul => "mul",
+                ast::BinOpKind::Div => "div",
+                ast::BinOpKind::And => "and",
+                ast::BinOpKind::Equal => "equal",
+                ast::BinOpKind::Implication => "implication",
+                ast::BinOpKind::Less => "less",
+                ast::BinOpKind::LessOrEq => "less_or_eq",
+                ast::BinOpKind::More => "more",
+                ast::BinOpKind::MoreOrEq => "more_or_eq",
+                ast::BinOpKind::NotEqual => "not_equal",
+                ast::BinOpKind::Or => "or",
+            },
+        )?;
+
+        map.serialize_entry("lhs", &SerializeAST(&self.0.lhs().unwrap()))?;
+        map.serialize_entry("rhs", &SerializeAST(&self.0.rhs().unwrap()))?;
+        map.end()
+    }
+}
+
+impl<'a> Serialize for SerializeAST<&'a ast::Paren> {
+    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+        let mut map = serializer.serialize_map(Some(2))?;
+        map.serialize_entry("kind", "paren")?;
+        map.serialize_entry("expr", &SerializeAST(&self.0.expr().unwrap()))?;
+        map.end()
+    }
+}
+
+impl<'a> Serialize for SerializeAST<&'a ast::Root> {
+    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+        let mut map = serializer.serialize_map(Some(2))?;
+        map.serialize_entry("kind", "root")?;
+        map.serialize_entry("expr", &SerializeAST(&self.0.expr().unwrap()))?;
+        map.end()
+    }
+}
+
+impl Serialize for SerializeAST<ast::AttrpathValue> {
+    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+        let mut map = serializer.serialize_map(Some(2))?;
+        map.serialize_entry("name", &SerializeAST(self.0.attrpath().unwrap()))?;
+        map.serialize_entry("value", &SerializeAST(self.0.value().unwrap()))?;
+        map.end()
+    }
+}
+
+impl Serialize for SerializeAST<ast::Inherit> {
+    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+        let mut map = serializer.serialize_map(None)?;
+
+        if let Some(from) = self.0.from() {
+            map.serialize_entry("namespace", &SerializeAST(&from.expr().unwrap()))?;
+        }
+
+        map.serialize_entry(
+            "names",
+            &self.0.attrs().map(SerializeAST).collect::<Vec<_>>(),
+        )?;
+
+        map.end()
+    }
+}
+
+impl<'a> Serialize for SerializeAST<&'a ast::AttrSet> {
+    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+        let mut map = serializer.serialize_map(None)?;
+        map.serialize_entry("kind", "attrset")?;
+        map.serialize_entry("recursive", &self.0.rec_token().is_some())?;
+
+        map.serialize_entry(
+            "entries",
+            &self
+                .0
+                .attrpath_values()
+                .map(SerializeAST)
+                .collect::<Vec<_>>(),
+        )?;
+
+        map.serialize_entry(
+            "inherits",
+            &self.0.inherits().map(SerializeAST).collect::<Vec<_>>(),
+        )?;
+
+        map.end()
+    }
+}
+
+impl<'a> Serialize for SerializeAST<&'a ast::UnaryOp> {
+    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+        let mut map = serializer.serialize_map(Some(3))?;
+        map.serialize_entry("kind", "unary_op")?;
+
+        map.serialize_entry(
+            "operator",
+            match self.0.operator().unwrap() {
+                ast::UnaryOpKind::Invert => "invert",
+                ast::UnaryOpKind::Negate => "negate",
+            },
+        )?;
+
+        map.serialize_entry("expr", &SerializeAST(&self.0.expr().unwrap()))?;
+        map.end()
+    }
+}
+
+impl<'a> Serialize for SerializeAST<&'a ast::Ident> {
+    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+        let mut map = serializer.serialize_map(Some(2))?;
+        map.serialize_entry("kind", "ident")?;
+        map.serialize_entry("ident", self.0.ident_token().unwrap().text())?;
+        map.end()
+    }
+}
+
+impl<'a> Serialize for SerializeAST<&'a ast::With> {
+    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+        let mut map = serializer.serialize_map(Some(3))?;
+        map.serialize_entry("kind", "with")?;
+        map.serialize_entry("with", &SerializeAST(&self.0.namespace().unwrap()))?;
+        map.serialize_entry("body", &SerializeAST(&self.0.body().unwrap()))?;
+        map.end()
+    }
+}
+
+impl<'a> Serialize for SerializeAST<&'a ast::Dynamic> {
+    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+        let mut map = serializer.serialize_map(Some(2))?;
+        map.serialize_entry("kind", "dynamic")?;
+        map.serialize_entry("expr", &SerializeAST(&self.0.expr().unwrap()))?;
+        map.end()
+    }
+}
+
+impl Serialize for SerializeAST<ast::Attr> {
+    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+        match &self.0 {
+            ast::Attr::Ident(ident) => Serialize::serialize(&SerializeAST(ident), serializer),
+            ast::Attr::Dynamic(node) => Serialize::serialize(&SerializeAST(node), serializer),
+            ast::Attr::Str(node) => Serialize::serialize(&SerializeAST(node), serializer),
+        }
+    }
+}
+
+impl Serialize for SerializeAST<ast::Attrpath> {
+    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+        let mut map = serializer.serialize_map(Some(2))?;
+        map.serialize_entry("kind", "attrpath")?;
+
+        map.serialize_entry(
+            "path",
+            &self.0.attrs().map(SerializeAST).collect::<Vec<_>>(),
+        )?;
+
+        map.end()
+    }
+}
+
+impl<'a> Serialize for SerializeAST<&'a ast::HasAttr> {
+    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+        let mut map = serializer.serialize_map(Some(3))?;
+        map.serialize_entry("kind", "has_attr")?;
+        map.serialize_entry("expr", &SerializeAST(&self.0.expr().unwrap()))?;
+        map.serialize_entry("attrpath", &SerializeAST(self.0.attrpath().unwrap()))?;
+        map.end()
+    }
+}
+
+impl<'a> Serialize for SerializeAST<&'a ast::Expr> {
+    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+        match self.0 {
+            ast::Expr::Apply(node) => Serialize::serialize(&SerializeAST(node), serializer),
+            ast::Expr::Assert(node) => Serialize::serialize(&SerializeAST(node), serializer),
+            ast::Expr::Error(node) => Serialize::serialize(&SerializeAST(node), serializer),
+            ast::Expr::IfElse(node) => Serialize::serialize(&SerializeAST(node), serializer),
+            ast::Expr::Select(node) => Serialize::serialize(&SerializeAST(node), serializer),
+            ast::Expr::Str(node) => Serialize::serialize(&SerializeAST(node), serializer),
+            ast::Expr::Path(node) => Serialize::serialize(&SerializeAST(node), serializer),
+            ast::Expr::Literal(node) => Serialize::serialize(&SerializeAST(node), serializer),
+            ast::Expr::Lambda(node) => Serialize::serialize(&SerializeAST(node), serializer),
+            ast::Expr::LegacyLet(node) => Serialize::serialize(&SerializeAST(node), serializer),
+            ast::Expr::LetIn(node) => Serialize::serialize(&SerializeAST(node), serializer),
+            ast::Expr::List(node) => Serialize::serialize(&SerializeAST(node), serializer),
+            ast::Expr::BinOp(node) => Serialize::serialize(&SerializeAST(node), serializer),
+            ast::Expr::Paren(node) => Serialize::serialize(&SerializeAST(node), serializer),
+            ast::Expr::Root(node) => Serialize::serialize(&SerializeAST(node), serializer),
+            ast::Expr::AttrSet(node) => Serialize::serialize(&SerializeAST(node), serializer),
+            ast::Expr::UnaryOp(node) => Serialize::serialize(&SerializeAST(node), serializer),
+            ast::Expr::Ident(node) => Serialize::serialize(&SerializeAST(node), serializer),
+            ast::Expr::With(node) => Serialize::serialize(&SerializeAST(node), serializer),
+            ast::Expr::HasAttr(node) => Serialize::serialize(&SerializeAST(node), serializer),
+        }
+    }
+}
+
+impl Serialize for SerializeAST<ast::Expr> {
+    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+        SerializeAST(&self.0).serialize(serializer)
+    }
+}
diff --git a/tvix/eval/src/properties.rs b/tvix/eval/src/properties.rs
new file mode 100644
index 0000000000..45c1cdfce9
--- /dev/null
+++ b/tvix/eval/src/properties.rs
@@ -0,0 +1,164 @@
+//! Macros that generate proptest test suites checking laws of stdlib traits
+
+/// Generate a suite of tests to check the laws of the [`Eq`] impl for the given type
+macro_rules! eq_laws {
+    ($ty: ty) => {
+        eq_laws!(
+            #[strategy(::proptest::arbitrary::any::<$ty>())]
+            $ty,
+            Default::default()
+        );
+    };
+    ($ty: ty, $config: expr) => {
+        eq_laws!(
+            #[strategy(::proptest::arbitrary::any::<$ty>())]
+            $ty,
+            $config
+        );
+    };
+    (#[$meta: meta] $ty: ty, $config: expr) => {
+        #[allow(clippy::eq_op)]
+        mod eq {
+            use test_strategy::proptest;
+
+            use super::*;
+
+            #[proptest($config)]
+            fn reflexive(#[$meta] x: $ty) {
+                assert!(x == x);
+            }
+
+            #[proptest($config)]
+            fn symmetric(#[$meta] x: $ty, #[$meta] y: $ty) {
+                assert_eq!(x == y, y == x);
+            }
+
+            #[proptest($config)]
+            fn transitive(#[$meta] x: $ty, #[$meta] y: $ty, #[$meta] z: $ty) {
+                if x == y && y == z {
+                    assert!(x == z);
+                }
+            }
+        }
+    };
+}
+
+/// Generate a suite of tests to check the laws of the [`Ord`] impl for the given type
+macro_rules! ord_laws {
+    ($ty: ty) => {
+        ord_laws!(
+            #[strategy(::proptest::arbitrary::any::<$ty>())]
+            $ty,
+            Default::default()
+        );
+    };
+    ($ty: ty, $config: expr) => {
+        ord_laws!(
+            #[strategy(::proptest::arbitrary::any::<$ty>())]
+            $ty,
+            $config
+        );
+    };
+    (#[$meta: meta] $ty: ty, $config: expr) => {
+        mod ord {
+            use test_strategy::proptest;
+
+            use super::*;
+
+            #[proptest($config)]
+            fn partial_cmp_matches_cmp(#[$meta] x: $ty, #[$meta] y: $ty) {
+                assert_eq!(x.partial_cmp(&y), Some(x.cmp(&y)));
+            }
+
+            #[proptest($config)]
+            fn dual(#[$meta] x: $ty, #[$meta] y: $ty) {
+                if x < y {
+                    assert!(y > x);
+                }
+                if y < x {
+                    assert!(x > y);
+                }
+            }
+
+            #[proptest($config)]
+            fn le_transitive(#[$meta] x: $ty, #[$meta] y: $ty, #[$meta] z: $ty) {
+                if x < y && y < z {
+                    assert!(x < z)
+                }
+            }
+
+            #[proptest($config)]
+            fn gt_transitive(#[$meta] x: $ty, #[$meta] y: $ty, #[$meta] z: $ty) {
+                if x > y && y > z {
+                    assert!(x > z)
+                }
+            }
+
+            #[proptest($config)]
+            fn trichotomy(#[$meta] x: $ty, #[$meta] y: $ty) {
+                let less = x < y;
+                let greater = x > y;
+                let eq = x == y;
+
+                if less {
+                    assert!(!greater);
+                    assert!(!eq);
+                }
+
+                if greater {
+                    assert!(!less);
+                    assert!(!eq);
+                }
+
+                if eq {
+                    assert!(!less);
+                    assert!(!greater);
+                }
+            }
+        }
+    };
+}
+
+/// Generate a test to check the laws of the [`Hash`] impl for the given type
+macro_rules! hash_laws {
+    ($ty: ty) => {
+        hash_laws!(
+            #[strategy(::proptest::arbitrary::any::<$ty>())]
+            $ty,
+            Default::default()
+        );
+    };
+    ($ty: ty, $config: expr) => {
+        hash_laws!(
+            #[strategy(::proptest::arbitrary::any::<$ty>())]
+            $ty,
+            $config
+        );
+    };
+    (#[$meta: meta] $ty: ty, $config: expr) => {
+        mod hash {
+            use test_strategy::proptest;
+
+            use super::*;
+
+            #[proptest($config)]
+            fn matches_eq(#[$meta] x: $ty, #[$meta] y: $ty) {
+                let hash = |x: &$ty| {
+                    use std::hash::Hasher;
+
+                    let mut hasher = ::std::collections::hash_map::DefaultHasher::new();
+                    x.hash(&mut hasher);
+                    hasher.finish()
+                };
+
+                if x == y {
+                    assert_eq!(hash(&x), hash(&y));
+                }
+            }
+        }
+    };
+}
+
+pub(crate) use eq_laws;
+pub(crate) use hash_laws;
+pub(crate) use ord_laws;
diff --git a/tvix/eval/src/source.rs b/tvix/eval/src/source.rs
new file mode 100644
index 0000000000..5a7f10abb8
--- /dev/null
+++ b/tvix/eval/src/source.rs
@@ -0,0 +1,65 @@
+//! This module contains utilities for dealing with the codemap that
+//! needs to be carried across different compiler instantiations in an
+//! evaluation.
+//!
+//! The data type `SourceCode` should be carried through all relevant
+//! places instead of copying the codemap structures directly.
+
+use std::{
+    cell::{Ref, RefCell, RefMut},
+    rc::Rc,
+    sync::Arc,
+};
+
+use codemap::{CodeMap, Span};
+
+/// Tracks all source code in a Tvix evaluation for accurate error
+/// reporting.
+#[derive(Clone, Debug)]
+pub struct SourceCode(Rc<RefCell<CodeMap>>);
+
+impl SourceCode {
+    /// Access a read-only reference to the codemap.
+    pub fn codemap(&self) -> Ref<CodeMap> {
+        self.0.borrow()
+    }
+
+    /// Access a writable reference to the codemap.
+    fn codemap_mut(&self) -> RefMut<CodeMap> {
+        self.0.borrow_mut()
+    }
+
+    /// Add a file to the codemap. The returned Arc is managed by the
+    /// codemap internally and can be used like a normal reference.
+    pub fn add_file(&self, name: String, code: String) -> Arc<codemap::File> {
+        self.codemap_mut().add_file(name, code)
+    }
+
+    /// Retrieve the line number of the given span. If it spans
+    /// multiple lines, the first line will be returned.
+    pub fn get_line(&self, span: Span) -> usize {
+        // lines are 0-indexed in the codemap, but users probably want
+        // real line numbers
+        self.codemap().look_up_span(span).begin.line + 1
+    }
+
+    /// Returns the literal source slice of the given span.
+    pub fn source_slice(&self, span: Span) -> Ref<str> {
+        Ref::map(self.codemap(), |c| {
+            c.find_file(span.low()).source_slice(span)
+        })
+    }
+
+    /// Returns the reference to the file structure that a given span
+    /// is in.
+    pub fn get_file(&self, span: Span) -> Arc<codemap::File> {
+        self.codemap().look_up_span(span).file
+    }
+}
+
+impl Default for SourceCode {
+    /// Create a new SourceCode instance.
+    fn default() -> Self {
+        Self(Rc::new(RefCell::new(CodeMap::new())))
+    }
+}
diff --git a/tvix/eval/src/spans.rs b/tvix/eval/src/spans.rs
new file mode 100644
index 0000000000..f422093b0d
--- /dev/null
+++ b/tvix/eval/src/spans.rs
@@ -0,0 +1,109 @@
+//! Utilities for dealing with span tracking in the compiler and in
+//! error reporting.
+
+use codemap::{File, Span};
+use rnix::ast;
+use rowan::ast::AstNode;
+
+/// Helper struct to carry information required for making a span, but
+/// without actually performing the (expensive) span lookup.
+///
+/// This is used for tracking spans across thunk boundaries, as they
+/// are frequently instantiated but spans are only used in error or
+/// warning cases.
+#[derive(Clone, Debug)]
+pub enum LightSpan {
+    /// The span has already been computed and can just be used right
+    /// away.
+    Actual { span: Span },
+}
+
+impl LightSpan {
+    pub fn new_actual(span: Span) -> Self {
+        Self::Actual { span }
+    }
+
+    pub fn span(&self) -> Span {
+        match self {
+            LightSpan::Actual { span } => *span,
+        }
+    }
+}
+
+impl From<Span> for LightSpan {
+    fn from(span: Span) -> Self {
+        LightSpan::Actual { span }
+    }
+}
+
+/// Trait implemented by all types from which we can retrieve a span.
+pub trait ToSpan {
+    fn span_for(&self, file: &File) -> Span;
+}
+
+impl ToSpan for Span {
+    fn span_for(&self, _: &File) -> Span {
+        *self
+    }
+}
+
+impl ToSpan for rnix::TextRange {
+    fn span_for(&self, file: &File) -> Span {
+        file.span
+            .subspan(u32::from(self.start()) as u64, u32::from(self.end()) as u64)
+    }
+}
+
+impl ToSpan for rnix::SyntaxToken {
+    fn span_for(&self, file: &File) -> Span {
+        self.text_range().span_for(file)
+    }
+}
+
+impl ToSpan for rnix::SyntaxNode {
+    fn span_for(&self, file: &File) -> Span {
+        self.text_range().span_for(file)
+    }
+}
+
+/// Generates a `ToSpan` implementation for a type implementing
+/// `rowan::AstNode`. This is impossible to do as a blanket
+/// implementation because `rustc` forbids these implementations for
+/// traits from third-party crates due to a belief that semantic
+/// versioning truly could work (it doesn't).
+macro_rules! expr_to_span {
+    ( $type:path ) => {
+        impl ToSpan for $type {
+            fn span_for(&self, file: &File) -> Span {
+                self.syntax().span_for(file)
+            }
+        }
+    };
+}
+
+expr_to_span!(ast::Expr);
+expr_to_span!(ast::Apply);
+expr_to_span!(ast::Assert);
+expr_to_span!(ast::Attr);
+expr_to_span!(ast::AttrSet);
+expr_to_span!(ast::Attrpath);
+expr_to_span!(ast::AttrpathValue);
+expr_to_span!(ast::BinOp);
+expr_to_span!(ast::HasAttr);
+expr_to_span!(ast::Ident);
+expr_to_span!(ast::IdentParam);
+expr_to_span!(ast::IfElse);
+expr_to_span!(ast::Inherit);
+expr_to_span!(ast::Interpol);
+expr_to_span!(ast::Lambda);
+expr_to_span!(ast::LegacyLet);
+expr_to_span!(ast::LetIn);
+expr_to_span!(ast::List);
+expr_to_span!(ast::Literal);
+expr_to_span!(ast::PatBind);
+expr_to_span!(ast::Path);
+expr_to_span!(ast::Pattern);
+expr_to_span!(ast::Select);
+expr_to_span!(ast::Str);
+expr_to_span!(ast::UnaryOp);
+expr_to_span!(ast::With);
diff --git a/tvix/eval/src/systems.rs b/tvix/eval/src/systems.rs
new file mode 100644
index 0000000000..16386cb9e0
--- /dev/null
+++ b/tvix/eval/src/systems.rs
@@ -0,0 +1,351 @@
+/// true iff the argument is recognized by cppnix as the second
+/// coordinate of a "nix double"
+fn is_second_coordinate(x: &str) -> bool {
+    matches!(x, "linux" | "darwin" | "netbsd" | "openbsd" | "freebsd")
+}
+
+/// This function takes an llvm triple (which may have three or four
+/// components, separated by dashes) and returns the "best"
+/// approximation as a nix double, where "best" is currently defined
+/// as "however cppnix handles it".
+pub fn llvm_triple_to_nix_double(llvm_triple: &str) -> String {
+    let parts: Vec<&str> = llvm_triple.split('-').collect();
+    let cpu = match parts[0] {
+        "armv6" => "armv6l", // cppnix appends an "l" to armv6
+        "armv7" => "armv7l", // cppnix appends an "l" to armv7
+        x => match x.as_bytes() {
+            [b'i', _, b'8', b'6'] => "i686", // cppnix glob-matches against i*86
+            _ => x,
+        },
+    };
+    let os = match parts[1..] {
+        [_vendor, kernel, _environment] if is_second_coordinate(kernel) => kernel,
+        [_vendor, kernel] if is_second_coordinate(kernel) => kernel,
+        [kernel, _environment] if is_second_coordinate(kernel) => kernel,
+
+        // Rustc uses wasm32-unknown-unknown, which is rejected by
+        // config.sub, for wasm-in-the-browser environments.  Rustc
+        // should be using wasm32-unknown-none, which config.sub
+        // accepts.  Hopefully the rustc people will change their
+        // triple before stabilising this triple.  In the meantime,
+        // we fix it here in order to unbreak tvixbolt.
+        //
+        // https://doc.rust-lang.org/beta/nightly-rustc/rustc_target/spec/wasm32_unknown_unknown/index.html
+        ["unknown", "unknown"] if cpu == "wasm32" => "none",
+
+        _ => panic!("unrecognized triple {llvm_triple}"),
+    };
+    format!("{cpu}-{os}")
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    #[test]
+    fn test_systems() {
+        assert_eq!(
+            llvm_triple_to_nix_double("aarch64-unknown-linux-gnu"),
+            "aarch64-linux"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("i686-unknown-linux-gnu"),
+            "i686-linux"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("x86_64-apple-darwin"),
+            "x86_64-darwin"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("x86_64-unknown-linux-gnu"),
+            "x86_64-linux"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("aarch64-apple-darwin"),
+            "aarch64-darwin"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("aarch64-unknown-linux-musl"),
+            "aarch64-linux"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("arm-unknown-linux-gnueabi"),
+            "arm-linux"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("arm-unknown-linux-gnueabihf"),
+            "arm-linux"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("armv7-unknown-linux-gnueabihf"),
+            "armv7l-linux"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("mips-unknown-linux-gnu"),
+            "mips-linux"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("mips64-unknown-linux-gnuabi64"),
+            "mips64-linux"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("mips64-unknown-linux-gnuabin32"),
+            "mips64-linux"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("mips64el-unknown-linux-gnuabi64"),
+            "mips64el-linux"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("mips64el-unknown-linux-gnuabin32"),
+            "mips64el-linux"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("mipsel-unknown-linux-gnu"),
+            "mipsel-linux"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("powerpc-unknown-linux-gnu"),
+            "powerpc-linux"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("powerpc64-unknown-linux-gnu"),
+            "powerpc64-linux"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("powerpc64le-unknown-linux-gnu"),
+            "powerpc64le-linux"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("s390x-unknown-linux-gnu"),
+            "s390x-linux"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("x86_64-unknown-linux-musl"),
+            "x86_64-linux"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("x86_64-unknown-netbsd"),
+            "x86_64-netbsd"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("aarch64-linux-android"),
+            "aarch64-linux"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("arm-linux-androideabi"),
+            "arm-linux"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("arm-unknown-linux-musleabi"),
+            "arm-linux"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("arm-unknown-linux-musleabihf"),
+            "arm-linux"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("armv5te-unknown-linux-gnueabi"),
+            "armv5te-linux"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("armv5te-unknown-linux-musleabi"),
+            "armv5te-linux"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("armv7-linux-androideabi"),
+            "armv7l-linux"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("armv7-unknown-linux-gnueabi"),
+            "armv7l-linux"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("armv7-unknown-linux-musleabi"),
+            "armv7l-linux"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("armv7-unknown-linux-musleabihf"),
+            "armv7l-linux"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("i586-unknown-linux-gnu"),
+            "i686-linux"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("i586-unknown-linux-musl"),
+            "i686-linux"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("i686-linux-android"),
+            "i686-linux"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("i686-unknown-linux-musl"),
+            "i686-linux"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("mips-unknown-linux-musl"),
+            "mips-linux"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("mips64-unknown-linux-muslabi64"),
+            "mips64-linux"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("mips64el-unknown-linux-muslabi64"),
+            "mips64el-linux"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("mipsel-unknown-linux-musl"),
+            "mipsel-linux"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("sparc64-unknown-linux-gnu"),
+            "sparc64-linux"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("thumbv7neon-linux-androideabi"),
+            "thumbv7neon-linux"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("thumbv7neon-unknown-linux-gnueabihf"),
+            "thumbv7neon-linux"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("x86_64-linux-android"),
+            "x86_64-linux"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("x86_64-unknown-linux-gnux32"),
+            "x86_64-linux"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("aarch64-unknown-linux-gnu_ilp32"),
+            "aarch64-linux"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("aarch64-unknown-netbsd"),
+            "aarch64-netbsd"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("aarch64_be-unknown-linux-gnu_ilp32"),
+            "aarch64_be-linux"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("aarch64_be-unknown-linux-gnu"),
+            "aarch64_be-linux"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("armeb-unknown-linux-gnueabi"),
+            "armeb-linux"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("armv4t-unknown-linux-gnueabi"),
+            "armv4t-linux"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("armv6-unknown-netbsd-eabihf"),
+            "armv6l-netbsd"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("armv7-unknown-linux-uclibceabi"),
+            "armv7l-linux"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("armv7-unknown-linux-uclibceabihf"),
+            "armv7l-linux"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("armv7-unknown-netbsd-eabihf"),
+            "armv7l-netbsd"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("hexagon-unknown-linux-musl"),
+            "hexagon-linux"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("i686-unknown-netbsd"),
+            "i686-netbsd"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("m68k-unknown-linux-gnu"),
+            "m68k-linux"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("mips-unknown-linux-uclibc"),
+            "mips-linux"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("mips64-openwrt-linux-musl"),
+            "mips64-linux"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("mipsel-unknown-linux-uclibc"),
+            "mipsel-linux"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("mipsisa32r6-unknown-linux-gnu"),
+            "mipsisa32r6-linux"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("mipsisa32r6el-unknown-linux-gnu"),
+            "mipsisa32r6el-linux"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("mipsisa64r6-unknown-linux-gnuabi64"),
+            "mipsisa64r6-linux"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("mipsisa64r6el-unknown-linux-gnuabi64"),
+            "mipsisa64r6el-linux"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("powerpc-unknown-linux-gnuspe"),
+            "powerpc-linux"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("powerpc-unknown-linux-musl"),
+            "powerpc-linux"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("powerpc-unknown-netbsd"),
+            "powerpc-netbsd"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("powerpc64-unknown-linux-musl"),
+            "powerpc64-linux"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("powerpc64le-unknown-linux-musl"),
+            "powerpc64le-linux"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("riscv32gc-unknown-linux-gnu"),
+            "riscv32gc-linux"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("riscv32gc-unknown-linux-musl"),
+            "riscv32gc-linux"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("riscv64gc-unknown-linux-musl"),
+            "riscv64gc-linux"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("s390x-unknown-linux-musl"),
+            "s390x-linux"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("sparc-unknown-linux-gnu"),
+            "sparc-linux"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("sparc64-unknown-netbsd"),
+            "sparc64-netbsd"
+        );
+        assert_eq!(
+            llvm_triple_to_nix_double("thumbv7neon-unknown-linux-musleabihf"),
+            "thumbv7neon-linux"
+        );
+    }
+}
diff --git a/tvix/eval/src/test_utils.rs b/tvix/eval/src/test_utils.rs
new file mode 100644
index 0000000000..a7d1c3f968
--- /dev/null
+++ b/tvix/eval/src/test_utils.rs
@@ -0,0 +1,8 @@
+use codemap::CodeMap;
+
+/// Create a dummy [`codemap::Span`] for use in tests
+pub(crate) fn dummy_span() -> codemap::Span {
+    let mut codemap = CodeMap::new();
+    let file = codemap.add_file("<dummy>".to_owned(), "<dummy>".to_owned());
+    file.span
+}
diff --git a/tvix/eval/src/tests/mod.rs b/tvix/eval/src/tests/mod.rs
new file mode 100644
index 0000000000..5a7708e298
--- /dev/null
+++ b/tvix/eval/src/tests/mod.rs
@@ -0,0 +1,203 @@
+use crate::{value::Value, EvalIO};
+use builtin_macros::builtins;
+use pretty_assertions::assert_eq;
+use rstest::rstest;
+use std::path::PathBuf;
+
+/// Module for one-off tests which do not follow the rest of the
+/// test layout.
+mod one_offs;
+
+#[builtins]
+mod mock_builtins {
+    //! Builtins which are required by language tests, but should not
+    //! actually exist in //tvix/eval.
+    use crate as tvix_eval;
+    use crate::generators::GenCo;
+    use crate::*;
+    use genawaiter::rc::Gen;
+
+    #[builtin("derivation")]
+    async fn builtin_derivation(co: GenCo, input: Value) -> Result<Value, ErrorKind> {
+        let input = input.to_attrs()?;
+        let attrs = input.update(NixAttrs::from_iter(
+            [
+                (
+                    "outPath",
+                    "/nix/store/00000000000000000000000000000000-mock",
+                ),
+                (
+                    "drvPath",
+                    "/nix/store/00000000000000000000000000000000-mock.drv",
+                ),
+                ("type", "derivation"),
+            ]
+            .into_iter(),
+        ));
+
+        Ok(Value::Attrs(Box::new(attrs)))
+    }
+}
+
+fn eval_test(code_path: PathBuf, expect_success: bool) {
+    std::env::set_var("TEST_VAR", "foo"); // for eval-okay-getenv.nix
+
+    eprintln!("path: {}", code_path.display());
+    assert_eq!(
+        code_path.extension().unwrap(),
+        "nix",
+        "test files always end in .nix"
+    );
+
+    let code = std::fs::read_to_string(&code_path).expect("should be able to read test code");
+
+    let mut eval = crate::Evaluation::new_impure();
+    eval.strict = true;
+    eval.builtins.extend(mock_builtins::builtins());
+
+    let result = eval.evaluate(code, Some(code_path.clone()));
+    let failed = match result.value {
+        Some(Value::Catchable(_)) => true,
+        _ => !result.errors.is_empty(),
+    };
+    if expect_success && failed {
+        panic!(
+            "{}: evaluation of eval-okay test should succeed, but failed with {:?}",
+            code_path.display(),
+            result.errors,
+        );
+    }
+
+    if !expect_success && failed {
+        return;
+    }
+    // !expect_success can also mean the output differs, so don't panic if the
+    // evaluation didn't fail.
+
+    let value = result.value.unwrap();
+    let result_str = value.to_string();
+
+    let exp_path = code_path.with_extension("exp");
+    if exp_path.exists() {
+        // If there's an .exp file provided alongside, compare it with the
+        // output of the NixValue .to_string() method.
+        let exp_str = std::fs::read_to_string(&exp_path).expect("unable to read .exp file");
+
+        if expect_success {
+            assert_eq!(
+                result_str,
+                exp_str.trim(),
+                "{}: result value representation (left) must match expectation (right)",
+                code_path.display()
+            );
+        } else {
+            assert_ne!(
+                result_str,
+                exp_str.trim(),
+                "{}: test passed unexpectedly!  consider moving it out of notyetpassing",
+                code_path.display()
+            );
+
+            // Early return here, we don't compare .xml outputs if this is a !
+            // expect_success test.
+            return;
+        }
+    }
+
+    let exp_xml_path = code_path.with_extension("exp.xml");
+    if exp_xml_path.exists() {
+        // If there's an XML file provided alongside, compare it with the
+        // output produced when serializing the Value as XML.
+        let exp_xml_str = std::fs::read_to_string(exp_xml_path).expect("unable to read .xml file");
+
+        let mut xml_actual_buf = Vec::new();
+        crate::builtins::value_to_xml(&mut xml_actual_buf, &value).expect("value_to_xml failed");
+
+        assert_eq!(
+            String::from_utf8(xml_actual_buf).expect("to_xml produced invalid utf-8"),
+            exp_xml_str,
+            "{}: result value representation (left) must match expectation (right)",
+            code_path.display()
+        );
+    }
+}
+
+// identity-* tests contain Nix code snippets which should evaluate to
+// themselves exactly (i.e. literals).
+#[rstest]
+fn identity(#[files("src/tests/tvix_tests/identity-*.nix")] code_path: PathBuf) {
+    let code = std::fs::read_to_string(code_path).expect("should be able to read test code");
+
+    let mut eval = crate::Evaluation::new(Box::new(crate::StdIO) as Box<dyn EvalIO>, false);
+    eval.strict = true;
+
+    let result = eval.evaluate(&code, None);
+    assert!(
+        result.errors.is_empty(),
+        "evaluation of identity test failed: {:?}",
+        result.errors
+    );
+
+    let result_str = result.value.unwrap().to_string();
+
+    assert_eq!(
+        result_str,
+        code.trim(),
+        "result value representation (left) must match expectation (right)"
+    )
+}
+
+// eval-okay-* tests contain a snippet of Nix code, and an expectation
+// of the produced string output of the evaluator.
+//
+// These evaluations are always supposed to succeed, i.e. all snippets
+// are guaranteed to be valid Nix code.
+#[rstest]
+fn eval_okay(#[files("src/tests/tvix_tests/eval-okay-*.nix")] code_path: PathBuf) {
+    eval_test(code_path, true)
+}
+
+// eval-okay-* tests from the original Nix test suite.
+#[cfg(feature = "nix_tests")]
+#[rstest]
+fn nix_eval_okay(#[files("src/tests/nix_tests/eval-okay-*.nix")] code_path: PathBuf) {
+    eval_test(code_path, true)
+}
+
+// eval-okay-* tests from the original Nix test suite which do not yet pass for tvix
+//
+// Eventually there will be none of these left, and this function
+// will disappear :)
+//
+// Please don't submit failing tests unless they're in
+// notyetpassing; this makes the test suite much more useful for
+// regression testing, since there should always be zero non-ignored
+// failing tests.
+#[rstest]
+fn nix_eval_okay_currently_failing(
+    #[files("src/tests/nix_tests/notyetpassing/eval-okay-*.nix")] code_path: PathBuf,
+) {
+    eval_test(code_path, false)
+}
+
+#[rstest]
+fn eval_okay_currently_failing(
+    #[files("src/tests/tvix_tests/notyetpassing/eval-okay-*.nix")] code_path: PathBuf,
+) {
+    eval_test(code_path, false)
+}
+
+// eval-fail-* tests contain a snippet of Nix code, which is
+// expected to fail evaluation.  The exact type of failure
+// (assertion, parse error, etc) is not currently checked.
+#[rstest]
+fn eval_fail(#[files("src/tests/tvix_tests/eval-fail-*.nix")] code_path: PathBuf) {
+    eval_test(code_path, false)
+}
+
+// eval-fail-* tests from the original Nix test suite.
+#[cfg(feature = "nix_tests")]
+#[rstest]
+fn nix_eval_fail(#[files("src/tests/nix_tests/eval-fail-*.nix")] code_path: PathBuf) {
+    eval_test(code_path, false)
+}
diff --git a/tvix/eval/src/tests/nix_tests/README.md b/tvix/eval/src/tests/nix_tests/README.md
new file mode 100644
index 0000000000..357f3547da
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/README.md
@@ -0,0 +1,8 @@
+These test definitions are taken from the Nix 2.3 code base, they can
+be found upstream at:
+
+  https://github.com/NixOS/nix/tree/2.3.16/tests/lang
+
+These tests follow the licensing directions of Nix 2.3 itself:
+
+  https://github.com/NixOS/nix/blob/2.3.16/COPYING
diff --git a/tvix/eval/src/tests/nix_tests/binary-data b/tvix/eval/src/tests/nix_tests/binary-data
new file mode 100644
index 0000000000..06d7405020
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/binary-data
Binary files differdiff --git a/tvix/eval/src/tests/nix_tests/data b/tvix/eval/src/tests/nix_tests/data
new file mode 100644
index 0000000000..257cc5642c
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/data
@@ -0,0 +1 @@
+foo
diff --git a/tvix/eval/src/tests/nix_tests/dir1/a.nix b/tvix/eval/src/tests/nix_tests/dir1/a.nix
new file mode 100644
index 0000000000..231f150c57
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/dir1/a.nix
@@ -0,0 +1 @@
+"a"
diff --git a/tvix/eval/src/tests/nix_tests/dir2/a.nix b/tvix/eval/src/tests/nix_tests/dir2/a.nix
new file mode 100644
index 0000000000..170df520ab
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/dir2/a.nix
@@ -0,0 +1 @@
+"X"
diff --git a/tvix/eval/src/tests/nix_tests/dir2/b.nix b/tvix/eval/src/tests/nix_tests/dir2/b.nix
new file mode 100644
index 0000000000..19010cc35c
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/dir2/b.nix
@@ -0,0 +1 @@
+"b"
diff --git a/tvix/eval/src/tests/nix_tests/dir3/a.nix b/tvix/eval/src/tests/nix_tests/dir3/a.nix
new file mode 100644
index 0000000000..170df520ab
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/dir3/a.nix
@@ -0,0 +1 @@
+"X"
diff --git a/tvix/eval/src/tests/nix_tests/dir3/b.nix b/tvix/eval/src/tests/nix_tests/dir3/b.nix
new file mode 100644
index 0000000000..170df520ab
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/dir3/b.nix
@@ -0,0 +1 @@
+"X"
diff --git a/tvix/eval/src/tests/nix_tests/dir3/c.nix b/tvix/eval/src/tests/nix_tests/dir3/c.nix
new file mode 100644
index 0000000000..cdf158597e
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/dir3/c.nix
@@ -0,0 +1 @@
+"c"
diff --git a/tvix/eval/src/tests/nix_tests/dir4/a.nix b/tvix/eval/src/tests/nix_tests/dir4/a.nix
new file mode 100644
index 0000000000..170df520ab
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/dir4/a.nix
@@ -0,0 +1 @@
+"X"
diff --git a/tvix/eval/src/tests/nix_tests/dir4/c.nix b/tvix/eval/src/tests/nix_tests/dir4/c.nix
new file mode 100644
index 0000000000..170df520ab
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/dir4/c.nix
@@ -0,0 +1 @@
+"X"
diff --git a/tvix/eval/src/tests/nix_tests/eval-fail-abort.nix b/tvix/eval/src/tests/nix_tests/eval-fail-abort.nix
new file mode 100644
index 0000000000..75c51bceb5
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-fail-abort.nix
@@ -0,0 +1 @@
+if true then abort "this should fail" else 1
diff --git a/tvix/eval/src/tests/nix_tests/eval-fail-assert.nix b/tvix/eval/src/tests/nix_tests/eval-fail-assert.nix
new file mode 100644
index 0000000000..3b7a1e8bf0
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-fail-assert.nix
@@ -0,0 +1,5 @@
+let {
+  x = arg: assert arg == "y"; 123;
+
+  body = x "x";
+}
\ No newline at end of file
diff --git a/tvix/eval/src/tests/nix_tests/eval-fail-bad-antiquote-1.nix b/tvix/eval/src/tests/nix_tests/eval-fail-bad-antiquote-1.nix
new file mode 100644
index 0000000000..ffe9c983c2
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-fail-bad-antiquote-1.nix
@@ -0,0 +1 @@
+"${x: x}"
diff --git a/tvix/eval/src/tests/nix_tests/eval-fail-bad-antiquote-3.nix b/tvix/eval/src/tests/nix_tests/eval-fail-bad-antiquote-3.nix
new file mode 100644
index 0000000000..65b9d4f505
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-fail-bad-antiquote-3.nix
@@ -0,0 +1 @@
+''${x: x}''
diff --git a/tvix/eval/src/tests/nix_tests/eval-fail-blackhole.nix b/tvix/eval/src/tests/nix_tests/eval-fail-blackhole.nix
new file mode 100644
index 0000000000..81133b511c
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-fail-blackhole.nix
@@ -0,0 +1,5 @@
+let {
+  body = x;
+  x = y;
+  y = x;
+}
diff --git a/tvix/eval/src/tests/nix_tests/eval-fail-deepseq.nix b/tvix/eval/src/tests/nix_tests/eval-fail-deepseq.nix
new file mode 100644
index 0000000000..9baa49b063
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-fail-deepseq.nix
@@ -0,0 +1 @@
+builtins.deepSeq { x = abort "foo"; } 456
diff --git a/tvix/eval/src/tests/nix_tests/eval-fail-foldlStrict-strict-op-application.nix b/tvix/eval/src/tests/nix_tests/eval-fail-foldlStrict-strict-op-application.nix
new file mode 100644
index 0000000000..1620cc76ee
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-fail-foldlStrict-strict-op-application.nix
@@ -0,0 +1,5 @@
+# Tests that the result of applying op is forced even if the value is never used
+builtins.foldl'
+  (_: f: f null)
+  null
+  [ (_: throw "Not the final value, but is still forced!") (_: 23) ]
diff --git a/tvix/eval/src/tests/nix_tests/eval-fail-hashfile-missing.nix b/tvix/eval/src/tests/nix_tests/eval-fail-hashfile-missing.nix
new file mode 100644
index 0000000000..ce098b8238
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-fail-hashfile-missing.nix
@@ -0,0 +1,5 @@
+let
+  paths = [ ./this-file-is-definitely-not-there-7392097 "/and/neither/is/this/37293620" ];
+in
+  toString (builtins.concatLists (map (hash: map (builtins.hashFile hash) paths) ["md5" "sha1" "sha256" "sha512"]))
+
diff --git a/tvix/eval/src/tests/nix_tests/eval-fail-missing-arg.nix b/tvix/eval/src/tests/nix_tests/eval-fail-missing-arg.nix
new file mode 100644
index 0000000000..c4be9797c5
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-fail-missing-arg.nix
@@ -0,0 +1 @@
+({x, y, z}: x + y + z) {x = "foo"; z = "bar";}
diff --git a/tvix/eval/src/tests/nix_tests/eval-fail-path-slash.nix b/tvix/eval/src/tests/nix_tests/eval-fail-path-slash.nix
new file mode 100644
index 0000000000..8c2e104c78
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-fail-path-slash.nix
@@ -0,0 +1,6 @@
+# Trailing slashes in paths are not allowed.
+# This restriction could be lifted sometime,
+# for example if we make '/' a path concatenation operator.
+# See https://github.com/NixOS/nix/issues/1138
+# and https://nixos.org/nix-dev/2016-June/020829.html
+/nix/store/
diff --git a/tvix/eval/src/tests/nix_tests/eval-fail-remove.nix b/tvix/eval/src/tests/nix_tests/eval-fail-remove.nix
new file mode 100644
index 0000000000..539e0eb0a6
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-fail-remove.nix
@@ -0,0 +1,5 @@
+let {
+  attrs = {x = 123; y = 456;};
+
+  body = (removeAttrs attrs ["x"]).x;
+}
\ No newline at end of file
diff --git a/tvix/eval/src/tests/nix_tests/eval-fail-seq.nix b/tvix/eval/src/tests/nix_tests/eval-fail-seq.nix
new file mode 100644
index 0000000000..cddbbfd326
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-fail-seq.nix
@@ -0,0 +1 @@
+builtins.seq (abort "foo") 2
diff --git a/tvix/eval/src/tests/nix_tests/eval-fail-substring.nix b/tvix/eval/src/tests/nix_tests/eval-fail-substring.nix
new file mode 100644
index 0000000000..f37c2bc0a1
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-fail-substring.nix
@@ -0,0 +1 @@
+builtins.substring (builtins.sub 0 1) 1 "x"
diff --git a/tvix/eval/src/tests/nix_tests/eval-fail-to-path.nix b/tvix/eval/src/tests/nix_tests/eval-fail-to-path.nix
new file mode 100644
index 0000000000..5e322bc313
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-fail-to-path.nix
@@ -0,0 +1 @@
+builtins.toPath "foo/bar"
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-any-all.exp b/tvix/eval/src/tests/nix_tests/eval-okay-any-all.exp
new file mode 100644
index 0000000000..eb273f45b2
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-any-all.exp
@@ -0,0 +1 @@
+[ false false true true true true false true ]
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-any-all.nix b/tvix/eval/src/tests/nix_tests/eval-okay-any-all.nix
new file mode 100644
index 0000000000..a3f26ea2aa
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-any-all.nix
@@ -0,0 +1,11 @@
+with builtins;
+
+[ (any (x: x == 1) [])
+  (any (x: x == 1) [2 3 4])
+  (any (x: x == 1) [1 2 3 4])
+  (any (x: x == 1) [4 3 2 1])
+  (all (x: x == 1) [])
+  (all (x: x == 1) [1])
+  (all (x: x == 1) [1 2 3])
+  (all (x: x == 1) [1 1 1])
+]
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-arithmetic.exp b/tvix/eval/src/tests/nix_tests/eval-okay-arithmetic.exp
new file mode 100644
index 0000000000..5c54d10b7b
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-arithmetic.exp
@@ -0,0 +1 @@
+2216
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-arithmetic.nix b/tvix/eval/src/tests/nix_tests/eval-okay-arithmetic.nix
new file mode 100644
index 0000000000..7e9e6a0b66
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-arithmetic.nix
@@ -0,0 +1,59 @@
+with import ./lib.nix;
+
+let {
+
+  /* Supposedly tail recursive version:
+
+  range_ = accum: first: last:
+    if first == last then ([first] ++ accum)
+    else range_ ([first] ++ accum) (builtins.add first 1) last;
+
+  range = range_ [];
+  */
+
+  x = 12;
+
+  err = abort "urgh";
+
+  body = sum
+    [ (sum (range 1 50))
+      (123 + 456)
+      (0 + -10 + -(-11) + -x)
+      (10 - 7 - -2)
+      (10 - (6 - -1))
+      (10 - 1 + 2)
+      (3 * 4 * 5)
+      (56088 / 123 / 2)
+      (3 + 4 * const 5 0 - 6 / id 2)
+
+      (builtins.bitAnd 12 10) # 0b1100 & 0b1010 =  8
+      (builtins.bitOr  12 10) # 0b1100 | 0b1010 = 14
+      (builtins.bitXor 12 10) # 0b1100 ^ 0b1010 =  6
+
+      (if 3 < 7 then 1 else err)
+      (if 7 < 3 then err else 1)
+      (if 3 < 3 then err else 1)
+
+      (if 3 <= 7 then 1 else err)
+      (if 7 <= 3 then err else 1)
+      (if 3 <= 3 then 1 else err)
+
+      (if 3 > 7 then err else 1)
+      (if 7 > 3 then 1 else err)
+      (if 3 > 3 then err else 1)
+
+      (if 3 >= 7 then err else 1)
+      (if 7 >= 3 then 1 else err)
+      (if 3 >= 3 then 1 else err)
+
+      (if 2 > 1 == 1 < 2 then 1 else err)
+      (if 1 + 2 * 3 >= 7 then 1 else err)
+      (if 1 + 2 * 3 < 7 then err else 1)
+
+      # Not integer, but so what.
+      (if "aa" < "ab" then 1 else err)
+      (if "aa" < "aa" then err else 1)
+      (if "foo" < "foobar" then 1 else err)
+    ];
+
+}
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-attrnames.exp b/tvix/eval/src/tests/nix_tests/eval-okay-attrnames.exp
new file mode 100644
index 0000000000..b4aa387e07
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-attrnames.exp
@@ -0,0 +1 @@
+"newxfoonewxy"
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-attrnames.nix b/tvix/eval/src/tests/nix_tests/eval-okay-attrnames.nix
new file mode 100644
index 0000000000..e5b26e9f2e
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-attrnames.nix
@@ -0,0 +1,11 @@
+with import ./lib.nix;
+
+let
+
+  attrs = {y = "y"; x = "x"; foo = "foo";} // rec {x = "newx"; bar = x;};
+
+  names = builtins.attrNames attrs;
+
+  values = map (name: builtins.getAttr name attrs) names;
+
+in assert values == builtins.attrValues attrs; concat values
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-attrs.exp b/tvix/eval/src/tests/nix_tests/eval-okay-attrs.exp
new file mode 100644
index 0000000000..45b0f829eb
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-attrs.exp
@@ -0,0 +1 @@
+987
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-attrs.nix b/tvix/eval/src/tests/nix_tests/eval-okay-attrs.nix
new file mode 100644
index 0000000000..810b31a5da
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-attrs.nix
@@ -0,0 +1,5 @@
+let {
+  as = { x = 123; y = 456; } // { z = 789; } // { z = 987; };
+
+  body = if as ? a then as.a else assert as ? z; as.z;
+}
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-attrs2.exp b/tvix/eval/src/tests/nix_tests/eval-okay-attrs2.exp
new file mode 100644
index 0000000000..45b0f829eb
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-attrs2.exp
@@ -0,0 +1 @@
+987
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-attrs2.nix b/tvix/eval/src/tests/nix_tests/eval-okay-attrs2.nix
new file mode 100644
index 0000000000..9e06b83ac1
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-attrs2.nix
@@ -0,0 +1,10 @@
+let {
+  as = { x = 123; y = 456; } // { z = 789; } // { z = 987; };
+
+  A = "a";
+  Z = "z";
+
+  body = if builtins.hasAttr A as
+         then builtins.getAttr A as
+         else assert builtins.hasAttr Z as; builtins.getAttr Z as;
+}
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-attrs3.exp b/tvix/eval/src/tests/nix_tests/eval-okay-attrs3.exp
new file mode 100644
index 0000000000..19de4fdf79
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-attrs3.exp
@@ -0,0 +1 @@
+"foo 22 80 itchyxac"
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-attrs3.nix b/tvix/eval/src/tests/nix_tests/eval-okay-attrs3.nix
new file mode 100644
index 0000000000..f29de11fe6
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-attrs3.nix
@@ -0,0 +1,22 @@
+let
+
+  config = 
+    {
+      services.sshd.enable = true;
+      services.sshd.port = 22;
+      services.httpd.port = 80;
+      hostName = "itchy";
+      a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z = "x";
+      foo = {
+        a = "a";
+        b.c = "c";
+      };
+    };
+
+in
+  if config.services.sshd.enable
+  then "foo ${toString config.services.sshd.port} ${toString config.services.httpd.port} ${config.hostName}"
+       + "${config.a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z}"
+       + "${config.foo.a}"
+       + "${config.foo.b.c}"
+  else "bar"
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-attrs4.exp b/tvix/eval/src/tests/nix_tests/eval-okay-attrs4.exp
new file mode 100644
index 0000000000..1851731442
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-attrs4.exp
@@ -0,0 +1 @@
+[ true false true false false true false false ]
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-attrs4.nix b/tvix/eval/src/tests/nix_tests/eval-okay-attrs4.nix
new file mode 100644
index 0000000000..43ec81210f
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-attrs4.nix
@@ -0,0 +1,7 @@
+let
+
+  as = { x.y.z = 123; a.b.c = 456; };
+
+  bs = null;
+
+in [ (as ? x) (as ? y) (as ? x.y.z) (as ? x.y.z.a) (as ? x.y.a) (as ? a.b.c) (bs ? x) (bs ? x.y.z) ]
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-attrs5.exp b/tvix/eval/src/tests/nix_tests/eval-okay-attrs5.exp
new file mode 100644
index 0000000000..ce0430d780
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-attrs5.exp
@@ -0,0 +1 @@
+[ 123 "foo" 456 456 "foo" "xyzzy" "xyzzy" true ]
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-attrs5.nix b/tvix/eval/src/tests/nix_tests/eval-okay-attrs5.nix
new file mode 100644
index 0000000000..a4584cd3b3
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-attrs5.nix
@@ -0,0 +1,21 @@
+with import ./lib.nix;
+
+let
+
+  as = { x.y.z = 123; a.b.c = 456; };
+
+  bs = { f-o-o.bar = "foo"; };
+
+  or = x: y: x || y;
+  
+in
+  [ as.x.y.z
+    as.foo or "foo"
+    as.x.y.bla or as.a.b.c
+    as.a.b.c or as.x.y.z
+    as.x.y.bla or bs.f-o-o.bar or "xyzzy"
+    as.x.y.bla or bs.bar.foo or "xyzzy"
+    (123).bla or null.foo or "xyzzy"
+    # Backwards compatibility test.
+    (fold or [] [true false false])
+  ]
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-backslash-newline-1.exp b/tvix/eval/src/tests/nix_tests/eval-okay-backslash-newline-1.exp
new file mode 100644
index 0000000000..3e754364cc
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-backslash-newline-1.exp
@@ -0,0 +1 @@
+"a\nb"
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-backslash-newline-1.nix b/tvix/eval/src/tests/nix_tests/eval-okay-backslash-newline-1.nix
new file mode 100644
index 0000000000..7fef3dddd4
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-backslash-newline-1.nix
@@ -0,0 +1,2 @@
+"a\
+b"
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-backslash-newline-2.exp b/tvix/eval/src/tests/nix_tests/eval-okay-backslash-newline-2.exp
new file mode 100644
index 0000000000..3e754364cc
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-backslash-newline-2.exp
@@ -0,0 +1 @@
+"a\nb"
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-backslash-newline-2.nix b/tvix/eval/src/tests/nix_tests/eval-okay-backslash-newline-2.nix
new file mode 100644
index 0000000000..35ddf495c6
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-backslash-newline-2.nix
@@ -0,0 +1,2 @@
+''a''\
+b''
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-builtins-add.exp b/tvix/eval/src/tests/nix_tests/eval-okay-builtins-add.exp
new file mode 100644
index 0000000000..0350b518a7
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-builtins-add.exp
@@ -0,0 +1 @@
+[ 5 4 "int" "tt" "float" 4 ]
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-builtins-add.nix b/tvix/eval/src/tests/nix_tests/eval-okay-builtins-add.nix
new file mode 100644
index 0000000000..c841816222
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-builtins-add.nix
@@ -0,0 +1,8 @@
+[
+(builtins.add 2 3)
+(builtins.add 2 2)
+(builtins.typeOf (builtins.add 2  2))
+("t" + "t")
+(builtins.typeOf (builtins.add 2.0 2))
+(builtins.add 2.0 2)
+]
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-builtins.exp b/tvix/eval/src/tests/nix_tests/eval-okay-builtins.exp
new file mode 100644
index 0000000000..0661686d61
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-builtins.exp
@@ -0,0 +1 @@
+/foo
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-builtins.nix b/tvix/eval/src/tests/nix_tests/eval-okay-builtins.nix
new file mode 100644
index 0000000000..e9d65e88a8
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-builtins.nix
@@ -0,0 +1,12 @@
+assert builtins ? currentSystem;
+assert !builtins ? __currentSystem;
+
+let {
+
+  x = if builtins ? dirOf then builtins.dirOf /foo/bar else "";
+
+  y = if builtins ? fnord then builtins.fnord "foo" else "";
+
+  body = x + y;
+  
+}
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-callable-attrs.exp b/tvix/eval/src/tests/nix_tests/eval-okay-callable-attrs.exp
new file mode 100644
index 0000000000..27ba77ddaf
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-callable-attrs.exp
@@ -0,0 +1 @@
+true
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-callable-attrs.nix b/tvix/eval/src/tests/nix_tests/eval-okay-callable-attrs.nix
new file mode 100644
index 0000000000..310a030df0
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-callable-attrs.nix
@@ -0,0 +1 @@
+({ __functor = self: x: self.foo && x; foo = false; } // { foo = true; }) true
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-catattrs.exp b/tvix/eval/src/tests/nix_tests/eval-okay-catattrs.exp
new file mode 100644
index 0000000000..b4a1e66d6b
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-catattrs.exp
@@ -0,0 +1 @@
+[ 1 2 ]
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-catattrs.nix b/tvix/eval/src/tests/nix_tests/eval-okay-catattrs.nix
new file mode 100644
index 0000000000..2c3dc10da5
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-catattrs.nix
@@ -0,0 +1 @@
+builtins.catAttrs "a" [ { a = 1; } { b = 0; } { a = 2; } ]
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-closure.exp b/tvix/eval/src/tests/nix_tests/eval-okay-closure.exp
new file mode 100644
index 0000000000..e7dbf97816
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-closure.exp
@@ -0,0 +1 @@
+[ { foo = true; key = -13; } { foo = true; key = -12; } { foo = true; key = -11; } { foo = true; key = -9; } { foo = true; key = -8; } { foo = true; key = -7; } { foo = true; key = -5; } { foo = true; key = -4; } { foo = true; key = -3; } { key = -1; } { foo = true; key = 0; } { foo = true; key = 1; } { foo = true; key = 2; } { foo = true; key = 4; } { foo = true; key = 5; } { foo = true; key = 6; } { key = 8; } { foo = true; key = 9; } { foo = true; key = 10; } { foo = true; key = 13; } { foo = true; key = 14; } { foo = true; key = 15; } { key = 17; } { foo = true; key = 18; } { foo = true; key = 19; } { foo = true; key = 22; } { foo = true; key = 23; } { key = 26; } { foo = true; key = 27; } { foo = true; key = 28; } { foo = true; key = 31; } { foo = true; key = 32; } { key = 35; } { foo = true; key = 36; } { foo = true; key = 40; } { foo = true; key = 41; } { key = 44; } { foo = true; key = 45; } { foo = true; key = 49; } { key = 53; } { foo = true; key = 54; } { foo = true; key = 58; } { key = 62; } { foo = true; key = 67; } { key = 71; } { key = 80; } ]
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-closure.exp.xml b/tvix/eval/src/tests/nix_tests/eval-okay-closure.exp.xml
new file mode 100644
index 0000000000..dffc03a998
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-closure.exp.xml
@@ -0,0 +1,343 @@
+<?xml version='1.0' encoding='utf-8'?>
+<expr>
+  <list>
+    <attrs>
+      <attr name="foo">
+        <bool value="true" />
+      </attr>
+      <attr name="key">
+        <int value="-13" />
+      </attr>
+    </attrs>
+    <attrs>
+      <attr name="foo">
+        <bool value="true" />
+      </attr>
+      <attr name="key">
+        <int value="-12" />
+      </attr>
+    </attrs>
+    <attrs>
+      <attr name="foo">
+        <bool value="true" />
+      </attr>
+      <attr name="key">
+        <int value="-11" />
+      </attr>
+    </attrs>
+    <attrs>
+      <attr name="foo">
+        <bool value="true" />
+      </attr>
+      <attr name="key">
+        <int value="-9" />
+      </attr>
+    </attrs>
+    <attrs>
+      <attr name="foo">
+        <bool value="true" />
+      </attr>
+      <attr name="key">
+        <int value="-8" />
+      </attr>
+    </attrs>
+    <attrs>
+      <attr name="foo">
+        <bool value="true" />
+      </attr>
+      <attr name="key">
+        <int value="-7" />
+      </attr>
+    </attrs>
+    <attrs>
+      <attr name="foo">
+        <bool value="true" />
+      </attr>
+      <attr name="key">
+        <int value="-5" />
+      </attr>
+    </attrs>
+    <attrs>
+      <attr name="foo">
+        <bool value="true" />
+      </attr>
+      <attr name="key">
+        <int value="-4" />
+      </attr>
+    </attrs>
+    <attrs>
+      <attr name="foo">
+        <bool value="true" />
+      </attr>
+      <attr name="key">
+        <int value="-3" />
+      </attr>
+    </attrs>
+    <attrs>
+      <attr name="key">
+        <int value="-1" />
+      </attr>
+    </attrs>
+    <attrs>
+      <attr name="foo">
+        <bool value="true" />
+      </attr>
+      <attr name="key">
+        <int value="0" />
+      </attr>
+    </attrs>
+    <attrs>
+      <attr name="foo">
+        <bool value="true" />
+      </attr>
+      <attr name="key">
+        <int value="1" />
+      </attr>
+    </attrs>
+    <attrs>
+      <attr name="foo">
+        <bool value="true" />
+      </attr>
+      <attr name="key">
+        <int value="2" />
+      </attr>
+    </attrs>
+    <attrs>
+      <attr name="foo">
+        <bool value="true" />
+      </attr>
+      <attr name="key">
+        <int value="4" />
+      </attr>
+    </attrs>
+    <attrs>
+      <attr name="foo">
+        <bool value="true" />
+      </attr>
+      <attr name="key">
+        <int value="5" />
+      </attr>
+    </attrs>
+    <attrs>
+      <attr name="foo">
+        <bool value="true" />
+      </attr>
+      <attr name="key">
+        <int value="6" />
+      </attr>
+    </attrs>
+    <attrs>
+      <attr name="key">
+        <int value="8" />
+      </attr>
+    </attrs>
+    <attrs>
+      <attr name="foo">
+        <bool value="true" />
+      </attr>
+      <attr name="key">
+        <int value="9" />
+      </attr>
+    </attrs>
+    <attrs>
+      <attr name="foo">
+        <bool value="true" />
+      </attr>
+      <attr name="key">
+        <int value="10" />
+      </attr>
+    </attrs>
+    <attrs>
+      <attr name="foo">
+        <bool value="true" />
+      </attr>
+      <attr name="key">
+        <int value="13" />
+      </attr>
+    </attrs>
+    <attrs>
+      <attr name="foo">
+        <bool value="true" />
+      </attr>
+      <attr name="key">
+        <int value="14" />
+      </attr>
+    </attrs>
+    <attrs>
+      <attr name="foo">
+        <bool value="true" />
+      </attr>
+      <attr name="key">
+        <int value="15" />
+      </attr>
+    </attrs>
+    <attrs>
+      <attr name="key">
+        <int value="17" />
+      </attr>
+    </attrs>
+    <attrs>
+      <attr name="foo">
+        <bool value="true" />
+      </attr>
+      <attr name="key">
+        <int value="18" />
+      </attr>
+    </attrs>
+    <attrs>
+      <attr name="foo">
+        <bool value="true" />
+      </attr>
+      <attr name="key">
+        <int value="19" />
+      </attr>
+    </attrs>
+    <attrs>
+      <attr name="foo">
+        <bool value="true" />
+      </attr>
+      <attr name="key">
+        <int value="22" />
+      </attr>
+    </attrs>
+    <attrs>
+      <attr name="foo">
+        <bool value="true" />
+      </attr>
+      <attr name="key">
+        <int value="23" />
+      </attr>
+    </attrs>
+    <attrs>
+      <attr name="key">
+        <int value="26" />
+      </attr>
+    </attrs>
+    <attrs>
+      <attr name="foo">
+        <bool value="true" />
+      </attr>
+      <attr name="key">
+        <int value="27" />
+      </attr>
+    </attrs>
+    <attrs>
+      <attr name="foo">
+        <bool value="true" />
+      </attr>
+      <attr name="key">
+        <int value="28" />
+      </attr>
+    </attrs>
+    <attrs>
+      <attr name="foo">
+        <bool value="true" />
+      </attr>
+      <attr name="key">
+        <int value="31" />
+      </attr>
+    </attrs>
+    <attrs>
+      <attr name="foo">
+        <bool value="true" />
+      </attr>
+      <attr name="key">
+        <int value="32" />
+      </attr>
+    </attrs>
+    <attrs>
+      <attr name="key">
+        <int value="35" />
+      </attr>
+    </attrs>
+    <attrs>
+      <attr name="foo">
+        <bool value="true" />
+      </attr>
+      <attr name="key">
+        <int value="36" />
+      </attr>
+    </attrs>
+    <attrs>
+      <attr name="foo">
+        <bool value="true" />
+      </attr>
+      <attr name="key">
+        <int value="40" />
+      </attr>
+    </attrs>
+    <attrs>
+      <attr name="foo">
+        <bool value="true" />
+      </attr>
+      <attr name="key">
+        <int value="41" />
+      </attr>
+    </attrs>
+    <attrs>
+      <attr name="key">
+        <int value="44" />
+      </attr>
+    </attrs>
+    <attrs>
+      <attr name="foo">
+        <bool value="true" />
+      </attr>
+      <attr name="key">
+        <int value="45" />
+      </attr>
+    </attrs>
+    <attrs>
+      <attr name="foo">
+        <bool value="true" />
+      </attr>
+      <attr name="key">
+        <int value="49" />
+      </attr>
+    </attrs>
+    <attrs>
+      <attr name="key">
+        <int value="53" />
+      </attr>
+    </attrs>
+    <attrs>
+      <attr name="foo">
+        <bool value="true" />
+      </attr>
+      <attr name="key">
+        <int value="54" />
+      </attr>
+    </attrs>
+    <attrs>
+      <attr name="foo">
+        <bool value="true" />
+      </attr>
+      <attr name="key">
+        <int value="58" />
+      </attr>
+    </attrs>
+    <attrs>
+      <attr name="key">
+        <int value="62" />
+      </attr>
+    </attrs>
+    <attrs>
+      <attr name="foo">
+        <bool value="true" />
+      </attr>
+      <attr name="key">
+        <int value="67" />
+      </attr>
+    </attrs>
+    <attrs>
+      <attr name="key">
+        <int value="71" />
+      </attr>
+    </attrs>
+    <attrs>
+      <attr name="key">
+        <int value="80" />
+      </attr>
+    </attrs>
+  </list>
+</expr>
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-closure.nix b/tvix/eval/src/tests/nix_tests/eval-okay-closure.nix
new file mode 100644
index 0000000000..cccd4dc357
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-closure.nix
@@ -0,0 +1,13 @@
+let
+
+  closure = builtins.genericClosure {
+    startSet = [{key = 80;}];
+    operator = {key, foo ? false}:
+      if builtins.lessThan key 0
+      then []
+      else [{key = builtins.sub key 9;} {key = builtins.sub key 13; foo = true;}];
+  };
+
+  sort = (import ./lib.nix).sortBy (a: b: builtins.lessThan a.key b.key);
+
+in sort closure
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-comments.exp b/tvix/eval/src/tests/nix_tests/eval-okay-comments.exp
new file mode 100644
index 0000000000..7182dc2f9b
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-comments.exp
@@ -0,0 +1 @@
+"abcdefghijklmnopqrstuvwxyz"
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-comments.nix b/tvix/eval/src/tests/nix_tests/eval-okay-comments.nix
new file mode 100644
index 0000000000..cb2cce2180
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-comments.nix
@@ -0,0 +1,59 @@
+# A simple comment
+"a"+ # And another
+## A double comment
+"b"+  ## And another
+# Nested # comments #
+"c"+   # and # some # other #
+# An empty line, following here:
+
+"d"+      # and a comment not starting the line !
+
+"e"+
+/* multiline comments */
+"f" +
+/* multiline
+   comments,
+   on
+   multiple
+   lines
+*/
+"g" +
+# Small, tricky comments
+/**/ "h"+ /*/*/ "i"+ /***/ "j"+ /* /*/ "k"+ /*/* /*/ "l"+
+# Comments with an even number of ending '*' used to fail:
+"m"+
+/* */ /* **/ /* ***/ /* ****/ "n"+
+/* */ /** */ /*** */ /**** */ "o"+
+/** **/ /*** ***/ /**** ****/ "p"+
+/* * ** *** **** ***** */     "q"+
+# Random comments
+/* ***** ////// * / * / /* */ "r"+
+# Mixed comments
+/* # */
+"s"+
+# /* #
+"t"+
+# /* # */
+"u"+
+# /*********/
+"v"+
+## */*
+"w"+
+/*
+ * Multiline, decorated comments
+ * # This ain't a nest'd comm'nt
+ */
+"x"+
+''${/** with **/"y"
+  # real
+  /* comments
+     inside ! # */
+
+  # (and empty lines)
+
+}''+          /* And a multiline comment,
+                 on the same line,
+                 after some spaces
+*/             # followed by a one-line comment
+"z"
+/* EOF */
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-concat.exp b/tvix/eval/src/tests/nix_tests/eval-okay-concat.exp
new file mode 100644
index 0000000000..bb4bbd5774
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-concat.exp
@@ -0,0 +1 @@
+[ 1 2 3 4 5 6 7 8 9 ]
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-concat.nix b/tvix/eval/src/tests/nix_tests/eval-okay-concat.nix
new file mode 100644
index 0000000000..d158a9bf05
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-concat.nix
@@ -0,0 +1 @@
+[1 2 3] ++ [4 5 6] ++ [7 8 9]
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-concatmap.exp b/tvix/eval/src/tests/nix_tests/eval-okay-concatmap.exp
new file mode 100644
index 0000000000..3b8be7739d
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-concatmap.exp
@@ -0,0 +1 @@
+[ [ 1 3 5 7 9 ] [ "a" "z" "b" "z" ] ]
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-concatmap.nix b/tvix/eval/src/tests/nix_tests/eval-okay-concatmap.nix
new file mode 100644
index 0000000000..97da5d37a4
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-concatmap.nix
@@ -0,0 +1,5 @@
+with import ./lib.nix;
+
+[ (builtins.concatMap (x: if x / 2 * 2 == x then [] else [ x ]) (range 0 10))
+  (builtins.concatMap (x: [x] ++ ["z"]) ["a" "b"])
+]
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-concatstringssep.exp b/tvix/eval/src/tests/nix_tests/eval-okay-concatstringssep.exp
new file mode 100644
index 0000000000..93987647ff
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-concatstringssep.exp
@@ -0,0 +1 @@
+[ "" "foobarxyzzy" "foo, bar, xyzzy" "foo" "" ]
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-concatstringssep.nix b/tvix/eval/src/tests/nix_tests/eval-okay-concatstringssep.nix
new file mode 100644
index 0000000000..adc4c41bd5
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-concatstringssep.nix
@@ -0,0 +1,8 @@
+with builtins;
+
+[ (concatStringsSep "" [])
+  (concatStringsSep "" ["foo" "bar" "xyzzy"])
+  (concatStringsSep ", " ["foo" "bar" "xyzzy"])
+  (concatStringsSep ", " ["foo"])
+  (concatStringsSep ", " [])
+]
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-deepseq.exp b/tvix/eval/src/tests/nix_tests/eval-okay-deepseq.exp
new file mode 100644
index 0000000000..8d38505c16
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-deepseq.exp
@@ -0,0 +1 @@
+456
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-deepseq.nix b/tvix/eval/src/tests/nix_tests/eval-okay-deepseq.nix
new file mode 100644
index 0000000000..53aa4b1dc2
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-deepseq.nix
@@ -0,0 +1 @@
+builtins.deepSeq (let as = { x = 123; y = as; }; in as) 456
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-delayed-with-inherit.exp b/tvix/eval/src/tests/nix_tests/eval-okay-delayed-with-inherit.exp
new file mode 100644
index 0000000000..eaacb55c1a
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-delayed-with-inherit.exp
@@ -0,0 +1 @@
+"b-overridden"
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-delayed-with-inherit.nix b/tvix/eval/src/tests/nix_tests/eval-okay-delayed-with-inherit.nix
new file mode 100644
index 0000000000..84b388c271
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-delayed-with-inherit.nix
@@ -0,0 +1,24 @@
+let
+  pkgs_ = with pkgs; {
+    a = derivation {
+      name = "a";
+      system = builtins.currentSystem;
+      builder = "/bin/sh";
+      args = [ "-c" "touch $out" ];
+      inherit b;
+    };
+
+    inherit b;
+  };
+
+  packageOverrides = p: {
+    b = derivation {
+      name = "b-overridden";
+      system = builtins.currentSystem;
+      builder = "/bin/sh";
+      args = [ "-c" "touch $out" ];
+    };
+  };
+
+  pkgs = pkgs_ // (packageOverrides pkgs_);
+in pkgs.a.b.name
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-dynamic-attrs-2.exp b/tvix/eval/src/tests/nix_tests/eval-okay-dynamic-attrs-2.exp
new file mode 100644
index 0000000000..27ba77ddaf
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-dynamic-attrs-2.exp
@@ -0,0 +1 @@
+true
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-dynamic-attrs-2.nix b/tvix/eval/src/tests/nix_tests/eval-okay-dynamic-attrs-2.nix
new file mode 100644
index 0000000000..6d57bf8549
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-dynamic-attrs-2.nix
@@ -0,0 +1 @@
+{ a."${"b"}" = true; a."${"c"}" = false; }.a.b
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-dynamic-attrs-bare.exp b/tvix/eval/src/tests/nix_tests/eval-okay-dynamic-attrs-bare.exp
new file mode 100644
index 0000000000..df8750afc0
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-dynamic-attrs-bare.exp
@@ -0,0 +1 @@
+{ binds = true; hasAttrs = true; multiAttrs = true; recBinds = true; selectAttrs = true; selectOrAttrs = true; }
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-dynamic-attrs-bare.nix b/tvix/eval/src/tests/nix_tests/eval-okay-dynamic-attrs-bare.nix
new file mode 100644
index 0000000000..0dbe15e638
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-dynamic-attrs-bare.nix
@@ -0,0 +1,17 @@
+let
+  aString = "a";
+
+  bString = "b";
+in {
+  hasAttrs = { a.b = null; } ? ${aString}.b;
+
+  selectAttrs = { a.b = true; }.a.${bString};
+
+  selectOrAttrs = { }.${aString} or true;
+
+  binds = { ${aString}."${bString}c" = true; }.a.bc;
+
+  recBinds = rec { ${bString} = a; a = true; }.b;
+
+  multiAttrs = { ${aString} = true; ${bString} = false; }.a;
+}
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-dynamic-attrs.exp b/tvix/eval/src/tests/nix_tests/eval-okay-dynamic-attrs.exp
new file mode 100644
index 0000000000..df8750afc0
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-dynamic-attrs.exp
@@ -0,0 +1 @@
+{ binds = true; hasAttrs = true; multiAttrs = true; recBinds = true; selectAttrs = true; selectOrAttrs = true; }
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-dynamic-attrs.nix b/tvix/eval/src/tests/nix_tests/eval-okay-dynamic-attrs.nix
new file mode 100644
index 0000000000..ee02ac7e65
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-dynamic-attrs.nix
@@ -0,0 +1,17 @@
+let
+  aString = "a";
+
+  bString = "b";
+in {
+  hasAttrs = { a.b = null; } ? "${aString}".b;
+
+  selectAttrs = { a.b = true; }.a."${bString}";
+
+  selectOrAttrs = { }."${aString}" or true;
+
+  binds = { "${aString}"."${bString}c" = true; }.a.bc;
+
+  recBinds = rec { "${bString}" = a; a = true; }.b;
+
+  multiAttrs = { "${aString}" = true; "${bString}" = false; }.a;
+}
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-elem.exp b/tvix/eval/src/tests/nix_tests/eval-okay-elem.exp
new file mode 100644
index 0000000000..3cf6c0e962
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-elem.exp
@@ -0,0 +1 @@
+[ true false 30 ]
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-elem.nix b/tvix/eval/src/tests/nix_tests/eval-okay-elem.nix
new file mode 100644
index 0000000000..71ea7a4ed0
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-elem.nix
@@ -0,0 +1,6 @@
+with import ./lib.nix;
+
+let xs = range 10 40; in
+
+[ (builtins.elem 23 xs) (builtins.elem 42 xs) (builtins.elemAt xs 20) ]
+
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-empty-args.exp b/tvix/eval/src/tests/nix_tests/eval-okay-empty-args.exp
new file mode 100644
index 0000000000..cb5537d5d7
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-empty-args.exp
@@ -0,0 +1 @@
+"ab"
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-empty-args.nix b/tvix/eval/src/tests/nix_tests/eval-okay-empty-args.nix
new file mode 100644
index 0000000000..78c133afdd
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-empty-args.nix
@@ -0,0 +1 @@
+({}: {x,y,}: "${x}${y}") {} {x = "a"; y = "b";}
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-eq.exp b/tvix/eval/src/tests/nix_tests/eval-okay-eq.exp
new file mode 100644
index 0000000000..27ba77ddaf
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-eq.exp
@@ -0,0 +1 @@
+true
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-eq.nix b/tvix/eval/src/tests/nix_tests/eval-okay-eq.nix
new file mode 100644
index 0000000000..73d200b381
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-eq.nix
@@ -0,0 +1,3 @@
+["foobar" (rec {x = 1; y = x;})]
+==
+[("foo" + "bar") ({x = 1; y = 1;})]
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-filter.exp b/tvix/eval/src/tests/nix_tests/eval-okay-filter.exp
new file mode 100644
index 0000000000..355d51c27d
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-filter.exp
@@ -0,0 +1 @@
+[ 0 2 4 6 8 10 100 102 104 106 108 110 ]
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-filter.nix b/tvix/eval/src/tests/nix_tests/eval-okay-filter.nix
new file mode 100644
index 0000000000..85109b0d0e
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-filter.nix
@@ -0,0 +1,5 @@
+with import ./lib.nix;
+
+builtins.filter
+  (x: x / 2 * 2 == x)
+  (builtins.concatLists [ (range 0 10) (range 100 110) ])
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-flatten.exp b/tvix/eval/src/tests/nix_tests/eval-okay-flatten.exp
new file mode 100644
index 0000000000..b979b2b8b9
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-flatten.exp
@@ -0,0 +1 @@
+"1234567"
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-flatten.nix b/tvix/eval/src/tests/nix_tests/eval-okay-flatten.nix
new file mode 100644
index 0000000000..fe911e9683
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-flatten.nix
@@ -0,0 +1,8 @@
+with import ./lib.nix;
+
+let {
+
+  l = ["1" "2" ["3" ["4"] ["5" "6"]] "7"];
+
+  body = concat (flatten l);
+}
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-float.exp b/tvix/eval/src/tests/nix_tests/eval-okay-float.exp
new file mode 100644
index 0000000000..3c50a8adce
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-float.exp
@@ -0,0 +1 @@
+[ 3.4 3.5 2.5 1.5 ]
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-float.nix b/tvix/eval/src/tests/nix_tests/eval-okay-float.nix
new file mode 100644
index 0000000000..b2702c7b16
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-float.nix
@@ -0,0 +1,6 @@
+[
+  (1.1 + 2.3)
+  (builtins.add (0.5 + 0.5) (2.0 + 0.5))
+  ((0.5 + 0.5) * (2.0 + 0.5))
+  ((1.5 + 1.5) / (0.5 * 4.0))
+]
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-foldlStrict-lazy-elements.exp b/tvix/eval/src/tests/nix_tests/eval-okay-foldlStrict-lazy-elements.exp
new file mode 100644
index 0000000000..d81cc0710e
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-foldlStrict-lazy-elements.exp
@@ -0,0 +1 @@
+42
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-foldlStrict-lazy-elements.nix b/tvix/eval/src/tests/nix_tests/eval-okay-foldlStrict-lazy-elements.nix
new file mode 100644
index 0000000000..c666e07f3a
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-foldlStrict-lazy-elements.nix
@@ -0,0 +1,9 @@
+# Tests that the rhs argument of op is not forced unconditionally
+let
+  lst = builtins.foldl'
+    (acc: x: acc ++ [ x ])
+    [ ]
+    [ 42 (throw "this shouldn't be evaluated") ];
+in
+
+builtins.head lst
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-foldlStrict-lazy-initial-accumulator.exp b/tvix/eval/src/tests/nix_tests/eval-okay-foldlStrict-lazy-initial-accumulator.exp
new file mode 100644
index 0000000000..d81cc0710e
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-foldlStrict-lazy-initial-accumulator.exp
@@ -0,0 +1 @@
+42
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-foldlStrict-lazy-initial-accumulator.nix b/tvix/eval/src/tests/nix_tests/eval-okay-foldlStrict-lazy-initial-accumulator.nix
new file mode 100644
index 0000000000..abcd5366ab
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-foldlStrict-lazy-initial-accumulator.nix
@@ -0,0 +1,6 @@
+# Checks that the nul value for the accumulator is not forced unconditionally.
+# Some languages provide a foldl' that is strict in this argument, but Nix does not.
+builtins.foldl'
+  (_: x: x)
+  (throw "This is never forced")
+  [ "but the results of applying op are" 42 ]
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-foldlStrict.exp b/tvix/eval/src/tests/nix_tests/eval-okay-foldlStrict.exp
new file mode 100644
index 0000000000..837e12b406
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-foldlStrict.exp
@@ -0,0 +1 @@
+500500
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-foldlStrict.nix b/tvix/eval/src/tests/nix_tests/eval-okay-foldlStrict.nix
new file mode 100644
index 0000000000..3b87188d24
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-foldlStrict.nix
@@ -0,0 +1,3 @@
+with import ./lib.nix;
+
+builtins.foldl' (x: y: x + y) 0 (range 1 1000)
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-fromTOML.exp b/tvix/eval/src/tests/nix_tests/eval-okay-fromTOML.exp
new file mode 100644
index 0000000000..d0dd3af2c8
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-fromTOML.exp
@@ -0,0 +1 @@
+[ { clients = { data = [ [ "gamma" "delta" ] [ 1 2 ] ]; hosts = [ "alpha" "omega" ]; }; database = { connection_max = 5000; enabled = true; ports = [ 8001 8001 8002 ]; server = "192.168.1.1"; }; owner = { name = "Tom Preston-Werner"; }; servers = { alpha = { dc = "eqdc10"; ip = "10.0.0.1"; }; beta = { dc = "eqdc10"; ip = "10.0.0.2"; }; }; title = "TOML Example"; } { "1234" = "value"; "127.0.0.1" = "value"; a = { b = { c = { }; }; }; arr1 = [ 1 2 3 ]; arr2 = [ "red" "yellow" "green" ]; arr3 = [ [ 1 2 ] [ 3 4 5 ] ]; arr4 = [ "all" "strings" "are the same" "type" ]; arr5 = [ [ 1 2 ] [ "a" "b" "c" ] ]; arr7 = [ 1 2 3 ]; arr8 = [ 1 2 ]; bare-key = "value"; bare_key = "value"; bin1 = 214; bool1 = true; bool2 = false; "character encoding" = "value"; d = { e = { f = { }; }; }; dog = { "tater.man" = { type = { name = "pug"; }; }; }; flt1 = 1; flt2 = 3.1415; flt3 = -0.01; flt4 = 5e+22; flt5 = 1e+06; flt6 = -0.02; flt7 = 6.626e-34; flt8 = 9.22462e+06; fruit = [ { name = "apple"; physical = { color = "red"; shape = "round"; }; variety = [ { name = "red delicious"; } { name = "granny smith"; } ]; } { name = "banana"; variety = [ { name = "plantain"; } ]; } ]; g = { h = { i = { }; }; }; hex1 = 3735928559; hex2 = 3735928559; hex3 = 3735928559; int1 = 99; int2 = 42; int3 = 0; int4 = -17; int5 = 1000; int6 = 5349221; int7 = 12345; j = { "ʞ" = { l = { }; }; }; key = "value"; key2 = "value"; name = "Orange"; oct1 = 342391; oct2 = 493; physical = { color = "orange"; shape = "round"; }; products = [ { name = "Hammer"; sku = 738594937; } { } { color = "gray"; name = "Nail"; sku = 284758393; } ]; "quoted \"value\"" = "value"; site = { "google.com" = true; }; str = "I'm a string. \"You can quote me\". Name\tJosé\nLocation\tSF."; table-1 = { key1 = "some string"; key2 = 123; }; table-2 = { key1 = "another string"; key2 = 456; }; x = { y = { z = { w = { animal = { type = { name = "pug"; }; }; name = { first = "Tom"; last = "Preston-Werner"; }; point = { x = 1; y = 2; }; }; }; }; }; "ʎǝʞ" = "value"; } { metadata = { "checksum aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d6531d44de723825aa81398a6415283229725a00fa30713812ab9323faa82fc4"; "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"; "checksum ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "23ac7c30002a5accbf7e8987d0632fa6de155b7c3d39d0067317a391e00a2ef6"; "checksum arrayvec 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" = "a1e964f9e24d588183fcb43503abda40d288c8657dfc27311516ce2f05675aef"; }; package = [ { dependencies = [ "memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" ]; name = "aho-corasick"; source = "registry+https://github.com/rust-lang/crates.io-index"; version = "0.6.4"; } { name = "ansi_term"; source = "registry+https://github.com/rust-lang/crates.io-index"; version = "0.9.0"; } { dependencies = [ "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)" "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" ]; name = "atty"; source = "registry+https://github.com/rust-lang/crates.io-index"; version = "0.2.10"; } ]; } { a = [ [ { b = true; } ] ]; c = [ [ { d = true; } ] ]; e = [ [ 123 ] ]; } ]
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-fromTOML.nix b/tvix/eval/src/tests/nix_tests/eval-okay-fromTOML.nix
new file mode 100644
index 0000000000..9639326899
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-fromTOML.nix
@@ -0,0 +1,208 @@
+[
+
+  (builtins.fromTOML ''
+    # This is a TOML document.
+
+    title = "TOML Example"
+
+    [owner]
+    name = "Tom Preston-Werner"
+    #dob = 1979-05-27T07:32:00-08:00 # First class dates
+
+    [database]
+    server = "192.168.1.1"
+    ports = [ 8001, 8001, 8002 ]
+    connection_max = 5000
+    enabled = true
+
+    [servers]
+
+      # Indentation (tabs and/or spaces) is allowed but not required
+      [servers.alpha]
+      ip = "10.0.0.1"
+      dc = "eqdc10"
+
+      [servers.beta]
+      ip = "10.0.0.2"
+      dc = "eqdc10"
+
+    [clients]
+    data = [ ["gamma", "delta"], [1, 2] ]
+
+    # Line breaks are OK when inside arrays
+    hosts = [
+      "alpha",
+      "omega"
+    ]
+  '')
+
+  (builtins.fromTOML ''
+    key = "value"
+    bare_key = "value"
+    bare-key = "value"
+    1234 = "value"
+
+    "127.0.0.1" = "value"
+    "character encoding" = "value"
+    "ʎǝʞ" = "value"
+    'key2' = "value"
+    'quoted "value"' = "value"
+
+    name = "Orange"
+
+    physical.color = "orange"
+    physical.shape = "round"
+    site."google.com" = true
+
+    # This is legal according to the spec, but cpptoml doesn't handle it.
+    #a.b.c = 1
+    #a.d = 2
+
+    str = "I'm a string. \"You can quote me\". Name\tJos\u00E9\nLocation\tSF."
+
+    int1 = +99
+    int2 = 42
+    int3 = 0
+    int4 = -17
+    int5 = 1_000
+    int6 = 5_349_221
+    int7 = 1_2_3_4_5
+
+    hex1 = 0xDEADBEEF
+    hex2 = 0xdeadbeef
+    hex3 = 0xdead_beef
+
+    oct1 = 0o01234567
+    oct2 = 0o755
+
+    bin1 = 0b11010110
+
+    flt1 = +1.0
+    flt2 = 3.1415
+    flt3 = -0.01
+    flt4 = 5e+22
+    flt5 = 1e6
+    flt6 = -2E-2
+    flt7 = 6.626e-34
+    flt8 = 9_224_617.445_991_228_313
+
+    bool1 = true
+    bool2 = false
+
+    # FIXME: not supported because Nix doesn't have a date/time type.
+    #odt1 = 1979-05-27T07:32:00Z
+    #odt2 = 1979-05-27T00:32:00-07:00
+    #odt3 = 1979-05-27T00:32:00.999999-07:00
+    #odt4 = 1979-05-27 07:32:00Z
+    #ldt1 = 1979-05-27T07:32:00
+    #ldt2 = 1979-05-27T00:32:00.999999
+    #ld1 = 1979-05-27
+    #lt1 = 07:32:00
+    #lt2 = 00:32:00.999999
+
+    arr1 = [ 1, 2, 3 ]
+    arr2 = [ "red", "yellow", "green" ]
+    arr3 = [ [ 1, 2 ], [3, 4, 5] ]
+    arr4 = [ "all", 'strings', """are the same""", ''''type'''']
+    arr5 = [ [ 1, 2 ], ["a", "b", "c"] ]
+
+    arr7 = [
+      1, 2, 3
+    ]
+
+    arr8 = [
+      1,
+      2, # this is ok
+    ]
+
+    [table-1]
+    key1 = "some string"
+    key2 = 123
+
+
+    [table-2]
+    key1 = "another string"
+    key2 = 456
+
+    [dog."tater.man"]
+    type.name = "pug"
+
+    [a.b.c]
+    [ d.e.f ]
+    [ g .  h  . i ]
+    [ j . "ʞ" . 'l' ]
+    [x.y.z.w]
+
+    name = { first = "Tom", last = "Preston-Werner" }
+    point = { x = 1, y = 2 }
+    animal = { type.name = "pug" }
+
+    [[products]]
+    name = "Hammer"
+    sku = 738594937
+
+    [[products]]
+
+    [[products]]
+    name = "Nail"
+    sku = 284758393
+    color = "gray"
+
+    [[fruit]]
+      name = "apple"
+
+      [fruit.physical]
+        color = "red"
+        shape = "round"
+
+      [[fruit.variety]]
+        name = "red delicious"
+
+      [[fruit.variety]]
+        name = "granny smith"
+
+    [[fruit]]
+      name = "banana"
+
+      [[fruit.variety]]
+        name = "plantain"
+  '')
+
+  (builtins.fromTOML ''
+    [[package]]
+    name = "aho-corasick"
+    version = "0.6.4"
+    source = "registry+https://github.com/rust-lang/crates.io-index"
+    dependencies = [
+     "memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+    ]
+
+    [[package]]
+    name = "ansi_term"
+    version = "0.9.0"
+    source = "registry+https://github.com/rust-lang/crates.io-index"
+
+    [[package]]
+    name = "atty"
+    version = "0.2.10"
+    source = "registry+https://github.com/rust-lang/crates.io-index"
+    dependencies = [
+     "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)",
+     "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
+     "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
+    ]
+
+    [metadata]
+    "checksum aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d6531d44de723825aa81398a6415283229725a00fa30713812ab9323faa82fc4"
+    "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
+    "checksum ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "23ac7c30002a5accbf7e8987d0632fa6de155b7c3d39d0067317a391e00a2ef6"
+    "checksum arrayvec 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" = "a1e964f9e24d588183fcb43503abda40d288c8657dfc27311516ce2f05675aef"
+  '')
+
+  (builtins.fromTOML ''
+    a = [[{ b = true }]]
+    c = [ [ { d = true } ] ]
+    e = [[123]]
+  '')
+
+]
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-fromjson-escapes.exp b/tvix/eval/src/tests/nix_tests/eval-okay-fromjson-escapes.exp
new file mode 100644
index 0000000000..add5505a82
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-fromjson-escapes.exp
@@ -0,0 +1 @@
+"quote \" reverse solidus \\ solidus / backspace  formfeed  newline \n carriage return \r horizontal tab \t 1 char unicode encoded backspace  1 char unicode encoded e with accent é 2 char unicode encoded s with caron š 3 char unicode encoded rightwards arrow →"
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-fromjson-escapes.nix b/tvix/eval/src/tests/nix_tests/eval-okay-fromjson-escapes.nix
new file mode 100644
index 0000000000..f007135077
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-fromjson-escapes.nix
@@ -0,0 +1,3 @@
+# This string contains all supported escapes in a JSON string, per json.org
+# \b and \f are not supported by Nix
+builtins.fromJSON ''"quote \" reverse solidus \\ solidus \/ backspace \b formfeed \f newline \n carriage return \r horizontal tab \t 1 char unicode encoded backspace \u0008 1 char unicode encoded e with accent \u00e9 2 char unicode encoded s with caron \u0161 3 char unicode encoded rightwards arrow \u2192"''
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-fromjson.exp b/tvix/eval/src/tests/nix_tests/eval-okay-fromjson.exp
new file mode 100644
index 0000000000..27ba77ddaf
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-fromjson.exp
@@ -0,0 +1 @@
+true
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-fromjson.nix b/tvix/eval/src/tests/nix_tests/eval-okay-fromjson.nix
new file mode 100644
index 0000000000..e1c0f86cc4
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-fromjson.nix
@@ -0,0 +1,35 @@
+builtins.fromJSON
+  ''
+    {
+      "Video": {
+          "Title":  "The Penguin Chronicles",
+          "Width":  1920,
+          "Height": 1080,
+          "EmbeddedData": [3.14159, 23493,null, true  ,false, -10],
+          "Thumb": {
+              "Url":    "http://www.example.com/video/5678931",
+              "Width":  200,
+              "Height": 250
+          },
+          "Subtitle" : false,
+          "Latitude":  46.2051,
+          "Longitude": 6.0723
+        }
+    }
+  ''
+==
+  { Video =
+    { Title = "The Penguin Chronicles";
+      Width = 1920;
+      Height = 1080;
+      EmbeddedData = [ 3.14159 23493 null true false (0-10) ];
+      Thumb =
+        { Url = "http://www.example.com/video/5678931";
+          Width = 200;
+          Height = 250;
+        };
+      Subtitle = false;
+      Latitude = 46.2051;
+      Longitude = 6.0723;
+    };
+  }
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-functionargs.exp b/tvix/eval/src/tests/nix_tests/eval-okay-functionargs.exp
new file mode 100644
index 0000000000..c1c9f8ffaf
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-functionargs.exp
@@ -0,0 +1 @@
+[ "stdenv" "fetchurl" "aterm-stdenv" "aterm-stdenv2" "libX11" "libXv" "mplayer-stdenv2.libXv-libX11" "mplayer-stdenv2.libXv-libX11_2" "nix-stdenv-aterm-stdenv" "nix-stdenv2-aterm2-stdenv2" ]
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-functionargs.exp.xml b/tvix/eval/src/tests/nix_tests/eval-okay-functionargs.exp.xml
new file mode 100644
index 0000000000..651f54c363
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-functionargs.exp.xml
@@ -0,0 +1,15 @@
+<?xml version='1.0' encoding='utf-8'?>
+<expr>
+  <list>
+    <string value="stdenv" />
+    <string value="fetchurl" />
+    <string value="aterm-stdenv" />
+    <string value="aterm-stdenv2" />
+    <string value="libX11" />
+    <string value="libXv" />
+    <string value="mplayer-stdenv2.libXv-libX11" />
+    <string value="mplayer-stdenv2.libXv-libX11_2" />
+    <string value="nix-stdenv-aterm-stdenv" />
+    <string value="nix-stdenv2-aterm2-stdenv2" />
+  </list>
+</expr>
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-functionargs.nix b/tvix/eval/src/tests/nix_tests/eval-okay-functionargs.nix
new file mode 100644
index 0000000000..68dca62ee1
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-functionargs.nix
@@ -0,0 +1,80 @@
+let
+
+  stdenvFun = { }: { name = "stdenv"; };
+  stdenv2Fun = { }: { name = "stdenv2"; };
+  fetchurlFun = { stdenv }: assert stdenv.name == "stdenv"; { name = "fetchurl"; };
+  atermFun = { stdenv, fetchurl }: { name = "aterm-${stdenv.name}"; };
+  aterm2Fun = { stdenv, fetchurl }: { name = "aterm2-${stdenv.name}"; };
+  nixFun = { stdenv, fetchurl, aterm }: { name = "nix-${stdenv.name}-${aterm.name}"; };
+  
+  mplayerFun =
+    { stdenv, fetchurl, enableX11 ? false, xorg ? null, enableFoo ? true, foo ? null  }:
+    assert stdenv.name == "stdenv2";
+    assert enableX11 -> xorg.libXv.name == "libXv";
+    assert enableFoo -> foo != null;
+    { name = "mplayer-${stdenv.name}.${xorg.libXv.name}-${xorg.libX11.name}"; };
+
+  makeOverridable = f: origArgs: f origArgs //
+    { override = newArgs:
+        makeOverridable f (origArgs // (if builtins.isFunction newArgs then newArgs origArgs else newArgs));
+    };
+    
+  callPackage_ = pkgs: f: args:
+    makeOverridable f ((builtins.intersectAttrs (builtins.functionArgs f) pkgs) // args);
+
+  allPackages =
+    { overrides ? (pkgs: pkgsPrev: { }) }:
+    let
+      callPackage = callPackage_ pkgs;
+      pkgs = pkgsStd // (overrides pkgs pkgsStd);
+      pkgsStd = {
+        inherit pkgs;
+        stdenv = callPackage stdenvFun { };
+        stdenv2 = callPackage stdenv2Fun { };
+        fetchurl = callPackage fetchurlFun { };
+        aterm = callPackage atermFun { };
+        xorg = callPackage xorgFun { };
+        mplayer = callPackage mplayerFun { stdenv = pkgs.stdenv2; enableFoo = false; };
+        nix = callPackage nixFun { };
+      };
+    in pkgs;
+
+  libX11Fun = { stdenv, fetchurl }: { name = "libX11"; };
+  libX11_2Fun = { stdenv, fetchurl }: { name = "libX11_2"; };
+  libXvFun = { stdenv, fetchurl, libX11 }: { name = "libXv"; };
+  
+  xorgFun =
+    { pkgs }:
+    let callPackage = callPackage_ (pkgs // pkgs.xorg); in
+    {
+      libX11 = callPackage libX11Fun { };
+      libXv = callPackage libXvFun { };
+    };
+
+in
+
+let
+
+  pkgs = allPackages { };
+  
+  pkgs2 = allPackages {
+    overrides = pkgs: pkgsPrev: {
+      stdenv = pkgs.stdenv2;
+      nix = pkgsPrev.nix.override { aterm = aterm2Fun { inherit (pkgs) stdenv fetchurl; }; };
+      xorg = pkgsPrev.xorg // { libX11 = libX11_2Fun { inherit (pkgs) stdenv fetchurl; }; };
+    };
+  };
+  
+in
+
+  [ pkgs.stdenv.name
+    pkgs.fetchurl.name
+    pkgs.aterm.name
+    pkgs2.aterm.name
+    pkgs.xorg.libX11.name
+    pkgs.xorg.libXv.name
+    pkgs.mplayer.name
+    pkgs2.mplayer.name
+    pkgs.nix.name
+    pkgs2.nix.name
+  ]
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-getenv.exp b/tvix/eval/src/tests/nix_tests/eval-okay-getenv.exp
new file mode 100644
index 0000000000..14e24d4190
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-getenv.exp
@@ -0,0 +1 @@
+"foobar"
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-getenv.nix b/tvix/eval/src/tests/nix_tests/eval-okay-getenv.nix
new file mode 100644
index 0000000000..4cfec5f553
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-getenv.nix
@@ -0,0 +1 @@
+builtins.getEnv "TEST_VAR" + (if builtins.getEnv "NO_SUCH_VAR" == "" then "bar" else "bla")
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-groupBy.exp b/tvix/eval/src/tests/nix_tests/eval-okay-groupBy.exp
new file mode 100644
index 0000000000..bfca5652a5
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-groupBy.exp
@@ -0,0 +1 @@
+{ "1" = [ 9 ]; "2" = [ 8 ]; "3" = [ 13 29 ]; "4" = [ 3 4 10 11 17 18 ]; "5" = [ 0 23 26 28 ]; "6" = [ 1 12 21 27 30 ]; "7" = [ 7 22 ]; "8" = [ 14 ]; "9" = [ 19 ]; b = [ 16 25 ]; c = [ 24 ]; d = [ 2 ]; e = [ 5 6 15 31 ]; f = [ 20 ]; }
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-groupBy.nix b/tvix/eval/src/tests/nix_tests/eval-okay-groupBy.nix
new file mode 100644
index 0000000000..862d89dbd6
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-groupBy.nix
@@ -0,0 +1,5 @@
+with import ./lib.nix;
+
+builtins.groupBy (n:
+  builtins.substring 0 1 (builtins.hashString "sha256" (toString n))
+) (range 0 31)
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-hash.exp b/tvix/eval/src/tests/nix_tests/eval-okay-hash.exp
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-hash.exp
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-hashfile.exp b/tvix/eval/src/tests/nix_tests/eval-okay-hashfile.exp
new file mode 100644
index 0000000000..ff1e8293ef
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-hashfile.exp
@@ -0,0 +1 @@
+[ "d3b07384d113edec49eaa6238ad5ff00" "0f343b0931126a20f133d67c2b018a3b" "f1d2d2f924e986ac86fdf7b36c94bcdf32beec15" "60cacbf3d72e1e7834203da608037b1bf83b40e8" "b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c" "5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef" "0cf9180a764aba863a67b6d72f0918bc131c6772642cb2dce5a34f0a702f9470ddc2bf125c12198b1995c233c34b4afd346c54a2334c350a948a51b6e8b4e6b6" "8efb4f73c5655351c444eb109230c556d39e2c7624e9c11abc9e3fb4b9b9254218cc5085b454a9698d085cfa92198491f07a723be4574adc70617b73eb0b6461" ]
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-hashfile.nix b/tvix/eval/src/tests/nix_tests/eval-okay-hashfile.nix
new file mode 100644
index 0000000000..aff5a18568
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-hashfile.nix
@@ -0,0 +1,4 @@
+let
+  paths = [ ./data ./binary-data ];
+in
+  builtins.concatLists (map (hash: map (builtins.hashFile hash) paths) ["md5" "sha1" "sha256" "sha512"])
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-hashstring.exp b/tvix/eval/src/tests/nix_tests/eval-okay-hashstring.exp
new file mode 100644
index 0000000000..d720a082dd
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-hashstring.exp
@@ -0,0 +1 @@
+[ "d41d8cd98f00b204e9800998ecf8427e" "6c69ee7f211c640419d5366cc076ae46" "bb3438fbabd460ea6dbd27d153e2233b" "da39a3ee5e6b4b0d3255bfef95601890afd80709" "cd54e8568c1b37cf1e5badb0779bcbf382212189" "6d12e10b1d331dad210e47fd25d4f260802b7e77" "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" "900a4469df00ccbfd0c145c6d1e4b7953dd0afafadd7534e3a4019e8d38fc663" "ad0387b3bd8652f730ca46d25f9c170af0fd589f42e7f23f5a9e6412d97d7e56" "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e" "9d0886f8c6b389398a16257bc79780fab9831c7fc11c8ab07fa732cb7b348feade382f92617c9c5305fefba0af02ab5fd39a587d330997ff5bd0db19f7666653" "21644b72aa259e5a588cd3afbafb1d4310f4889680f6c83b9d531596a5a284f34dbebff409d23bcc86aee6bad10c891606f075c6f4755cb536da27db5693f3a7" ]
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-hashstring.nix b/tvix/eval/src/tests/nix_tests/eval-okay-hashstring.nix
new file mode 100644
index 0000000000..b0f62b245c
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-hashstring.nix
@@ -0,0 +1,4 @@
+let
+  strings = [ "" "text 1" "text 2" ];
+in
+  builtins.concatLists (map (hash: map (builtins.hashString hash) strings) ["md5" "sha1" "sha256" "sha512"])
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-if.exp b/tvix/eval/src/tests/nix_tests/eval-okay-if.exp
new file mode 100644
index 0000000000..00750edc07
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-if.exp
@@ -0,0 +1 @@
+3
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-if.nix b/tvix/eval/src/tests/nix_tests/eval-okay-if.nix
new file mode 100644
index 0000000000..23e4c74d50
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-if.nix
@@ -0,0 +1 @@
+if "foo" != "f" + "oo" then 1 else if false then 2 else 3
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-ind-string.exp b/tvix/eval/src/tests/nix_tests/eval-okay-ind-string.exp
new file mode 100644
index 0000000000..7862331fa5
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-ind-string.exp
@@ -0,0 +1 @@
+"This is an indented multi-line string\nliteral.  An amount of whitespace at\nthe start of each line matching the minimum\nindentation of all lines in the string\nliteral together will be removed.  Thus,\nin this case four spaces will be\nstripped from each line, even though\n  THIS LINE is indented six spaces.\n\nAlso, empty lines don't count in the\ndetermination of the indentation level (the\nprevious empty line has indentation 0, but\nit doesn't matter).\nIf the string starts with whitespace\n  followed by a newline, it's stripped, but\n  that's not the case here. Two spaces are\n  stripped because of the \"  \" at the start. \nThis line is indented\na bit further.\nAnti-quotations, like so, are\nalso allowed.\n  The \\ is not special here.\n' can be followed by any character except another ', e.g. 'x'.\nLikewise for $, e.g. $$ or $varName.\nBut ' followed by ' is special, as is $ followed by {.\nIf you want them, use anti-quotations: '', \${.\n   Tabs are not interpreted as whitespace (since we can't guess\n   what tab settings are intended), so don't use them.\n\tThis line starts with a space and a tab, so only one\n   space will be stripped from each line.\nAlso note that if the last line (just before the closing ' ')\nconsists only of whitespace, it's ignored.  But here there is\nsome non-whitespace stuff, so the line isn't removed. \nThis shows a hacky way to preserve an empty line after the start.\nBut there's no reason to do so: you could just repeat the empty\nline.\n  Similarly you can force an indentation level,\n  in this case to 2 spaces.  This works because the anti-quote\n  is significant (not whitespace).\nstart on network-interfaces\n\nstart script\n\n  rm -f /var/run/opengl-driver\n  ln -sf 123 /var/run/opengl-driver\n\n  rm -f /var/log/slim.log\n   \nend script\n\nenv SLIM_CFGFILE=abc\nenv SLIM_THEMESDIR=def\nenv FONTCONFIG_FILE=/etc/fonts/fonts.conf  \t\t\t\t# !!! cleanup\nenv XKB_BINDIR=foo/bin         \t\t\t\t# Needed for the Xkb extension.\nenv LD_LIBRARY_PATH=libX11/lib:libXext/lib:/usr/lib/          # related to xorg-sys-opengl - needed to load libglx for (AI)GLX support (for compiz)\n\nenv XORG_DRI_DRIVER_PATH=nvidiaDrivers/X11R6/lib/modules/drivers/ \n\nexec slim/bin/slim\nEscaping of ' followed by ': ''\nEscaping of $ followed by {: \${\nAnd finally to interpret \\n etc. as in a string: \n, \r, \t.\nfoo\n'bla'\nbar\ncut -d $'\\t' -f 1\nending dollar $$\n"
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-ind-string.nix b/tvix/eval/src/tests/nix_tests/eval-okay-ind-string.nix
new file mode 100644
index 0000000000..95d59b5083
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-ind-string.nix
@@ -0,0 +1,128 @@
+let
+
+  s1 = ''
+    This is an indented multi-line string
+    literal.  An amount of whitespace at
+    the start of each line matching the minimum
+    indentation of all lines in the string
+    literal together will be removed.  Thus,
+    in this case four spaces will be
+    stripped from each line, even though
+      THIS LINE is indented six spaces.
+
+    Also, empty lines don't count in the
+    determination of the indentation level (the
+    previous empty line has indentation 0, but
+    it doesn't matter).
+  '';
+
+  s2 = ''  If the string starts with whitespace
+    followed by a newline, it's stripped, but
+    that's not the case here. Two spaces are
+    stripped because of the "  " at the start. 
+  '';
+
+  s3 = ''
+      This line is indented
+      a bit further.
+        ''; # indentation of last line doesn't count if it's empty
+
+  s4 = ''
+    Anti-quotations, like ${if true then "so" else "not so"}, are
+    also allowed.
+  '';
+
+  s5 = ''
+      The \ is not special here.
+    ' can be followed by any character except another ', e.g. 'x'.
+    Likewise for $, e.g. $$ or $varName.
+    But ' followed by ' is special, as is $ followed by {.
+    If you want them, use anti-quotations: ${"''"}, ${"\${"}.
+  '';
+
+  s6 = ''  
+    Tabs are not interpreted as whitespace (since we can't guess
+    what tab settings are intended), so don't use them.
+ 	This line starts with a space and a tab, so only one
+    space will be stripped from each line.
+  '';
+
+  s7 = ''
+    Also note that if the last line (just before the closing ' ')
+    consists only of whitespace, it's ignored.  But here there is
+    some non-whitespace stuff, so the line isn't removed. '';
+
+  s8 = ''    ${""}
+    This shows a hacky way to preserve an empty line after the start.
+    But there's no reason to do so: you could just repeat the empty
+    line.
+  '';
+
+  s9 = ''
+  ${""}  Similarly you can force an indentation level,
+    in this case to 2 spaces.  This works because the anti-quote
+    is significant (not whitespace).
+  '';
+
+  s10 = ''
+  '';
+
+  s11 = '''';
+
+  s12 = ''   '';
+
+  s13 = ''
+    start on network-interfaces
+
+    start script
+    
+      rm -f /var/run/opengl-driver
+      ${if true
+        then "ln -sf 123 /var/run/opengl-driver"
+        else if true
+        then "ln -sf 456 /var/run/opengl-driver"
+        else ""
+      }
+
+      rm -f /var/log/slim.log
+       
+    end script
+
+    env SLIM_CFGFILE=${"abc"}
+    env SLIM_THEMESDIR=${"def"}
+    env FONTCONFIG_FILE=/etc/fonts/fonts.conf  				# !!! cleanup
+    env XKB_BINDIR=${"foo"}/bin         				# Needed for the Xkb extension.
+    env LD_LIBRARY_PATH=${"libX11"}/lib:${"libXext"}/lib:/usr/lib/          # related to xorg-sys-opengl - needed to load libglx for (AI)GLX support (for compiz)
+
+    ${if true
+      then "env XORG_DRI_DRIVER_PATH=${"nvidiaDrivers"}/X11R6/lib/modules/drivers/"
+    else if true
+      then "env XORG_DRI_DRIVER_PATH=${"mesa"}/lib/modules/dri"
+      else ""
+    } 
+
+    exec ${"slim"}/bin/slim
+  '';
+
+  s14 = ''
+    Escaping of ' followed by ': '''
+    Escaping of $ followed by {: ''${
+    And finally to interpret \n etc. as in a string: ''\n, ''\r, ''\t.
+  '';
+
+  # Regression test: string interpolation in '${x}' should work, but didn't.
+  s15 = let x = "bla"; in ''
+    foo
+    '${x}'
+    bar
+  '';
+
+  # Regression test: accept $'.
+  s16 = ''
+    cut -d $'\t' -f 1
+  '';
+
+  # Accept dollars at end of strings 
+  s17 = ''ending dollar $'' + ''$'' + "\n";
+
+in s1 + s2 + s3 + s4 + s5 + s6 + s7 + s8 + s9 + s10 + s11 + s12 + s13 + s14 + s15 + s16 + s17
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-intersectAttrs.exp b/tvix/eval/src/tests/nix_tests/eval-okay-intersectAttrs.exp
new file mode 100644
index 0000000000..50445bc0ee
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-intersectAttrs.exp
@@ -0,0 +1 @@
+[ { } { a = 1; } { a = 1; } { a = "a"; } { m = 1; } { m = "m"; } { n = 1; } { n = "n"; } { n = 1; p = 2; } { n = "n"; p = "p"; } { n = 1; p = 2; } { n = "n"; p = "p"; } { a = "a"; b = "b"; c = "c"; d = "d"; e = "e"; f = "f"; g = "g"; h = "h"; i = "i"; j = "j"; k = "k"; l = "l"; m = "m"; n = "n"; o = "o"; p = "p"; q = "q"; r = "r"; s = "s"; t = "t"; u = "u"; v = "v"; w = "w"; x = "x"; y = "y"; z = "z"; } true ]
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-intersectAttrs.nix b/tvix/eval/src/tests/nix_tests/eval-okay-intersectAttrs.nix
new file mode 100644
index 0000000000..39d49938cc
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-intersectAttrs.nix
@@ -0,0 +1,50 @@
+let
+  alphabet =
+  { a = "a";
+    b = "b";
+    c = "c";
+    d = "d";
+    e = "e";
+    f = "f";
+    g = "g";
+    h = "h";
+    i = "i";
+    j = "j";
+    k = "k";
+    l = "l";
+    m = "m";
+    n = "n";
+    o = "o";
+    p = "p";
+    q = "q";
+    r = "r";
+    s = "s";
+    t = "t";
+    u = "u";
+    v = "v";
+    w = "w";
+    x = "x";
+    y = "y";
+    z = "z";
+  };
+  foo = {
+    inherit (alphabet) f o b a r z q u x;
+    aa = throw "aa";
+  };
+  alphabetFail = builtins.mapAttrs throw alphabet;
+in
+[ (builtins.intersectAttrs { a = abort "l1"; } { b = abort "r1"; })
+  (builtins.intersectAttrs { a = abort "l2"; } { a = 1; })
+  (builtins.intersectAttrs alphabetFail { a = 1; })
+  (builtins.intersectAttrs  { a = abort "laa"; } alphabet)
+  (builtins.intersectAttrs alphabetFail { m = 1; })
+  (builtins.intersectAttrs  { m = abort "lam"; } alphabet)
+  (builtins.intersectAttrs alphabetFail { n = 1; })
+  (builtins.intersectAttrs  { n = abort "lan"; } alphabet)
+  (builtins.intersectAttrs alphabetFail { n = 1; p = 2; })
+  (builtins.intersectAttrs  { n = abort "lan2"; p = abort "lap"; } alphabet)
+  (builtins.intersectAttrs alphabetFail { n = 1; p = 2; })
+  (builtins.intersectAttrs  { n = abort "lan2"; p = abort "lap"; } alphabet)
+  (builtins.intersectAttrs alphabetFail alphabet)
+  (builtins.intersectAttrs alphabet foo == builtins.intersectAttrs foo alphabet)
+]
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-let.exp b/tvix/eval/src/tests/nix_tests/eval-okay-let.exp
new file mode 100644
index 0000000000..14e24d4190
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-let.exp
@@ -0,0 +1 @@
+"foobar"
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-let.nix b/tvix/eval/src/tests/nix_tests/eval-okay-let.nix
new file mode 100644
index 0000000000..fe118c5282
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-let.nix
@@ -0,0 +1,5 @@
+let {
+  x = "foo";
+  y = "bar";
+  body = x + y;
+}
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-list.exp b/tvix/eval/src/tests/nix_tests/eval-okay-list.exp
new file mode 100644
index 0000000000..f784f26d83
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-list.exp
@@ -0,0 +1 @@
+"foobarblatest"
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-list.nix b/tvix/eval/src/tests/nix_tests/eval-okay-list.nix
new file mode 100644
index 0000000000..d433bcf908
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-list.nix
@@ -0,0 +1,7 @@
+with import ./lib.nix;
+
+let {
+
+  body = concat ["foo" "bar" "bla" "test"];
+    
+}
\ No newline at end of file
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-listtoattrs.exp b/tvix/eval/src/tests/nix_tests/eval-okay-listtoattrs.exp
new file mode 100644
index 0000000000..74abef7bc6
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-listtoattrs.exp
@@ -0,0 +1 @@
+"AAbar"
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-listtoattrs.nix b/tvix/eval/src/tests/nix_tests/eval-okay-listtoattrs.nix
new file mode 100644
index 0000000000..4186e029b5
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-listtoattrs.nix
@@ -0,0 +1,11 @@
+# this test shows how to use listToAttrs and that evaluation is still lazy (throw isn't called)
+with import ./lib.nix;
+
+let 
+  asi = name: value : { inherit name value; };
+  list = [ ( asi "a" "A" ) ( asi "b" "B" ) ];
+  a = builtins.listToAttrs list;
+  b = builtins.listToAttrs ( list ++ list );
+  r = builtins.listToAttrs [ (asi "result" [ a b ]) ( asi "throw" (throw "this should not be thrown")) ];
+  x = builtins.listToAttrs [ (asi "foo" "bar") (asi "foo" "bla") ];
+in concat (map (x: x.a) r.result) + x.foo
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-logic.exp b/tvix/eval/src/tests/nix_tests/eval-okay-logic.exp
new file mode 100644
index 0000000000..d00491fd7e
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-logic.exp
@@ -0,0 +1 @@
+1
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-logic.nix b/tvix/eval/src/tests/nix_tests/eval-okay-logic.nix
new file mode 100644
index 0000000000..fbb1279440
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-logic.nix
@@ -0,0 +1 @@
+assert !false && (true || false) -> true; 1
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-map.exp b/tvix/eval/src/tests/nix_tests/eval-okay-map.exp
new file mode 100644
index 0000000000..dbb64f717b
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-map.exp
@@ -0,0 +1 @@
+"foobarblabarxyzzybar"
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-map.nix b/tvix/eval/src/tests/nix_tests/eval-okay-map.nix
new file mode 100644
index 0000000000..a76c1d8114
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-map.nix
@@ -0,0 +1,3 @@
+with import ./lib.nix;
+
+concat (map (x: x + "bar") [ "foo" "bla" "xyzzy" ])
\ No newline at end of file
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-mapattrs.exp b/tvix/eval/src/tests/nix_tests/eval-okay-mapattrs.exp
new file mode 100644
index 0000000000..3f113f17ba
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-mapattrs.exp
@@ -0,0 +1 @@
+{ x = "x-foo"; y = "y-bar"; }
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-mapattrs.nix b/tvix/eval/src/tests/nix_tests/eval-okay-mapattrs.nix
new file mode 100644
index 0000000000..f075b6275e
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-mapattrs.nix
@@ -0,0 +1,3 @@
+with import ./lib.nix;
+
+builtins.mapAttrs (name: value: name + "-" + value) { x = "foo"; y = "bar"; }
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-nested-with.exp b/tvix/eval/src/tests/nix_tests/eval-okay-nested-with.exp
new file mode 100644
index 0000000000..0cfbf08886
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-nested-with.exp
@@ -0,0 +1 @@
+2
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-nested-with.nix b/tvix/eval/src/tests/nix_tests/eval-okay-nested-with.nix
new file mode 100644
index 0000000000..ba9d79aa79
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-nested-with.nix
@@ -0,0 +1,3 @@
+with { x = 1; };
+with { x = 2; };
+x
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-new-let.exp b/tvix/eval/src/tests/nix_tests/eval-okay-new-let.exp
new file mode 100644
index 0000000000..f98b388071
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-new-let.exp
@@ -0,0 +1 @@
+"xyzzyfoobar"
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-new-let.nix b/tvix/eval/src/tests/nix_tests/eval-okay-new-let.nix
new file mode 100644
index 0000000000..7381231415
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-new-let.nix
@@ -0,0 +1,14 @@
+let
+
+  f = z: 
+
+    let
+      x = "foo";
+      y = "bar";
+      body = 1; # compat test
+    in
+      z + x + y;
+
+  arg = "xyzzy";
+
+in f arg
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-null-dynamic-attrs.exp b/tvix/eval/src/tests/nix_tests/eval-okay-null-dynamic-attrs.exp
new file mode 100644
index 0000000000..27ba77ddaf
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-null-dynamic-attrs.exp
@@ -0,0 +1 @@
+true
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-null-dynamic-attrs.nix b/tvix/eval/src/tests/nix_tests/eval-okay-null-dynamic-attrs.nix
new file mode 100644
index 0000000000..b060c0bc98
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-null-dynamic-attrs.nix
@@ -0,0 +1 @@
+{ ${null} = true; } == {}
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-partition.exp b/tvix/eval/src/tests/nix_tests/eval-okay-partition.exp
new file mode 100644
index 0000000000..cd8b8b020c
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-partition.exp
@@ -0,0 +1 @@
+{ right = [ 0 2 4 6 8 10 100 102 104 106 108 110 ]; wrong = [ 1 3 5 7 9 101 103 105 107 109 ]; }
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-partition.nix b/tvix/eval/src/tests/nix_tests/eval-okay-partition.nix
new file mode 100644
index 0000000000..846d2ce494
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-partition.nix
@@ -0,0 +1,5 @@
+with import ./lib.nix;
+
+builtins.partition
+  (x: x / 2 * 2 == x)
+  (builtins.concatLists [ (range 0 10) (range 100 110) ])
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-path.exp b/tvix/eval/src/tests/nix_tests/eval-okay-path.exp
new file mode 100644
index 0000000000..3ce7f82830
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-path.exp
@@ -0,0 +1 @@
+"/nix/store/ya937r4ydw0l6kayq8jkyqaips9c75jm-output"
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-pathexists.exp b/tvix/eval/src/tests/nix_tests/eval-okay-pathexists.exp
new file mode 100644
index 0000000000..27ba77ddaf
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-pathexists.exp
@@ -0,0 +1 @@
+true
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-pathexists.nix b/tvix/eval/src/tests/nix_tests/eval-okay-pathexists.nix
new file mode 100644
index 0000000000..50c28ee0cd
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-pathexists.nix
@@ -0,0 +1,5 @@
+builtins.pathExists (builtins.toPath ./lib.nix)
+&& builtins.pathExists (builtins.toPath (builtins.toString ./lib.nix))
+&& !builtins.pathExists (builtins.toPath (builtins.toString ./bla.nix))
+&& builtins.pathExists ./lib.nix
+&& !builtins.pathExists ./bla.nix
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-patterns.exp b/tvix/eval/src/tests/nix_tests/eval-okay-patterns.exp
new file mode 100644
index 0000000000..a4304010fe
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-patterns.exp
@@ -0,0 +1 @@
+"abcxyzDDDDEFijk"
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-patterns.nix b/tvix/eval/src/tests/nix_tests/eval-okay-patterns.nix
new file mode 100644
index 0000000000..96fd25a015
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-patterns.nix
@@ -0,0 +1,16 @@
+let
+
+  f = args@{x, y, z}: x + args.y + z;
+
+  g = {x, y, z}@args: f args;
+
+  h = {x ? "d", y ? x, z ? args.x}@args: x + y + z;
+
+  j = {x, y, z, ...}: x + y + z;
+
+in
+  f {x = "a"; y = "b"; z = "c";} +
+  g {x = "x"; y = "y"; z = "z";} +
+  h {x = "D";} +
+  h {x = "D"; y = "E"; z = "F";} +
+  j {x = "i"; y = "j"; z = "k"; bla = "bla"; foo = "bar";}
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-readfile.exp b/tvix/eval/src/tests/nix_tests/eval-okay-readfile.exp
new file mode 100644
index 0000000000..a2c87d0c43
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-readfile.exp
@@ -0,0 +1 @@
+"builtins.readFile ./eval-okay-readfile.nix\n"
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-readfile.nix b/tvix/eval/src/tests/nix_tests/eval-okay-readfile.nix
new file mode 100644
index 0000000000..82f7cb1743
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-readfile.nix
@@ -0,0 +1 @@
+builtins.readFile ./eval-okay-readfile.nix
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-redefine-builtin.exp b/tvix/eval/src/tests/nix_tests/eval-okay-redefine-builtin.exp
new file mode 100644
index 0000000000..c508d5366f
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-redefine-builtin.exp
@@ -0,0 +1 @@
+false
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-redefine-builtin.nix b/tvix/eval/src/tests/nix_tests/eval-okay-redefine-builtin.nix
new file mode 100644
index 0000000000..df9fc3f37d
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-redefine-builtin.nix
@@ -0,0 +1,3 @@
+let
+  throw = abort "Error!";
+in (builtins.tryEval <foobaz>).success
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-regex-match.exp b/tvix/eval/src/tests/nix_tests/eval-okay-regex-match.exp
new file mode 100644
index 0000000000..27ba77ddaf
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-regex-match.exp
@@ -0,0 +1 @@
+true
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-regex-match.nix b/tvix/eval/src/tests/nix_tests/eval-okay-regex-match.nix
new file mode 100644
index 0000000000..273e259071
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-regex-match.nix
@@ -0,0 +1,29 @@
+with builtins;
+
+let
+
+  matches = pat: s: match pat s != null;
+
+  splitFN = match "((.*)/)?([^/]*)\\.(nix|cc)";
+
+in
+
+assert  matches "foobar" "foobar";
+assert  matches "fo*" "f";
+assert !matches "fo+" "f";
+assert  matches "fo*" "fo";
+assert  matches "fo*" "foo";
+assert  matches "fo+" "foo";
+assert  matches "fo{1,2}" "foo";
+assert !matches "fo{1,2}" "fooo";
+assert !matches "fo*" "foobar";
+assert  matches "[[:space:]]+([^[:space:]]+)[[:space:]]+" "  foo   ";
+assert !matches "[[:space:]]+([[:upper:]]+)[[:space:]]+" "  foo   ";
+
+assert match "(.*)\\.nix" "foobar.nix" == [ "foobar" ];
+assert match "[[:space:]]+([[:upper:]]+)[[:space:]]+" "  FOO   " == [ "FOO" ];
+
+assert splitFN "/path/to/foobar.nix" == [ "/path/to/" "/path/to" "foobar" "nix" ];
+assert splitFN "foobar.cc" == [ null null "foobar" "cc" ];
+
+true
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-regex-split.exp b/tvix/eval/src/tests/nix_tests/eval-okay-regex-split.exp
new file mode 100644
index 0000000000..27ba77ddaf
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-regex-split.exp
@@ -0,0 +1 @@
+true
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-regex-split.nix b/tvix/eval/src/tests/nix_tests/eval-okay-regex-split.nix
new file mode 100644
index 0000000000..0073e05778
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-regex-split.nix
@@ -0,0 +1,48 @@
+with builtins;
+
+# Non capturing regex returns empty lists
+assert  split "foobar" "foobar"  == ["" [] ""];
+assert  split "fo*" "f"          == ["" [] ""];
+assert  split "fo+" "f"          == ["f"];
+assert  split "fo*" "fo"         == ["" [] ""];
+assert  split "fo*" "foo"        == ["" [] ""];
+assert  split "fo+" "foo"        == ["" [] ""];
+assert  split "fo{1,2}" "foo"    == ["" [] ""];
+assert  split "fo{1,2}" "fooo"   == ["" [] "o"];
+assert  split "fo*" "foobar"     == ["" [] "bar"];
+
+# Capturing regex returns a list of sub-matches
+assert  split "(fo*)" "f"        == ["" ["f"] ""];
+assert  split "(fo+)" "f"        == ["f"];
+assert  split "(fo*)" "fo"       == ["" ["fo"] ""];
+assert  split "(f)(o*)" "f"      == ["" ["f" ""] ""];
+assert  split "(f)(o*)" "foo"    == ["" ["f" "oo"] ""];
+assert  split "(fo+)" "foo"      == ["" ["foo"] ""];
+assert  split "(fo{1,2})" "foo"  == ["" ["foo"] ""];
+assert  split "(fo{1,2})" "fooo" == ["" ["foo"] "o"];
+assert  split "(fo*)" "foobar"   == ["" ["foo"] "bar"];
+
+# Matches are greedy.
+assert  split "(o+)" "oooofoooo" == ["" ["oooo"] "f" ["oooo"] ""];
+
+# Matches multiple times.
+assert  split "(b)" "foobarbaz"  == ["foo" ["b"] "ar" ["b"] "az"];
+
+# Split large strings containing newlines. null are inserted when a
+# pattern within the current did not match anything.
+assert  split "[[:space:]]+|([',.!?])" ''
+  Nix Rocks!
+  That's why I use it.
+''  == [
+  "Nix" [ null ] "Rocks" ["!"] "" [ null ]
+  "That" ["'"] "s" [ null ] "why" [ null ] "I" [ null ] "use" [ null ] "it" ["."] "" [ null ]
+  ""
+];
+
+# Documentation examples
+assert  split  "(a)b" "abc"      == [ "" [ "a" ] "c" ];
+assert  split  "([ac])" "abc"    == [ "" [ "a" ] "b" [ "c" ] "" ];
+assert  split  "(a)|(c)" "abc"   == [ "" [ "a" null ] "b" [ null "c" ] "" ];
+assert  split  "([[:upper:]]+)" "  FOO   " == [ "  " [ "FOO" ] "   " ];
+
+true
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-regression-20220122.exp b/tvix/eval/src/tests/nix_tests/eval-okay-regression-20220122.exp
new file mode 100644
index 0000000000..00750edc07
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-regression-20220122.exp
@@ -0,0 +1 @@
+3
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-regression-20220122.nix b/tvix/eval/src/tests/nix_tests/eval-okay-regression-20220122.nix
new file mode 100644
index 0000000000..694e9a13b7
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-regression-20220122.nix
@@ -0,0 +1 @@
+((_: _) 1) + ((__: __) 2)
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-regression-20220125.exp b/tvix/eval/src/tests/nix_tests/eval-okay-regression-20220125.exp
new file mode 100644
index 0000000000..00750edc07
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-regression-20220125.exp
@@ -0,0 +1 @@
+3
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-regression-20220125.nix b/tvix/eval/src/tests/nix_tests/eval-okay-regression-20220125.nix
new file mode 100644
index 0000000000..4855023739
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-regression-20220125.nix
@@ -0,0 +1,2 @@
+((__curPosFoo: __curPosFoo) 1) + ((__curPosBar: __curPosBar) 2)
+
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-remove.exp b/tvix/eval/src/tests/nix_tests/eval-okay-remove.exp
new file mode 100644
index 0000000000..8d38505c16
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-remove.exp
@@ -0,0 +1 @@
+456
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-remove.nix b/tvix/eval/src/tests/nix_tests/eval-okay-remove.nix
new file mode 100644
index 0000000000..4ad5ba897f
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-remove.nix
@@ -0,0 +1,5 @@
+let {
+  attrs = {x = 123; y = 456;};
+
+  body = (removeAttrs attrs ["x"]).y;
+}
\ No newline at end of file
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-scope-1.exp b/tvix/eval/src/tests/nix_tests/eval-okay-scope-1.exp
new file mode 100644
index 0000000000..00750edc07
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-scope-1.exp
@@ -0,0 +1 @@
+3
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-scope-1.nix b/tvix/eval/src/tests/nix_tests/eval-okay-scope-1.nix
new file mode 100644
index 0000000000..fa38a7174e
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-scope-1.nix
@@ -0,0 +1,6 @@
+(({x}: x:
+
+  { x = 1;
+    y = x;
+  }
+) {x = 2;} 3).y
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-scope-2.exp b/tvix/eval/src/tests/nix_tests/eval-okay-scope-2.exp
new file mode 100644
index 0000000000..d00491fd7e
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-scope-2.exp
@@ -0,0 +1 @@
+1
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-scope-2.nix b/tvix/eval/src/tests/nix_tests/eval-okay-scope-2.nix
new file mode 100644
index 0000000000..eb8b02bc49
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-scope-2.nix
@@ -0,0 +1,6 @@
+((x: {x}:
+  rec {
+    x = 1;
+    y = x;
+  }
+) 2 {x = 3;}).y
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-scope-3.exp b/tvix/eval/src/tests/nix_tests/eval-okay-scope-3.exp
new file mode 100644
index 0000000000..b8626c4cff
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-scope-3.exp
@@ -0,0 +1 @@
+4
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-scope-3.nix b/tvix/eval/src/tests/nix_tests/eval-okay-scope-3.nix
new file mode 100644
index 0000000000..10d6bc04d8
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-scope-3.nix
@@ -0,0 +1,6 @@
+((x: as: {x}:
+  rec {
+    inherit (as) x;
+    y = x;
+  }
+) 2 {x = 4;} {x = 3;}).y
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-scope-4.exp b/tvix/eval/src/tests/nix_tests/eval-okay-scope-4.exp
new file mode 100644
index 0000000000..00ff03a46c
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-scope-4.exp
@@ -0,0 +1 @@
+"ccdd"
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-scope-4.nix b/tvix/eval/src/tests/nix_tests/eval-okay-scope-4.nix
new file mode 100644
index 0000000000..dc8243bc85
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-scope-4.nix
@@ -0,0 +1,10 @@
+let {
+
+  x = "a";
+  y = "b";
+
+  f = {x ? y, y ? x}: x + y;
+
+  body = f {x = "c";} + f {y = "d";};
+
+}
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-scope-6.exp b/tvix/eval/src/tests/nix_tests/eval-okay-scope-6.exp
new file mode 100644
index 0000000000..00ff03a46c
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-scope-6.exp
@@ -0,0 +1 @@
+"ccdd"
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-scope-6.nix b/tvix/eval/src/tests/nix_tests/eval-okay-scope-6.nix
new file mode 100644
index 0000000000..0995d4e7e7
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-scope-6.nix
@@ -0,0 +1,7 @@
+let {
+
+  f = {x ? y, y ? x}: x + y;
+
+  body = f {x = "c";} + f {y = "d";};
+
+}
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-scope-7.exp b/tvix/eval/src/tests/nix_tests/eval-okay-scope-7.exp
new file mode 100644
index 0000000000..d00491fd7e
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-scope-7.exp
@@ -0,0 +1 @@
+1
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-scope-7.nix b/tvix/eval/src/tests/nix_tests/eval-okay-scope-7.nix
new file mode 100644
index 0000000000..4da02968f6
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-scope-7.nix
@@ -0,0 +1,6 @@
+rec {
+  inherit (x) y;
+  x = {
+    y = 1;
+  };
+}.y
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-seq.exp b/tvix/eval/src/tests/nix_tests/eval-okay-seq.exp
new file mode 100644
index 0000000000..0cfbf08886
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-seq.exp
@@ -0,0 +1 @@
+2
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-seq.nix b/tvix/eval/src/tests/nix_tests/eval-okay-seq.nix
new file mode 100644
index 0000000000..0a9a21c03b
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-seq.nix
@@ -0,0 +1 @@
+builtins.seq 1 2
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-sort.exp b/tvix/eval/src/tests/nix_tests/eval-okay-sort.exp
new file mode 100644
index 0000000000..899119e20e
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-sort.exp
@@ -0,0 +1 @@
+[ [ 42 77 147 249 483 526 ] [ 526 483 249 147 77 42 ] [ "bar" "fnord" "foo" "xyzzy" ] [ { key = 1; value = "foo"; } { key = 1; value = "fnord"; } { key = 2; value = "bar"; } ] [ [ ] [ ] [ 1 ] [ 1 4 ] [ 1 5 ] [ 1 6 ] [ 2 ] [ 2 3 ] [ 3 ] [ 3 ] ] ]
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-sort.nix b/tvix/eval/src/tests/nix_tests/eval-okay-sort.nix
new file mode 100644
index 0000000000..50aa78e403
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-sort.nix
@@ -0,0 +1,20 @@
+with builtins;
+
+[ (sort lessThan [ 483 249 526 147 42 77 ])
+  (sort (x: y: y < x) [ 483 249 526 147 42 77 ])
+  (sort lessThan [ "foo" "bar" "xyzzy" "fnord" ])
+  (sort (x: y: x.key < y.key)
+    [ { key = 1; value = "foo"; } { key = 2; value = "bar"; } { key = 1; value = "fnord"; } ])
+  (sort lessThan [
+    [ 1 6 ]
+    [ ]
+    [ 2 3 ]
+    [ 3 ]
+    [ 1 5 ]
+    [ 2 ]
+    [ 1 ]
+    [ ]
+    [ 1 4 ]
+    [ 3 ]
+  ])
+]
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-splitversion.exp b/tvix/eval/src/tests/nix_tests/eval-okay-splitversion.exp
new file mode 100644
index 0000000000..153ceb8186
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-splitversion.exp
@@ -0,0 +1 @@
+[ "1" "2" "3" ]
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-splitversion.nix b/tvix/eval/src/tests/nix_tests/eval-okay-splitversion.nix
new file mode 100644
index 0000000000..9e5c99d2e7
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-splitversion.nix
@@ -0,0 +1 @@
+builtins.splitVersion "1.2.3"
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-string.exp b/tvix/eval/src/tests/nix_tests/eval-okay-string.exp
new file mode 100644
index 0000000000..63f650f73a
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-string.exp
@@ -0,0 +1 @@
+"foobar/a/b/c/d/foo/xyzzy/foo.txt/../foo/x/yescape: \"quote\" \n \\end\nof\nlinefoobarblaatfoo$bar$\"$\"$"
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-string.nix b/tvix/eval/src/tests/nix_tests/eval-okay-string.nix
new file mode 100644
index 0000000000..47cc989ad4
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-string.nix
@@ -0,0 +1,12 @@
+"foo" + "bar"
+  + toString (/a/b + /c/d)
+  + toString (/foo/bar + "/../xyzzy/." + "/foo.txt")
+  + ("/../foo" + toString /x/y)
+  + "escape: \"quote\" \n \\"
+  + "end
+of
+line"
+  + "foo${if true then "b${"a" + "r"}" else "xyzzy"}blaat"
+  + "foo$bar"
+  + "$\"$\""
+  + "$"
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-strings-as-attrs-names.exp b/tvix/eval/src/tests/nix_tests/eval-okay-strings-as-attrs-names.exp
new file mode 100644
index 0000000000..27ba77ddaf
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-strings-as-attrs-names.exp
@@ -0,0 +1 @@
+true
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-strings-as-attrs-names.nix b/tvix/eval/src/tests/nix_tests/eval-okay-strings-as-attrs-names.nix
new file mode 100644
index 0000000000..5e40928dbe
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-strings-as-attrs-names.nix
@@ -0,0 +1,20 @@
+let
+
+  attr = {
+    "key 1" = "test";
+    "key 2" = "caseok";
+  };
+
+  t1 = builtins.getAttr "key 1" attr;
+  t2 = attr."key 2";
+  t3 = attr ? "key 1";
+  t4 = builtins.attrNames { inherit (attr) "key 1"; };
+
+  # This is permitted, but there is currently no way to reference this
+  # variable.
+  "foo bar" = 1;
+
+in t1 == "test"
+   && t2 == "caseok"
+   && t3 == true
+   && t4 == ["key 1"]
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-substring.exp b/tvix/eval/src/tests/nix_tests/eval-okay-substring.exp
new file mode 100644
index 0000000000..6aace04b0f
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-substring.exp
@@ -0,0 +1 @@
+"ooxfoobarybarzobaabbc"
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-substring.nix b/tvix/eval/src/tests/nix_tests/eval-okay-substring.nix
new file mode 100644
index 0000000000..424af00d9b
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-substring.nix
@@ -0,0 +1,21 @@
+with builtins;
+
+let
+
+  s = "foobar";
+
+in
+
+substring 1 2 s
++ "x"
++ substring 0 (stringLength s) s
++ "y"
++ substring 3 100 s
++ "z"
++ substring 2 (sub (stringLength s) 3) s
++ "a"
++ substring 3 0 s
++ "b"
++ substring 3 1 s
++ "c"
++ substring 5 10 "perl"
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-tail-call-1.exp b/tvix/eval/src/tests/nix_tests/eval-okay-tail-call-1.exp
new file mode 100644
index 0000000000..f7393e847d
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-tail-call-1.exp
@@ -0,0 +1 @@
+100000
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-tail-call-1.nix b/tvix/eval/src/tests/nix_tests/eval-okay-tail-call-1.nix
new file mode 100644
index 0000000000..a3962ce3fd
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-tail-call-1.nix
@@ -0,0 +1,3 @@
+let
+  f = n: if n == 100000 then n else f (n + 1);
+in f 0
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-tojson.exp b/tvix/eval/src/tests/nix_tests/eval-okay-tojson.exp
new file mode 100644
index 0000000000..e92aae3235
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-tojson.exp
@@ -0,0 +1 @@
+"{\"a\":123,\"b\":-456,\"c\":\"foo\",\"d\":\"foo\\n\\\"bar\\\"\",\"e\":true,\"f\":false,\"g\":[1,2,3],\"h\":[\"a\",[\"b\",{\"foo\\nbar\":{}}]],\"i\":3,\"j\":1.44,\"k\":\"foo\"}"
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-tojson.nix b/tvix/eval/src/tests/nix_tests/eval-okay-tojson.nix
new file mode 100644
index 0000000000..ce67943bea
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-tojson.nix
@@ -0,0 +1,13 @@
+builtins.toJSON
+  { a = 123;
+    b = -456;
+    c = "foo";
+    d = "foo\n\"bar\"";
+    e = true;
+    f = false;
+    g = [ 1 2 3 ];
+    h = [ "a" [ "b" { "foo\nbar" = {}; } ] ];
+    i = 1 + 2;
+    j = 1.44;
+    k = { __toString = self: self.a; a = "foo"; };
+  }
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-toxml.exp b/tvix/eval/src/tests/nix_tests/eval-okay-toxml.exp
new file mode 100644
index 0000000000..828220890e
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-toxml.exp
@@ -0,0 +1 @@
+"<?xml version='1.0' encoding='utf-8'?>\n<expr>\n  <attrs>\n    <attr name=\"a\">\n      <string value=\"s\" />\n    </attr>\n  </attrs>\n</expr>\n"
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-toxml.nix b/tvix/eval/src/tests/nix_tests/eval-okay-toxml.nix
new file mode 100644
index 0000000000..068c97a6c1
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-toxml.nix
@@ -0,0 +1,3 @@
+# Make sure the expected XML output is produced; in particular, make sure it
+# doesn't contain source location information.
+builtins.toXML { a = "s"; }
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-toxml2.exp b/tvix/eval/src/tests/nix_tests/eval-okay-toxml2.exp
new file mode 100644
index 0000000000..634a841eb1
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-toxml2.exp
@@ -0,0 +1 @@
+"<?xml version='1.0' encoding='utf-8'?>\n<expr>\n  <list>\n    <string value=\"ab\" />\n    <int value=\"10\" />\n    <attrs>\n      <attr name=\"x\">\n        <string value=\"x\" />\n      </attr>\n      <attr name=\"y\">\n        <string value=\"x\" />\n      </attr>\n    </attrs>\n  </list>\n</expr>\n"
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-toxml2.nix b/tvix/eval/src/tests/nix_tests/eval-okay-toxml2.nix
new file mode 100644
index 0000000000..ff1791b30e
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-toxml2.nix
@@ -0,0 +1 @@
+builtins.toXML [("a" + "b") 10 (rec {x = "x"; y = x;})]
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-tryeval.exp b/tvix/eval/src/tests/nix_tests/eval-okay-tryeval.exp
new file mode 100644
index 0000000000..2b2e6fa711
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-tryeval.exp
@@ -0,0 +1 @@
+{ x = { success = true; value = "x"; }; y = { success = false; value = false; }; z = { success = false; value = false; }; }
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-tryeval.nix b/tvix/eval/src/tests/nix_tests/eval-okay-tryeval.nix
new file mode 100644
index 0000000000..629bc440a8
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-tryeval.nix
@@ -0,0 +1,5 @@
+{
+  x = builtins.tryEval "x";
+  y = builtins.tryEval (assert false; "y");
+  z = builtins.tryEval (throw "bla");
+}
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-types.exp b/tvix/eval/src/tests/nix_tests/eval-okay-types.exp
new file mode 100644
index 0000000000..92a1532993
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-types.exp
@@ -0,0 +1 @@
+[ true false true false true false true false true true true true true true true true true true true false true true true false "int" "bool" "string" "null" "set" "list" "lambda" "lambda" "lambda" "lambda" ]
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-types.nix b/tvix/eval/src/tests/nix_tests/eval-okay-types.nix
new file mode 100644
index 0000000000..9b58be5d1d
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-types.nix
@@ -0,0 +1,37 @@
+with builtins;
+
+[ (isNull null)
+  (isNull (x: x))
+  (isFunction (x: x))
+  (isFunction "fnord")
+  (isString ("foo" + "bar"))
+  (isString [ "x" ])
+  (isInt (1 + 2))
+  (isInt { x = 123; })
+  (isInt (1 / 2))
+  (isInt (1 + 1))
+  (isInt (1 / 2))
+  (isInt (1 * 2))
+  (isInt (1 - 2))
+  (isFloat (1.2))
+  (isFloat (1 + 1.0))
+  (isFloat (1 / 2.0))
+  (isFloat (1 * 2.0))
+  (isFloat (1 - 2.0))
+  (isBool (true && false))
+  (isBool null)
+  (isPath /nix/store)
+  (isPath ./.)
+  (isAttrs { x = 123; })
+  (isAttrs null)
+  (typeOf (3 * 4))
+  (typeOf true)
+  (typeOf "xyzzy")
+  (typeOf null)
+  (typeOf { x = 456; })
+  (typeOf [ 1 2 3 ])
+  (typeOf (x: x))
+  (typeOf ((x: y: x) 1))
+  (typeOf map)
+  (typeOf (map (x: x)))
+]
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-versions.exp b/tvix/eval/src/tests/nix_tests/eval-okay-versions.exp
new file mode 100644
index 0000000000..27ba77ddaf
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-versions.exp
@@ -0,0 +1 @@
+true
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-versions.nix b/tvix/eval/src/tests/nix_tests/eval-okay-versions.nix
new file mode 100644
index 0000000000..e9111f5f43
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-versions.nix
@@ -0,0 +1,43 @@
+let
+
+  name1 = "hello-1.0.2";
+  name2 = "hello";
+  name3 = "915resolution-0.5.2";
+  name4 = "xf86-video-i810-1.7.4";
+  name5 = "name-that-ends-with-dash--1.0";
+
+  eq = 0;
+  lt = builtins.sub 0 1;
+  gt = 1;
+
+  versionTest = v1: v2: expected:
+    let d1 = builtins.compareVersions v1 v2;
+        d2 = builtins.compareVersions v2 v1;
+    in d1 == builtins.sub 0 d2 && d1 == expected;
+
+  tests = [
+    ((builtins.parseDrvName name1).name == "hello")
+    ((builtins.parseDrvName name1).version == "1.0.2")
+    ((builtins.parseDrvName name2).name == "hello")
+    ((builtins.parseDrvName name2).version == "")
+    ((builtins.parseDrvName name3).name == "915resolution")
+    ((builtins.parseDrvName name3).version == "0.5.2")
+    ((builtins.parseDrvName name4).name == "xf86-video-i810")
+    ((builtins.parseDrvName name4).version == "1.7.4")
+    ((builtins.parseDrvName name5).name == "name-that-ends-with-dash")
+    ((builtins.parseDrvName name5).version == "-1.0")
+    (versionTest "1.0" "2.3" lt)
+    (versionTest "2.1" "2.3" lt)
+    (versionTest "2.3" "2.3" eq)
+    (versionTest "2.5" "2.3" gt)
+    (versionTest "3.1" "2.3" gt)
+    (versionTest "2.3.1" "2.3" gt)
+    (versionTest "2.3.1" "2.3a" gt)
+    (versionTest "2.3pre1" "2.3" lt)
+    (versionTest "2.3pre3" "2.3pre12" lt)
+    (versionTest "2.3a" "2.3c" lt)
+    (versionTest "2.3pre1" "2.3c" lt)
+    (versionTest "2.3pre1" "2.3q" lt)
+  ];
+
+in (import ./lib.nix).and tests
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-with.exp b/tvix/eval/src/tests/nix_tests/eval-okay-with.exp
new file mode 100644
index 0000000000..378c8dc804
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-with.exp
@@ -0,0 +1 @@
+"xyzzybarxyzzybar"
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-with.nix b/tvix/eval/src/tests/nix_tests/eval-okay-with.nix
new file mode 100644
index 0000000000..033e8d3aba
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-with.nix
@@ -0,0 +1,19 @@
+let {
+
+  a = "xyzzy";
+
+  as = {
+    a = "foo";
+    b = "bar";
+  };
+
+  bs = {
+    a = "bar";
+  };
+
+  x = with as; a + b;
+
+  y = with as; with bs; a + b;
+
+  body = x + y;
+}
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-xml.exp.xml b/tvix/eval/src/tests/nix_tests/eval-okay-xml.exp.xml
new file mode 100644
index 0000000000..20099326cc
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-xml.exp.xml
@@ -0,0 +1,52 @@
+<?xml version='1.0' encoding='utf-8'?>
+<expr>
+  <attrs>
+    <attr name="a">
+      <string value="foo" />
+    </attr>
+    <attr name="at">
+      <function>
+        <attrspat name="args">
+          <attr name="x" />
+          <attr name="y" />
+          <attr name="z" />
+        </attrspat>
+      </function>
+    </attr>
+    <attr name="b">
+      <string value="bar" />
+    </attr>
+    <attr name="c">
+      <string value="foobar" />
+    </attr>
+    <attr name="ellipsis">
+      <function>
+        <attrspat ellipsis="1">
+          <attr name="x" />
+          <attr name="y" />
+          <attr name="z" />
+        </attrspat>
+      </function>
+    </attr>
+    <attr name="f">
+      <function>
+        <attrspat>
+          <attr name="x" />
+          <attr name="y" />
+          <attr name="z" />
+        </attrspat>
+      </function>
+    </attr>
+    <attr name="id">
+      <function>
+        <varpat name="x" />
+      </function>
+    </attr>
+    <attr name="x">
+      <int value="123" />
+    </attr>
+    <attr name="y">
+      <float value="567.89" />
+    </attr>
+  </attrs>
+</expr>
diff --git a/tvix/eval/src/tests/nix_tests/eval-okay-xml.nix b/tvix/eval/src/tests/nix_tests/eval-okay-xml.nix
new file mode 100644
index 0000000000..9ee9f8a0b4
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/eval-okay-xml.nix
@@ -0,0 +1,21 @@
+rec {
+
+  x = 123;
+
+  y = 567.890;
+
+  a = "foo";
+
+  b = "bar";
+
+  c = "foo" + "bar";
+
+  f = {z, x, y}: if y then x else z;
+
+  id = x: x;
+
+  at = args@{x, y, z}: x;
+
+  ellipsis = {x, y, z, ...}: x;
+
+}
diff --git a/tvix/eval/src/tests/nix_tests/imported.nix b/tvix/eval/src/tests/nix_tests/imported.nix
new file mode 100644
index 0000000000..fb39ee4efa
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/imported.nix
@@ -0,0 +1,3 @@
+# The function ‘range’ comes from lib.nix and was added to the lexical
+# scope by scopedImport.
+range 1 5 ++ import ./imported2.nix
diff --git a/tvix/eval/src/tests/nix_tests/imported2.nix b/tvix/eval/src/tests/nix_tests/imported2.nix
new file mode 100644
index 0000000000..6d0a2992b7
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/imported2.nix
@@ -0,0 +1 @@
+range 6 10
diff --git a/tvix/eval/src/tests/nix_tests/lib.nix b/tvix/eval/src/tests/nix_tests/lib.nix
new file mode 100644
index 0000000000..028a538314
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/lib.nix
@@ -0,0 +1,61 @@
+with builtins;
+
+rec {
+
+  fold = op: nul: list:
+    if list == []
+    then nul
+    else op (head list) (fold op nul (tail list));
+
+  concat =
+    fold (x: y: x + y) "";
+
+  and = fold (x: y: x && y) true;
+
+  flatten = x:
+    if isList x
+    then fold (x: y: (flatten x) ++ y) [] x
+    else [x];
+
+  sum = foldl' (x: y: add x y) 0;
+
+  hasSuffix = ext: fileName:
+    let lenFileName = stringLength fileName;
+        lenExt = stringLength ext;
+    in !(lessThan lenFileName lenExt) &&
+       substring (sub lenFileName lenExt) lenFileName fileName == ext;
+
+  # Split a list at the given position.
+  splitAt = pos: list:
+    if pos == 0 then {first = []; second = list;} else
+    if list == [] then {first = []; second = [];} else
+    let res = splitAt (sub pos 1) (tail list);
+    in {first = [(head list)] ++ res.first; second = res.second;};
+
+  # Stable merge sort.
+  sortBy = comp: list:
+    if lessThan 1 (length list)
+    then
+      let
+        split = splitAt (div (length list) 2) list;
+        first = sortBy comp split.first;
+        second = sortBy comp split.second;
+      in mergeLists comp first second
+    else list;
+
+  mergeLists = comp: list1: list2:
+    if list1 == [] then list2 else
+    if list2 == [] then list1 else
+    if comp (head list2) (head list1) then [(head list2)] ++ mergeLists comp list1 (tail list2) else
+    [(head list1)] ++ mergeLists comp (tail list1) list2;
+
+  id = x: x;
+
+  const = x: y: x;
+
+  range = first: last:
+    if first > last
+      then []
+      else genList (n: first + n) (last - first + 1);
+
+}
diff --git a/tvix/eval/src/tests/nix_tests/notyetpassing/eval-fail-bad-antiquote-2.nix b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-fail-bad-antiquote-2.nix
new file mode 100644
index 0000000000..3745235ce9
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-fail-bad-antiquote-2.nix
@@ -0,0 +1 @@
+"${./fnord}"
diff --git a/tvix/eval/src/tests/nix_tests/notyetpassing/eval-fail-fromTOML-timestamps.nix b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-fail-fromTOML-timestamps.nix
new file mode 100644
index 0000000000..74cff9470a
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-fail-fromTOML-timestamps.nix
@@ -0,0 +1,130 @@
+builtins.fromTOML ''
+  key = "value"
+  bare_key = "value"
+  bare-key = "value"
+  1234 = "value"
+
+  "127.0.0.1" = "value"
+  "character encoding" = "value"
+  "ʎǝʞ" = "value"
+  'key2' = "value"
+  'quoted "value"' = "value"
+
+  name = "Orange"
+
+  physical.color = "orange"
+  physical.shape = "round"
+  site."google.com" = true
+
+  # This is legal according to the spec, but cpptoml doesn't handle it.
+  #a.b.c = 1
+  #a.d = 2
+
+  str = "I'm a string. \"You can quote me\". Name\tJos\u00E9\nLocation\tSF."
+
+  int1 = +99
+  int2 = 42
+  int3 = 0
+  int4 = -17
+  int5 = 1_000
+  int6 = 5_349_221
+  int7 = 1_2_3_4_5
+
+  hex1 = 0xDEADBEEF
+  hex2 = 0xdeadbeef
+  hex3 = 0xdead_beef
+
+  oct1 = 0o01234567
+  oct2 = 0o755
+
+  bin1 = 0b11010110
+
+  flt1 = +1.0
+  flt2 = 3.1415
+  flt3 = -0.01
+  flt4 = 5e+22
+  flt5 = 1e6
+  flt6 = -2E-2
+  flt7 = 6.626e-34
+  flt8 = 9_224_617.445_991_228_313
+
+  bool1 = true
+  bool2 = false
+
+  odt1 = 1979-05-27T07:32:00Z
+  odt2 = 1979-05-27T00:32:00-07:00
+  odt3 = 1979-05-27T00:32:00.999999-07:00
+  odt4 = 1979-05-27 07:32:00Z
+  ldt1 = 1979-05-27T07:32:00
+  ldt2 = 1979-05-27T00:32:00.999999
+  ld1 = 1979-05-27
+  lt1 = 07:32:00
+  lt2 = 00:32:00.999999
+
+  arr1 = [ 1, 2, 3 ]
+  arr2 = [ "red", "yellow", "green" ]
+  arr3 = [ [ 1, 2 ], [3, 4, 5] ]
+  arr4 = [ "all", 'strings', """are the same""", ''''type'''']
+  arr5 = [ [ 1, 2 ], ["a", "b", "c"] ]
+
+  arr7 = [
+    1, 2, 3
+  ]
+
+  arr8 = [
+    1,
+    2, # this is ok
+  ]
+
+  [table-1]
+  key1 = "some string"
+  key2 = 123
+
+
+  [table-2]
+  key1 = "another string"
+  key2 = 456
+
+  [dog."tater.man"]
+  type.name = "pug"
+
+  [a.b.c]
+  [ d.e.f ]
+  [ g .  h  . i ]
+  [ j . "ʞ" . 'l' ]
+  [x.y.z.w]
+
+  name = { first = "Tom", last = "Preston-Werner" }
+  point = { x = 1, y = 2 }
+  animal = { type.name = "pug" }
+
+  [[products]]
+  name = "Hammer"
+  sku = 738594937
+
+  [[products]]
+
+  [[products]]
+  name = "Nail"
+  sku = 284758393
+  color = "gray"
+
+  [[fruit]]
+    name = "apple"
+
+    [fruit.physical]
+      color = "red"
+      shape = "round"
+
+    [[fruit.variety]]
+      name = "red delicious"
+
+    [[fruit.variety]]
+      name = "granny smith"
+
+  [[fruit]]
+    name = "banana"
+
+    [[fruit.variety]]
+      name = "plantain"
+''
diff --git a/tvix/eval/src/tests/nix_tests/notyetpassing/eval-fail-nonexist-path.nix b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-fail-nonexist-path.nix
new file mode 100644
index 0000000000..f2f08107b5
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-fail-nonexist-path.nix
@@ -0,0 +1,4 @@
+# This must fail to evaluate, since ./fnord doesn't exist.  If it did
+# exist, it would produce "/nix/store/<hash>-fnord/xyzzy" (with an
+# appropriate context).
+"${./fnord}/xyzzy"
diff --git a/tvix/eval/src/tests/nix_tests/notyetpassing/eval-fail-scope-5.nix b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-fail-scope-5.nix
new file mode 100644
index 0000000000..f89a65a99b
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-fail-scope-5.nix
@@ -0,0 +1,10 @@
+let {
+
+  x = "a";
+  y = "b";
+
+  f = {x ? y, y ? x}: x + y;
+
+  body = f {};
+
+}
diff --git a/tvix/eval/src/tests/nix_tests/notyetpassing/eval-fail-undeclared-arg.nix b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-fail-undeclared-arg.nix
new file mode 100644
index 0000000000..cafdf16362
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-fail-undeclared-arg.nix
@@ -0,0 +1 @@
+({x, z}: x + z) {x = "foo"; y = "bla"; z = "bar";}
diff --git a/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-attrs6.exp b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-attrs6.exp
new file mode 100644
index 0000000000..b46938032e
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-attrs6.exp
@@ -0,0 +1 @@
+{ __overrides = { bar = "qux"; }; bar = "qux"; foo = "bar"; }
diff --git a/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-attrs6.nix b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-attrs6.nix
new file mode 100644
index 0000000000..2e5c85483b
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-attrs6.nix
@@ -0,0 +1,4 @@
+rec {
+  "${"foo"}" = "bar";
+   __overrides = { bar = "qux"; };
+}
diff --git a/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-autoargs.exp b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-autoargs.exp
new file mode 100644
index 0000000000..7a8391786a
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-autoargs.exp
@@ -0,0 +1 @@
+"xyzzy!xyzzy!foobar"
diff --git a/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-autoargs.flags b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-autoargs.flags
new file mode 100644
index 0000000000..217c7a5ae2
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-autoargs.flags
@@ -0,0 +1 @@
+--arg lib import(nix_tests/lib.nix) --argstr xyzzy xyzzy! -A result
diff --git a/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-autoargs.nix b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-autoargs.nix
new file mode 100644
index 0000000000..815f51b1d6
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-autoargs.nix
@@ -0,0 +1,15 @@
+let
+
+  foobar = "foobar";
+
+in
+
+{ xyzzy2 ? xyzzy # mutually recursive args
+, xyzzy ? "blaat" # will be overridden by --argstr
+, fb ? foobar
+, lib # will be set by --arg
+}:
+
+{
+  result = lib.concat [xyzzy xyzzy2 fb];
+}
diff --git a/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-context-introspection.exp b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-context-introspection.exp
new file mode 100644
index 0000000000..03b400cc88
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-context-introspection.exp
@@ -0,0 +1 @@
+[ true true true true true true ]
diff --git a/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-context-introspection.nix b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-context-introspection.nix
new file mode 100644
index 0000000000..50a78d946e
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-context-introspection.nix
@@ -0,0 +1,41 @@
+let
+  drv = derivation {
+    name = "fail";
+    builder = "/bin/false";
+    system = "x86_64-linux";
+    outputs = [ "out" "foo" ];
+  };
+
+  path = "${./eval-okay-context-introspection.nix}";
+
+  desired-context = {
+    "${builtins.unsafeDiscardStringContext path}" = {
+      path = true;
+    };
+    "${builtins.unsafeDiscardStringContext drv.drvPath}" = {
+      outputs = [ "foo" "out" ];
+      allOutputs = true;
+    };
+  };
+
+  combo-path = "${path}${drv.outPath}${drv.foo.outPath}${drv.drvPath}";
+  legit-context = builtins.getContext combo-path;
+
+  reconstructed-path = builtins.appendContext
+    (builtins.unsafeDiscardStringContext combo-path)
+    desired-context;
+
+  # Eta rule for strings with context.
+  etaRule = str:
+    str == builtins.appendContext
+      (builtins.unsafeDiscardStringContext str)
+      (builtins.getContext str);
+
+in [
+  (legit-context == desired-context)
+  (reconstructed-path == combo-path)
+  (etaRule "foo")
+  (etaRule drv.drvPath)
+  (etaRule drv.foo.outPath)
+  (etaRule (builtins.unsafeDiscardOutputDependency drv.drvPath))
+]
diff --git a/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-context.exp b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-context.exp
new file mode 100644
index 0000000000..2f535bdbc4
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-context.exp
@@ -0,0 +1 @@
+"foo eval-okay-context.nix bar"
diff --git a/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-context.nix b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-context.nix
new file mode 100644
index 0000000000..7b9531cfe9
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-context.nix
@@ -0,0 +1,6 @@
+let s = "foo ${builtins.substring 33 100 (baseNameOf "${./eval-okay-context.nix}")} bar";
+in
+  if s != "foo eval-okay-context.nix bar"
+  then abort "context not discarded"
+  else builtins.unsafeDiscardStringContext s
+
diff --git a/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-curpos.exp b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-curpos.exp
new file mode 100644
index 0000000000..65fd65b4d0
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-curpos.exp
@@ -0,0 +1 @@
+[ 3 7 4 9 ]
diff --git a/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-curpos.nix b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-curpos.nix
new file mode 100644
index 0000000000..b79553df0b
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-curpos.nix
@@ -0,0 +1,5 @@
+# Bla
+let
+  x = __curPos;
+    y = __curPos;
+in [ x.line x.column y.line y.column ]
diff --git a/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-delayed-with.exp b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-delayed-with.exp
new file mode 100644
index 0000000000..8e7c61ab8e
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-delayed-with.exp
@@ -0,0 +1 @@
+"b-overridden b-overridden a"
diff --git a/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-delayed-with.nix b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-delayed-with.nix
new file mode 100644
index 0000000000..3fb023e1cd
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-delayed-with.nix
@@ -0,0 +1,29 @@
+let
+
+  pkgs_ = with pkgs; {
+    a = derivation {
+      name = "a";
+      system = builtins.currentSystem;
+      builder = "/bin/sh";
+      args = [ "-c" "touch $out" ];
+      inherit b;
+    };
+
+    b = derivation {
+      name = "b";
+      system = builtins.currentSystem;
+      builder = "/bin/sh";
+      args = [ "-c" "touch $out" ];
+      inherit a;
+    };
+
+    c = b;
+  };
+
+  packageOverrides = pkgs: with pkgs; {
+    b = derivation (b.drvAttrs // { name = "${b.name}-overridden"; });
+  };
+
+  pkgs = pkgs_ // (packageOverrides pkgs_);
+
+in "${pkgs.a.b.name} ${pkgs.c.name} ${pkgs.b.a.name}"
diff --git a/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-eq-derivations.exp b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-eq-derivations.exp
new file mode 100644
index 0000000000..ec04aab6ae
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-eq-derivations.exp
@@ -0,0 +1 @@
+[ true true true false ]
diff --git a/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-eq-derivations.nix b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-eq-derivations.nix
new file mode 100644
index 0000000000..d526cb4a21
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-eq-derivations.nix
@@ -0,0 +1,10 @@
+let
+
+  drvA1 = derivation { name = "a"; builder = "/foo"; system = "i686-linux"; };
+  drvA2 = derivation { name = "a"; builder = "/foo"; system = "i686-linux"; };
+  drvA3 = derivation { name = "a"; builder = "/foo"; system = "i686-linux"; } // { dummy = 1; };
+  
+  drvC1 = derivation { name = "c"; builder = "/foo"; system = "i686-linux"; };
+  drvC2 = derivation { name = "c"; builder = "/bar"; system = "i686-linux"; };
+
+in [ (drvA1 == drvA1) (drvA1 == drvA2) (drvA1 == drvA3) (drvC1 == drvC2) ]
diff --git a/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-floor-ceil.exp b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-floor-ceil.exp
new file mode 100644
index 0000000000..81f80420b9
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-floor-ceil.exp
@@ -0,0 +1 @@
+"23;24;23;23"
diff --git a/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-floor-ceil.nix b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-floor-ceil.nix
new file mode 100644
index 0000000000..d76a0d86ea
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-floor-ceil.nix
@@ -0,0 +1,9 @@
+with import ./lib.nix;
+
+let
+  n1 = builtins.floor 23.5;
+  n2 = builtins.ceil 23.5;
+  n3 = builtins.floor 23;
+  n4 = builtins.ceil 23;
+in
+  builtins.concatStringsSep ";" (map toString [ n1 n2 n3 n4 ])
diff --git a/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-fromTOML-timestamps.exp b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-fromTOML-timestamps.exp
new file mode 100644
index 0000000000..08b3c69a6c
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-fromTOML-timestamps.exp
@@ -0,0 +1 @@
+{ "1234" = "value"; "127.0.0.1" = "value"; a = { b = { c = { }; }; }; arr1 = [ 1 2 3 ]; arr2 = [ "red" "yellow" "green" ]; arr3 = [ [ 1 2 ] [ 3 4 5 ] ]; arr4 = [ "all" "strings" "are the same" "type" ]; arr5 = [ [ 1 2 ] [ "a" "b" "c" ] ]; arr7 = [ 1 2 3 ]; arr8 = [ 1 2 ]; bare-key = "value"; bare_key = "value"; bin1 = 214; bool1 = true; bool2 = false; "character encoding" = "value"; d = { e = { f = { }; }; }; dog = { "tater.man" = { type = { name = "pug"; }; }; }; flt1 = 1; flt2 = 3.1415; flt3 = -0.01; flt4 = 5e+22; flt5 = 1e+06; flt6 = -0.02; flt7 = 6.626e-34; flt8 = 9.22462e+06; fruit = [ { name = "apple"; physical = { color = "red"; shape = "round"; }; variety = [ { name = "red delicious"; } { name = "granny smith"; } ]; } { name = "banana"; variety = [ { name = "plantain"; } ]; } ]; g = { h = { i = { }; }; }; hex1 = 3735928559; hex2 = 3735928559; hex3 = 3735928559; int1 = 99; int2 = 42; int3 = 0; int4 = -17; int5 = 1000; int6 = 5349221; int7 = 12345; j = { "ʞ" = { l = { }; }; }; key = "value"; key2 = "value"; ld1 = { _type = "timestamp"; value = "1979-05-27"; }; ldt1 = { _type = "timestamp"; value = "1979-05-27T07:32:00"; }; ldt2 = { _type = "timestamp"; value = "1979-05-27T00:32:00.999999"; }; lt1 = { _type = "timestamp"; value = "07:32:00"; }; lt2 = { _type = "timestamp"; value = "00:32:00.999999"; }; name = "Orange"; oct1 = 342391; oct2 = 493; odt1 = { _type = "timestamp"; value = "1979-05-27T07:32:00Z"; }; odt2 = { _type = "timestamp"; value = "1979-05-27T00:32:00-07:00"; }; odt3 = { _type = "timestamp"; value = "1979-05-27T00:32:00.999999-07:00"; }; odt4 = { _type = "timestamp"; value = "1979-05-27T07:32:00Z"; }; physical = { color = "orange"; shape = "round"; }; products = [ { name = "Hammer"; sku = 738594937; } { } { color = "gray"; name = "Nail"; sku = 284758393; } ]; "quoted \"value\"" = "value"; site = { "google.com" = true; }; str = "I'm a string. \"You can quote me\". Name\tJosé\nLocation\tSF."; table-1 = { key1 = "some string"; key2 = 123; }; table-2 = { key1 = "another string"; key2 = 456; }; x = { y = { z = { w = { animal = { type = { name = "pug"; }; }; name = { first = "Tom"; last = "Preston-Werner"; }; point = { x = 1; y = 2; }; }; }; }; }; "ʎǝʞ" = "value"; }
diff --git a/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-fromTOML-timestamps.flags b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-fromTOML-timestamps.flags
new file mode 100644
index 0000000000..9ed39dc6ba
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-fromTOML-timestamps.flags
@@ -0,0 +1 @@
+--extra-experimental-features parse-toml-timestamps
diff --git a/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-fromTOML-timestamps.nix b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-fromTOML-timestamps.nix
new file mode 100644
index 0000000000..74cff9470a
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-fromTOML-timestamps.nix
@@ -0,0 +1,130 @@
+builtins.fromTOML ''
+  key = "value"
+  bare_key = "value"
+  bare-key = "value"
+  1234 = "value"
+
+  "127.0.0.1" = "value"
+  "character encoding" = "value"
+  "ʎǝʞ" = "value"
+  'key2' = "value"
+  'quoted "value"' = "value"
+
+  name = "Orange"
+
+  physical.color = "orange"
+  physical.shape = "round"
+  site."google.com" = true
+
+  # This is legal according to the spec, but cpptoml doesn't handle it.
+  #a.b.c = 1
+  #a.d = 2
+
+  str = "I'm a string. \"You can quote me\". Name\tJos\u00E9\nLocation\tSF."
+
+  int1 = +99
+  int2 = 42
+  int3 = 0
+  int4 = -17
+  int5 = 1_000
+  int6 = 5_349_221
+  int7 = 1_2_3_4_5
+
+  hex1 = 0xDEADBEEF
+  hex2 = 0xdeadbeef
+  hex3 = 0xdead_beef
+
+  oct1 = 0o01234567
+  oct2 = 0o755
+
+  bin1 = 0b11010110
+
+  flt1 = +1.0
+  flt2 = 3.1415
+  flt3 = -0.01
+  flt4 = 5e+22
+  flt5 = 1e6
+  flt6 = -2E-2
+  flt7 = 6.626e-34
+  flt8 = 9_224_617.445_991_228_313
+
+  bool1 = true
+  bool2 = false
+
+  odt1 = 1979-05-27T07:32:00Z
+  odt2 = 1979-05-27T00:32:00-07:00
+  odt3 = 1979-05-27T00:32:00.999999-07:00
+  odt4 = 1979-05-27 07:32:00Z
+  ldt1 = 1979-05-27T07:32:00
+  ldt2 = 1979-05-27T00:32:00.999999
+  ld1 = 1979-05-27
+  lt1 = 07:32:00
+  lt2 = 00:32:00.999999
+
+  arr1 = [ 1, 2, 3 ]
+  arr2 = [ "red", "yellow", "green" ]
+  arr3 = [ [ 1, 2 ], [3, 4, 5] ]
+  arr4 = [ "all", 'strings', """are the same""", ''''type'''']
+  arr5 = [ [ 1, 2 ], ["a", "b", "c"] ]
+
+  arr7 = [
+    1, 2, 3
+  ]
+
+  arr8 = [
+    1,
+    2, # this is ok
+  ]
+
+  [table-1]
+  key1 = "some string"
+  key2 = 123
+
+
+  [table-2]
+  key1 = "another string"
+  key2 = 456
+
+  [dog."tater.man"]
+  type.name = "pug"
+
+  [a.b.c]
+  [ d.e.f ]
+  [ g .  h  . i ]
+  [ j . "ʞ" . 'l' ]
+  [x.y.z.w]
+
+  name = { first = "Tom", last = "Preston-Werner" }
+  point = { x = 1, y = 2 }
+  animal = { type.name = "pug" }
+
+  [[products]]
+  name = "Hammer"
+  sku = 738594937
+
+  [[products]]
+
+  [[products]]
+  name = "Nail"
+  sku = 284758393
+  color = "gray"
+
+  [[fruit]]
+    name = "apple"
+
+    [fruit.physical]
+      color = "red"
+      shape = "round"
+
+    [[fruit.variety]]
+      name = "red delicious"
+
+    [[fruit.variety]]
+      name = "granny smith"
+
+  [[fruit]]
+    name = "banana"
+
+    [[fruit.variety]]
+      name = "plantain"
+''
diff --git a/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-getattrpos-functionargs.exp b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-getattrpos-functionargs.exp
new file mode 100644
index 0000000000..7f9ac40e81
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-getattrpos-functionargs.exp
@@ -0,0 +1 @@
+{ column = 11; file = "eval-okay-getattrpos-functionargs.nix"; line = 2; }
diff --git a/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-getattrpos-functionargs.nix b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-getattrpos-functionargs.nix
new file mode 100644
index 0000000000..11d6bb0e3a
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-getattrpos-functionargs.nix
@@ -0,0 +1,4 @@
+let
+  fun = { foo }: {};
+  pos = builtins.unsafeGetAttrPos "foo" (builtins.functionArgs fun);
+in { inherit (pos) column line; file = baseNameOf pos.file; }
diff --git a/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-getattrpos-undefined.exp b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-getattrpos-undefined.exp
new file mode 100644
index 0000000000..19765bd501
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-getattrpos-undefined.exp
@@ -0,0 +1 @@
+null
diff --git a/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-getattrpos-undefined.nix b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-getattrpos-undefined.nix
new file mode 100644
index 0000000000..14dd38f773
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-getattrpos-undefined.nix
@@ -0,0 +1 @@
+builtins.unsafeGetAttrPos "abort" builtins
diff --git a/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-getattrpos.exp b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-getattrpos.exp
new file mode 100644
index 0000000000..469249bbc6
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-getattrpos.exp
@@ -0,0 +1 @@
+{ column = 5; file = "eval-okay-getattrpos.nix"; line = 3; }
diff --git a/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-getattrpos.nix b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-getattrpos.nix
new file mode 100644
index 0000000000..ca6b079615
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-getattrpos.nix
@@ -0,0 +1,6 @@
+let
+  as = {
+    foo = "bar";
+  };
+  pos = builtins.unsafeGetAttrPos "foo" as;
+in { inherit (pos) column line; file = baseNameOf pos.file; }
diff --git a/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-import.exp b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-import.exp
new file mode 100644
index 0000000000..c508125b55
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-import.exp
@@ -0,0 +1 @@
+[ 1 2 3 4 5 6 7 8 9 10 ]
diff --git a/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-import.nix b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-import.nix
new file mode 100644
index 0000000000..76213a9541
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-import.nix
@@ -0,0 +1,11 @@
+let
+
+  overrides = {
+    import = fn: scopedImport overrides fn;
+
+    scopedImport = attrs: fn: scopedImport (overrides // attrs) fn;
+
+    builtins = builtins // overrides;
+  } // import ./../lib.nix;
+
+in scopedImport overrides ./../imported.nix
diff --git a/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-overrides.exp b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-overrides.exp
new file mode 100644
index 0000000000..0cfbf08886
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-overrides.exp
@@ -0,0 +1 @@
+2
diff --git a/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-overrides.nix b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-overrides.nix
new file mode 100644
index 0000000000..358742b36e
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-overrides.nix
@@ -0,0 +1,9 @@
+let
+
+  overrides = { a = 2; };
+
+in (rec {
+  __overrides = overrides;
+  x = a;
+  a = 1;
+}).x
diff --git a/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-path-antiquotation.exp b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-path-antiquotation.exp
new file mode 100644
index 0000000000..5b8ea02438
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-path-antiquotation.exp
@@ -0,0 +1 @@
+{ absolute = /foo; expr = /pwd/lang/foo/bar; home = /fake-home/foo; notfirst = /pwd/lang/bar/foo; simple = /pwd/lang/foo; slashes = /foo/bar; surrounded = /pwd/lang/a-foo-b; }
diff --git a/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-path-antiquotation.nix b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-path-antiquotation.nix
new file mode 100644
index 0000000000..497d7c1c75
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-path-antiquotation.nix
@@ -0,0 +1,12 @@
+let
+  foo = "foo";
+in
+{
+  simple = ./${foo};
+  surrounded = ./a-${foo}-b;
+  absolute = /${foo};
+  expr = ./${foo + "/bar"};
+  home = ~/${foo};
+  notfirst = ./bar/${foo};
+  slashes = /${foo}/${"bar"};
+}
diff --git a/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-path.nix b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-path.nix
new file mode 100644
index 0000000000..e67168cf3e
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-path.nix
@@ -0,0 +1,7 @@
+builtins.path
+  { path = ./.;
+    filter = path: _: baseNameOf path == "data";
+    recursive = true;
+    sha256 = "1yhm3gwvg5a41yylymgblsclk95fs6jy72w0wv925mmidlhcq4sw";
+    name = "output";
+  }
diff --git a/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-readDir.exp b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-readDir.exp
new file mode 100644
index 0000000000..6413f6d4f9
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-readDir.exp
@@ -0,0 +1 @@
+{ bar = "regular"; foo = "directory"; ldir = "symlink"; linked = "symlink"; }
diff --git a/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-readDir.nix.disabled b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-readDir.nix.disabled
new file mode 100644
index 0000000000..a7ec9292aa
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-readDir.nix.disabled
@@ -0,0 +1 @@
+builtins.readDir ./readDir
diff --git a/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-readFileType.exp b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-readFileType.exp
new file mode 100644
index 0000000000..6413f6d4f9
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-readFileType.exp
@@ -0,0 +1 @@
+{ bar = "regular"; foo = "directory"; ldir = "symlink"; linked = "symlink"; }
diff --git a/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-readFileType.nix b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-readFileType.nix
new file mode 100644
index 0000000000..174fb6c3a0
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-readFileType.nix
@@ -0,0 +1,6 @@
+{
+  bar    = builtins.readFileType ./readDir/bar;
+  foo    = builtins.readFileType ./readDir/foo;
+  linked = builtins.readFileType ./readDir/linked;
+  ldir   = builtins.readFileType ./readDir/ldir;
+}
diff --git a/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-replacestrings.exp b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-replacestrings.exp
new file mode 100644
index 0000000000..eac67c5fed
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-replacestrings.exp
@@ -0,0 +1 @@
+[ "faabar" "fbar" "fubar" "faboor" "fubar" "XaXbXcX" "X" "a_b" "fubar" ]
diff --git a/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-replacestrings.nix b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-replacestrings.nix
new file mode 100644
index 0000000000..a803e65199
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-replacestrings.nix
@@ -0,0 +1,12 @@
+with builtins;
+
+[ (replaceStrings ["o"] ["a"] "foobar")
+  (replaceStrings ["o"] [""] "foobar")
+  (replaceStrings ["oo"] ["u"] "foobar")
+  (replaceStrings ["oo" "a"] ["a" "oo"] "foobar")
+  (replaceStrings ["oo" "oo"] ["u" "i"] "foobar")
+  (replaceStrings [""] ["X"] "abc")
+  (replaceStrings [""] ["X"] "")
+  (replaceStrings ["-"] ["_"] "a-b")
+  (replaceStrings ["oo" "XX"] ["u" (throw "unreachable")] "foobar")
+]
diff --git a/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-search-path.exp b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-search-path.exp
new file mode 100644
index 0000000000..4519bc406d
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-search-path.exp
@@ -0,0 +1 @@
+"abccX"
diff --git a/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-search-path.flags b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-search-path.flags
new file mode 100644
index 0000000000..a28e682100
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-search-path.flags
@@ -0,0 +1 @@
+-I lang/dir1 -I lang/dir2 -I dir5=lang/dir3
\ No newline at end of file
diff --git a/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-search-path.nix b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-search-path.nix
new file mode 100644
index 0000000000..6fe33decc0
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-search-path.nix
@@ -0,0 +1,10 @@
+with import ./lib.nix;
+with builtins;
+
+assert isFunction (import <nix/fetchurl.nix>);
+
+assert length __nixPath == 5;
+assert length (filter (x: baseNameOf x.path == "dir4") __nixPath) == 1;
+
+import <a.nix> + import <b.nix> + import <c.nix> + import <dir5/c.nix>
+  + (let __nixPath = [ { path = ./dir2; } { path = ./dir1; } ]; in import <a.nix>)
diff --git a/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-zipAttrsWith.exp b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-zipAttrsWith.exp
new file mode 100644
index 0000000000..9c0b15d22b
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-zipAttrsWith.exp
@@ -0,0 +1 @@
+{ "0" = { n = "0"; v = [ 5 23 29 ]; }; "1" = { n = "1"; v = [ 7 30 ]; }; "2" = { n = "2"; v = [ 18 ]; }; "4" = { n = "4"; v = [ 10 ]; }; "5" = { n = "5"; v = [ 15 25 26 31 ]; }; "6" = { n = "6"; v = [ 3 14 ]; }; "7" = { n = "7"; v = [ 12 ]; }; "8" = { n = "8"; v = [ 2 6 8 9 ]; }; "9" = { n = "9"; v = [ 0 16 ]; }; a = { n = "a"; v = [ 17 21 22 27 ]; }; c = { n = "c"; v = [ 11 24 ]; }; d = { n = "d"; v = [ 4 13 28 ]; }; e = { n = "e"; v = [ 20 ]; }; f = { n = "f"; v = [ 1 19 ]; }; }
diff --git a/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-zipAttrsWith.nix b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-zipAttrsWith.nix
new file mode 100644
index 0000000000..e5d4cdccb7
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/notyetpassing/eval-okay-zipAttrsWith.nix
@@ -0,0 +1,9 @@
+with import ./../lib.nix;
+
+let
+  str = builtins.hashString "sha256" "test";
+in
+builtins.zipAttrsWith
+  (n: v: { inherit n v; })
+  (map (n: { ${builtins.substring n 1 str} = n; })
+    (range 0 31))
diff --git a/tvix/eval/src/tests/nix_tests/notyetpassing/readDir/bar b/tvix/eval/src/tests/nix_tests/notyetpassing/readDir/bar
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/notyetpassing/readDir/bar
diff --git a/tvix/eval/src/tests/nix_tests/notyetpassing/readDir/foo/git-hates-directories b/tvix/eval/src/tests/nix_tests/notyetpassing/readDir/foo/git-hates-directories
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/notyetpassing/readDir/foo/git-hates-directories
diff --git a/tvix/eval/src/tests/nix_tests/notyetpassing/readDir/ldir b/tvix/eval/src/tests/nix_tests/notyetpassing/readDir/ldir
new file mode 120000
index 0000000000..1910281566
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/notyetpassing/readDir/ldir
@@ -0,0 +1 @@
+foo
\ No newline at end of file
diff --git a/tvix/eval/src/tests/nix_tests/notyetpassing/readDir/linked b/tvix/eval/src/tests/nix_tests/notyetpassing/readDir/linked
new file mode 120000
index 0000000000..c503f86a0c
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/notyetpassing/readDir/linked
@@ -0,0 +1 @@
+foo/git-hates-directories
\ No newline at end of file
diff --git a/tvix/eval/src/tests/nix_tests/parse-fail-dup-attrs-1.nix b/tvix/eval/src/tests/nix_tests/parse-fail-dup-attrs-1.nix
new file mode 100644
index 0000000000..2c02317d2a
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/parse-fail-dup-attrs-1.nix
@@ -0,0 +1,4 @@
+{ x = 123;
+  y = 456;
+  x = 789;
+}
diff --git a/tvix/eval/src/tests/nix_tests/parse-fail-dup-attrs-2.nix b/tvix/eval/src/tests/nix_tests/parse-fail-dup-attrs-2.nix
new file mode 100644
index 0000000000..864d9865e0
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/parse-fail-dup-attrs-2.nix
@@ -0,0 +1,13 @@
+let {
+
+  as = {
+    x = 123;
+    y = 456;
+  };
+
+  bs = {
+    x = 789;
+    inherit (as) x;
+  };
+  
+}
diff --git a/tvix/eval/src/tests/nix_tests/parse-fail-dup-attrs-3.nix b/tvix/eval/src/tests/nix_tests/parse-fail-dup-attrs-3.nix
new file mode 100644
index 0000000000..114d19779f
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/parse-fail-dup-attrs-3.nix
@@ -0,0 +1,13 @@
+let {
+
+  as = {
+    x = 123;
+    y = 456;
+  };
+
+  bs = rec {
+    x = 789;
+    inherit (as) x;
+  };
+  
+}
diff --git a/tvix/eval/src/tests/nix_tests/parse-fail-dup-attrs-4.nix b/tvix/eval/src/tests/nix_tests/parse-fail-dup-attrs-4.nix
new file mode 100644
index 0000000000..77417432b3
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/parse-fail-dup-attrs-4.nix
@@ -0,0 +1,4 @@
+{
+  services.ssh.port = 22;
+  services.ssh.port = 23;
+}
diff --git a/tvix/eval/src/tests/nix_tests/parse-fail-dup-attrs-7.nix b/tvix/eval/src/tests/nix_tests/parse-fail-dup-attrs-7.nix
new file mode 100644
index 0000000000..bbc3eb08c0
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/parse-fail-dup-attrs-7.nix
@@ -0,0 +1,9 @@
+rec {
+
+  x = 1;
+
+  as = {
+    inherit x;
+    inherit x;
+  };
+}
\ No newline at end of file
diff --git a/tvix/eval/src/tests/nix_tests/parse-fail-dup-formals.nix b/tvix/eval/src/tests/nix_tests/parse-fail-dup-formals.nix
new file mode 100644
index 0000000000..a0edd91a96
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/parse-fail-dup-formals.nix
@@ -0,0 +1 @@
+{x, y, x}: x
\ No newline at end of file
diff --git a/tvix/eval/src/tests/nix_tests/parse-fail-eof-in-string.nix b/tvix/eval/src/tests/nix_tests/parse-fail-eof-in-string.nix
new file mode 100644
index 0000000000..19775d2ec8
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/parse-fail-eof-in-string.nix
@@ -0,0 +1,3 @@
+# https://github.com/NixOS/nix/issues/6562
+# Note that this file must not end with a newline.
+a 1"$
\ No newline at end of file
diff --git a/tvix/eval/src/tests/nix_tests/parse-fail-mixed-nested-attrs1.nix b/tvix/eval/src/tests/nix_tests/parse-fail-mixed-nested-attrs1.nix
new file mode 100644
index 0000000000..11e40e66fd
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/parse-fail-mixed-nested-attrs1.nix
@@ -0,0 +1,4 @@
+{ 
+  x.z = 3; 
+  x = { y = 3; z = 3; }; 
+}
diff --git a/tvix/eval/src/tests/nix_tests/parse-fail-mixed-nested-attrs2.nix b/tvix/eval/src/tests/nix_tests/parse-fail-mixed-nested-attrs2.nix
new file mode 100644
index 0000000000..17da82e5f0
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/parse-fail-mixed-nested-attrs2.nix
@@ -0,0 +1,4 @@
+{ 
+  x.y.y = 3; 
+  x = { y.y= 3; z = 3; }; 
+}
diff --git a/tvix/eval/src/tests/nix_tests/parse-fail-patterns-1.nix b/tvix/eval/src/tests/nix_tests/parse-fail-patterns-1.nix
new file mode 100644
index 0000000000..7b40616417
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/parse-fail-patterns-1.nix
@@ -0,0 +1 @@
+args@{args, x, y, z}: x
diff --git a/tvix/eval/src/tests/nix_tests/parse-fail-regression-20060610.nix b/tvix/eval/src/tests/nix_tests/parse-fail-regression-20060610.nix
new file mode 100644
index 0000000000..b1934f7e1e
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/parse-fail-regression-20060610.nix
@@ -0,0 +1,11 @@
+let {
+  x =
+    {gcc}:
+    {
+      inherit gcc;
+    };
+
+  body = ({
+    inherit gcc;
+  }).gcc;
+}
diff --git a/tvix/eval/src/tests/nix_tests/parse-fail-uft8.nix b/tvix/eval/src/tests/nix_tests/parse-fail-uft8.nix
new file mode 100644
index 0000000000..34948d48ae
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/parse-fail-uft8.nix
@@ -0,0 +1 @@
+123 é 4
diff --git a/tvix/eval/src/tests/nix_tests/parse-fail-undef-var-2.nix b/tvix/eval/src/tests/nix_tests/parse-fail-undef-var-2.nix
new file mode 100644
index 0000000000..c10a52b1ea
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/parse-fail-undef-var-2.nix
@@ -0,0 +1,7 @@
+let {
+
+  f = {x, y : ["baz" "bar" z "bat"]}: x + y;
+
+  body = f {x = "foo"; y = "bar";};
+
+}
diff --git a/tvix/eval/src/tests/nix_tests/parse-fail-undef-var.nix b/tvix/eval/src/tests/nix_tests/parse-fail-undef-var.nix
new file mode 100644
index 0000000000..7b63008110
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/parse-fail-undef-var.nix
@@ -0,0 +1 @@
+x: y
diff --git a/tvix/eval/src/tests/nix_tests/parse-okay-1.nix b/tvix/eval/src/tests/nix_tests/parse-okay-1.nix
new file mode 100644
index 0000000000..23a58ed109
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/parse-okay-1.nix
@@ -0,0 +1 @@
+{x, y, z}: x + y + z
diff --git a/tvix/eval/src/tests/nix_tests/parse-okay-crlf.nix b/tvix/eval/src/tests/nix_tests/parse-okay-crlf.nix
new file mode 100644
index 0000000000..21518d4c6d
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/parse-okay-crlf.nix
@@ -0,0 +1,17 @@
+rec {
+
+  /* Dit is

+  een test. */
+
+  x = 
+  # Dit is een test.
y;
+  
+  y = 123;

+
+  # CR or CR/LF (but not explicit \r's) in strings should be
+  # translated to LF.
+  foo = "multi
line

+  string
+  test\r";
+
+  z = 456;
}
diff --git a/tvix/eval/src/tests/nix_tests/parse-okay-dup-attrs-5.nix b/tvix/eval/src/tests/nix_tests/parse-okay-dup-attrs-5.nix
new file mode 100644
index 0000000000..f4b9efd0c5
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/parse-okay-dup-attrs-5.nix
@@ -0,0 +1,4 @@
+{
+  services.ssh = { enable = true; };
+  services.ssh.port = 23;
+}
diff --git a/tvix/eval/src/tests/nix_tests/parse-okay-dup-attrs-6.nix b/tvix/eval/src/tests/nix_tests/parse-okay-dup-attrs-6.nix
new file mode 100644
index 0000000000..ae6d7a7693
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/parse-okay-dup-attrs-6.nix
@@ -0,0 +1,4 @@
+{
+  services.ssh.port = 23;
+  services.ssh = { enable = true; };
+}
diff --git a/tvix/eval/src/tests/nix_tests/parse-okay-mixed-nested-attrs-1.nix b/tvix/eval/src/tests/nix_tests/parse-okay-mixed-nested-attrs-1.nix
new file mode 100644
index 0000000000..fd1001c8ca
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/parse-okay-mixed-nested-attrs-1.nix
@@ -0,0 +1,4 @@
+{ 
+  x = { y = 3; z = 3; }; 
+  x.q = 3; 
+}
diff --git a/tvix/eval/src/tests/nix_tests/parse-okay-mixed-nested-attrs-2.nix b/tvix/eval/src/tests/nix_tests/parse-okay-mixed-nested-attrs-2.nix
new file mode 100644
index 0000000000..ad066b6803
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/parse-okay-mixed-nested-attrs-2.nix
@@ -0,0 +1,4 @@
+{ 
+  x.q = 3; 
+  x = { y = 3; z = 3; }; 
+}
diff --git a/tvix/eval/src/tests/nix_tests/parse-okay-mixed-nested-attrs-3.nix b/tvix/eval/src/tests/nix_tests/parse-okay-mixed-nested-attrs-3.nix
new file mode 100644
index 0000000000..45a33e4803
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/parse-okay-mixed-nested-attrs-3.nix
@@ -0,0 +1,7 @@
+{
+    services.ssh.enable = true;
+    services.ssh = { port = 123; };
+    services = {
+        httpd.enable = true;
+    };
+}
diff --git a/tvix/eval/src/tests/nix_tests/parse-okay-regression-20041027.nix b/tvix/eval/src/tests/nix_tests/parse-okay-regression-20041027.nix
new file mode 100644
index 0000000000..ae2e256eea
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/parse-okay-regression-20041027.nix
@@ -0,0 +1,11 @@
+{stdenv, fetchurl /* pkgconfig, libX11 */ }:
+
+stdenv.mkDerivation {
+  name = "libXi-6.0.1";
+  src = fetchurl {
+    url = http://freedesktop.org/~xlibs/release/libXi-6.0.1.tar.bz2;
+    md5 = "7e935a42428d63a387b3c048be0f2756";
+  };
+/*  buildInputs = [pkgconfig];
+  propagatedBuildInputs = [libX11]; */
+}
diff --git a/tvix/eval/src/tests/nix_tests/parse-okay-regression-751.nix b/tvix/eval/src/tests/nix_tests/parse-okay-regression-751.nix
new file mode 100644
index 0000000000..05c78b3016
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/parse-okay-regression-751.nix
@@ -0,0 +1,2 @@
+let const = a: "const"; in
+''${ const { x = "q"; }}''
diff --git a/tvix/eval/src/tests/nix_tests/parse-okay-subversion.nix b/tvix/eval/src/tests/nix_tests/parse-okay-subversion.nix
new file mode 100644
index 0000000000..356272815d
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/parse-okay-subversion.nix
@@ -0,0 +1,43 @@
+{ localServer ? false
+, httpServer ? false
+, sslSupport ? false
+, pythonBindings ? false
+, javaSwigBindings ? false
+, javahlBindings ? false
+, stdenv, fetchurl
+, openssl ? null, httpd ? null, db4 ? null, expat, swig ? null, j2sdk ? null
+}:
+
+assert expat != null;
+assert localServer -> db4 != null;
+assert httpServer -> httpd != null && httpd.expat == expat;
+assert sslSupport -> openssl != null && (httpServer -> httpd.openssl == openssl);
+assert pythonBindings -> swig != null && swig.pythonSupport;
+assert javaSwigBindings -> swig != null && swig.javaSupport;
+assert javahlBindings -> j2sdk != null;
+
+stdenv.mkDerivation {
+  name = "subversion-1.1.1";
+
+  builder = /foo/bar;
+  src = fetchurl {
+    url = http://subversion.tigris.org/tarballs/subversion-1.1.1.tar.bz2;
+    md5 = "a180c3fe91680389c210c99def54d9e0";
+  };
+
+  # This is a hopefully temporary fix for the problem that
+  # libsvnjavahl.so isn't linked against libstdc++, which causes
+  # loading the library into the JVM to fail.
+  patches = if javahlBindings then [/javahl.patch] else [];
+
+  openssl = if sslSupport then openssl else null;
+  httpd = if httpServer then httpd else null;
+  db4 = if localServer then db4 else null;
+  swig = if pythonBindings || javaSwigBindings then swig else null;
+  python = if pythonBindings then swig.python else null;
+  j2sdk = if javaSwigBindings then swig.j2sdk else
+          if javahlBindings then j2sdk else null;
+
+  inherit expat localServer httpServer sslSupport
+          pythonBindings javaSwigBindings javahlBindings;
+}
diff --git a/tvix/eval/src/tests/nix_tests/parse-okay-url.nix b/tvix/eval/src/tests/nix_tests/parse-okay-url.nix
new file mode 100644
index 0000000000..08de27d0a4
--- /dev/null
+++ b/tvix/eval/src/tests/nix_tests/parse-okay-url.nix
@@ -0,0 +1,8 @@
+[ x:x
+  https://svn.cs.uu.nl:12443/repos/trace/trunk
+  http://www2.mplayerhq.hu/MPlayer/releases/fonts/font-arial-iso-8859-1.tar.bz2
+  http://losser.st-lab.cs.uu.nl/~armijn/.nix/gcc-3.3.4-static-nix.tar.gz
+  http://fpdownload.macromedia.com/get/shockwave/flash/english/linux/7.0r25/install_flash_player_7_linux.tar.gz
+  https://ftp5.gwdg.de/pub/linux/archlinux/extra/os/x86_64/unzip-6.0-14-x86_64.pkg.tar.zst
+  ftp://ftp.gtk.org/pub/gtk/v1.2/gtk+-1.2.10.tar.gz
+]
diff --git a/tvix/eval/src/tests/one_offs.rs b/tvix/eval/src/tests/one_offs.rs
new file mode 100644
index 0000000000..565d1dd48f
--- /dev/null
+++ b/tvix/eval/src/tests/one_offs.rs
@@ -0,0 +1,36 @@
+use crate::*;
+
+#[test]
+fn test_source_builtin() {
+    // Test an evaluation with a source-only builtin. The test ensures
+    // that the artificially constructed thunking is correct.
+
+    let mut eval = Evaluation::new_impure();
+    eval.src_builtins.push(("testSourceBuiltin", "42"));
+
+    let result = eval.evaluate("builtins.testSourceBuiltin", None);
+    assert!(
+        result.errors.is_empty(),
+        "evaluation failed: {:?}",
+        result.errors
+    );
+
+    let value = result.value.unwrap();
+    assert!(
+        matches!(value, Value::Integer(42)),
+        "expected the integer 42, but got {}",
+        value,
+    );
+}
+
+#[test]
+fn skip_broken_bytecode() {
+    let result = Evaluation::new_pure().evaluate(/* code = */ "x", None);
+
+    assert_eq!(result.errors.len(), 1);
+
+    assert!(matches!(
+        result.errors[0].kind,
+        ErrorKind::UnknownStaticVariable
+    ));
+}
diff --git a/tvix/eval/src/tests/tvix_tests/README.md b/tvix/eval/src/tests/tvix_tests/README.md
new file mode 100644
index 0000000000..b493aa81f1
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/README.md
@@ -0,0 +1,19 @@
+These tests are "native" to Tvix and exist in addition to the Nix test
+suite.
+
+All of these are straightforward code snippets which are expected to
+produce a certain result.
+
+# `identity-*` tests
+
+Files named `identity-*.nix` contain code that is supposed to produce
+itself exactly after evaluation.
+
+These are useful for testing literals.
+
+# `eval-okay-*` tests
+
+Files named `eval-okay-*.nix` contain code which is supposed to
+evaluate to the output in the corresponding `eval-okay-*.exp` file.
+
+This convention is taken from the original Nix test suite.
diff --git a/tvix/eval/src/tests/tvix_tests/directory/default.nix b/tvix/eval/src/tests/tvix_tests/directory/default.nix
new file mode 100644
index 0000000000..d81cc0710e
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/directory/default.nix
@@ -0,0 +1 @@
+42
diff --git a/tvix/eval/src/tests/tvix_tests/eval-fail-builtins-substring-negative-start.nix b/tvix/eval/src/tests/tvix_tests/eval-fail-builtins-substring-negative-start.nix
new file mode 100644
index 0000000000..bc7a16ded8
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-fail-builtins-substring-negative-start.nix
@@ -0,0 +1,3 @@
+# Negative start is illegal, but negative length works, see
+# eval-okay-builtins-substring-negative-length.nix
+builtins.substring (-1) 1 "Wiggly Donkers"
diff --git a/tvix/eval/src/tests/tvix_tests/eval-fail-builtins-thunk-error.nix b/tvix/eval/src/tests/tvix_tests/eval-fail-builtins-thunk-error.nix
new file mode 100644
index 0000000000..6df79d13f4
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-fail-builtins-thunk-error.nix
@@ -0,0 +1 @@
+builtins.genList (_: { }.foo) 1
diff --git a/tvix/eval/src/tests/tvix_tests/eval-fail-builtins-tojson-tostring-notcallable.nix b/tvix/eval/src/tests/tvix_tests/eval-fail-builtins-tojson-tostring-notcallable.nix
new file mode 100644
index 0000000000..345b76fde0
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-fail-builtins-tojson-tostring-notcallable.nix
@@ -0,0 +1,5 @@
+# attribute sets with a non-callable `__toString` can not be
+# serialised to JSON.
+builtins.toJSON {
+  __toString = 42;
+}
diff --git a/tvix/eval/src/tests/tvix_tests/eval-fail-builtins-tojson-tostring-strong.nix b/tvix/eval/src/tests/tvix_tests/eval-fail-builtins-tojson-tostring-strong.nix
new file mode 100644
index 0000000000..d1c72dc678
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-fail-builtins-tojson-tostring-strong.nix
@@ -0,0 +1,6 @@
+# String coercions when using builtins.toJSON on an attribute set with
+# a `__toString` attribute should be weak.
+builtins.toJSON {
+  __toString = self: self.x;
+  x = 42;
+}
diff --git a/tvix/eval/src/tests/tvix_tests/eval-fail-closed-formals.nix b/tvix/eval/src/tests/tvix_tests/eval-fail-closed-formals.nix
new file mode 100644
index 0000000000..a0cd20c470
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-fail-closed-formals.nix
@@ -0,0 +1 @@
+({ x }: x) { x = 1; y = 2; }
diff --git a/tvix/eval/src/tests/tvix_tests/eval-fail-deep-forced-thunk-error.nix b/tvix/eval/src/tests/tvix_tests/eval-fail-deep-forced-thunk-error.nix
new file mode 100644
index 0000000000..b7a7583022
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-fail-deep-forced-thunk-error.nix
@@ -0,0 +1 @@
+[ (throw "error!") ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-fail-deepseq.nix b/tvix/eval/src/tests/tvix_tests/eval-fail-deepseq.nix
new file mode 100644
index 0000000000..9baa49b063
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-fail-deepseq.nix
@@ -0,0 +1 @@
+builtins.deepSeq { x = abort "foo"; } 456
diff --git a/tvix/eval/src/tests/tvix_tests/eval-fail-division-by-zero-float.nix b/tvix/eval/src/tests/tvix_tests/eval-fail-division-by-zero-float.nix
new file mode 100644
index 0000000000..82dd687321
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-fail-division-by-zero-float.nix
@@ -0,0 +1 @@
+1.0 / 0.0
diff --git a/tvix/eval/src/tests/tvix_tests/eval-fail-division-by-zero-int.nix b/tvix/eval/src/tests/tvix_tests/eval-fail-division-by-zero-int.nix
new file mode 100644
index 0000000000..72dca4d5e4
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-fail-division-by-zero-int.nix
@@ -0,0 +1 @@
+1 / 0
diff --git a/tvix/eval/src/tests/tvix_tests/eval-fail-foldlStrict-strict-op-application.nix b/tvix/eval/src/tests/tvix_tests/eval-fail-foldlStrict-strict-op-application.nix
new file mode 100644
index 0000000000..adc029b2f2
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-fail-foldlStrict-strict-op-application.nix
@@ -0,0 +1,4 @@
+builtins.foldl'
+  (_: f: f null)
+  (throw "This doesn't explode")
+  [ (_: throw "Not the final value, but is still forced!") (_: 23) ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-fail-force-before-value-pointer-equality.nix b/tvix/eval/src/tests/tvix_tests/eval-fail-force-before-value-pointer-equality.nix
new file mode 100644
index 0000000000..a2182a508f
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-fail-force-before-value-pointer-equality.nix
@@ -0,0 +1,5 @@
+let
+  x = throw "I have been forced";
+in
+
+x == x
diff --git a/tvix/eval/src/tests/tvix_tests/eval-fail-function-formals-typecheck.nix b/tvix/eval/src/tests/tvix_tests/eval-fail-function-formals-typecheck.nix
new file mode 100644
index 0000000000..0108f958bf
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-fail-function-formals-typecheck.nix
@@ -0,0 +1,2 @@
+# A function with formal set arguments forces its argument set and verifies its type.
+({ ... }@args: 42) [ ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-fail-getEnv-coercion.nix b/tvix/eval/src/tests/tvix_tests/eval-fail-getEnv-coercion.nix
new file mode 100644
index 0000000000..fe48a5690c
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-fail-getEnv-coercion.nix
@@ -0,0 +1 @@
+builtins.getEnv { var = "PATH"; __toString = self: self.var; }
diff --git a/tvix/eval/src/tests/tvix_tests/eval-fail-infinite-recursion.nix b/tvix/eval/src/tests/tvix_tests/eval-fail-infinite-recursion.nix
new file mode 100644
index 0000000000..5e4fd3789c
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-fail-infinite-recursion.nix
@@ -0,0 +1 @@
+let x = x; in x
diff --git a/tvix/eval/src/tests/tvix_tests/eval-fail-outer-value-never-pointer-equal.nix b/tvix/eval/src/tests/tvix_tests/eval-fail-outer-value-never-pointer-equal.nix
new file mode 100644
index 0000000000..a8c3cedf61
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-fail-outer-value-never-pointer-equal.nix
@@ -0,0 +1,7 @@
+# For an explanation of this behavior see //tvix/docs/value-pointer-equality.md
+let
+  x = { foo = throw "foo"; };
+in
+
+# while `builtins.seq x null` would succeed, this fails!
+x == x
diff --git a/tvix/eval/src/tests/tvix_tests/eval-fail-parsedrvname-coerce.nix b/tvix/eval/src/tests/tvix_tests/eval-fail-parsedrvname-coerce.nix
new file mode 100644
index 0000000000..a1218de3fe
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-fail-parsedrvname-coerce.nix
@@ -0,0 +1 @@
+builtins.parseDrvName { outPath = "lol"; }
diff --git a/tvix/eval/src/tests/tvix_tests/eval-fail-remove.nix b/tvix/eval/src/tests/tvix_tests/eval-fail-remove.nix
new file mode 100644
index 0000000000..93dd8ccd45
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-fail-remove.nix
@@ -0,0 +1,5 @@
+let {
+attrs = { x = 123; y = 456; };
+
+body = (removeAttrs attrs [ "x" ]).x;
+}
diff --git a/tvix/eval/src/tests/tvix_tests/eval-fail-seq.nix b/tvix/eval/src/tests/tvix_tests/eval-fail-seq.nix
new file mode 100644
index 0000000000..cddbbfd326
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-fail-seq.nix
@@ -0,0 +1 @@
+builtins.seq (abort "foo") 2
diff --git a/tvix/eval/src/tests/tvix_tests/eval-fail-throw-abort-cannot-be-caught.nix b/tvix/eval/src/tests/tvix_tests/eval-fail-throw-abort-cannot-be-caught.nix
new file mode 100644
index 0000000000..10781cb4ea
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-fail-throw-abort-cannot-be-caught.nix
@@ -0,0 +1 @@
+(builtins.tryEval (builtins.throw (builtins.abort "abc"))).success
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-abort-throw-can-be-caught.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-abort-throw-can-be-caught.exp
new file mode 100644
index 0000000000..c508d5366f
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-abort-throw-can-be-caught.exp
@@ -0,0 +1 @@
+false
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-abort-throw-can-be-caught.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-abort-throw-can-be-caught.nix
new file mode 100644
index 0000000000..aebeca1dad
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-abort-throw-can-be-caught.nix
@@ -0,0 +1 @@
+(builtins.tryEval (builtins.abort (builtins.throw "abc"))).success
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-access-strange-identifier.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-access-strange-identifier.exp
new file mode 100644
index 0000000000..d81cc0710e
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-access-strange-identifier.exp
@@ -0,0 +1 @@
+42
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-access-strange-identifier.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-access-strange-identifier.nix
new file mode 100644
index 0000000000..433f53dc56
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-access-strange-identifier.nix
@@ -0,0 +1,9 @@
+let
+  # There is no syntax for accessing this identifier in an ordinary
+  # way.
+  "foo bar" = 42;
+in
+({
+  # but we *can* inherit it back out
+  inherit "foo bar";
+})."foo bar"
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-add-paths.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-add-paths.exp
new file mode 100644
index 0000000000..94ba9a881a
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-add-paths.exp
@@ -0,0 +1 @@
+[ /bin /binbar /binbar /binbar /binbar /bin/bar /bin/bin ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-add-paths.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-add-paths.nix
new file mode 100644
index 0000000000..462f670882
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-add-paths.nix
@@ -0,0 +1,9 @@
+[
+  (/bin + "/")
+  (/bin + "bar")
+  (let name = "bar"; in /bin + name)
+  (let name = "bar"; in /bin + "${name}")
+  (let name = "bar"; in /bin + "/" + "${name}")
+  (let name = "bar"; in /bin + "/${name}")
+  (/bin + /bin)
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-arithmetic-float.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-arithmetic-float.exp
new file mode 100644
index 0000000000..08ef6079f8
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-arithmetic-float.exp
@@ -0,0 +1 @@
+{ add = 37.34; div = 1.05714; mul = 105.154; sub = 14.35; }
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-arithmetic-float.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-arithmetic-float.nix
new file mode 100644
index 0000000000..9d12aee061
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-arithmetic-float.nix
@@ -0,0 +1,6 @@
+{
+  add = 12.34 + 25.0;
+  sub = 20.05 - 5.7;
+  mul = 28.42 * 3.70;
+  div = 18.5 / 17.5;
+}
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-arithmetic-int.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-arithmetic-int.exp
new file mode 100644
index 0000000000..a5711e8bfe
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-arithmetic-int.exp
@@ -0,0 +1 @@
+{ add = 20; div = 3; mul = 8; sub = 15; }
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-arithmetic-int.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-arithmetic-int.nix
new file mode 100644
index 0000000000..c53790db09
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-arithmetic-int.nix
@@ -0,0 +1,6 @@
+{
+  add = 15 + 5;
+  sub = 20 - 5;
+  mul = 4 * 2;
+  div = 9 / 3;
+}
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-assert-thunk-condition.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-assert-thunk-condition.exp
new file mode 100644
index 0000000000..aabe6ec390
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-assert-thunk-condition.exp
@@ -0,0 +1 @@
+21
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-assert-thunk-condition.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-assert-thunk-condition.nix
new file mode 100644
index 0000000000..ac65f5814d
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-assert-thunk-condition.nix
@@ -0,0 +1,7 @@
+let
+  condition = x: y: x < y;
+in
+
+# The function application here will become a thunk which verifies that
+  # assert forces the condition expression correctly.
+assert condition 21 42; 21
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-attempt-to-call-catchable.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-attempt-to-call-catchable.exp
new file mode 100644
index 0000000000..c508d5366f
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-attempt-to-call-catchable.exp
@@ -0,0 +1 @@
+false
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-attempt-to-call-catchable.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-attempt-to-call-catchable.nix
new file mode 100644
index 0000000000..f4ef72a88b
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-attempt-to-call-catchable.nix
@@ -0,0 +1 @@
+(builtins.tryEval (throw "fred" 5)).success
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-attr-key-catchable.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-attr-key-catchable.exp
new file mode 100644
index 0000000000..c508d5366f
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-attr-key-catchable.exp
@@ -0,0 +1 @@
+false
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-attr-key-catchable.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-attr-key-catchable.nix
new file mode 100644
index 0000000000..b9d835bcc5
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-attr-key-catchable.nix
@@ -0,0 +1 @@
+(builtins.tryEval { "${builtins.throw "a"}" = "b"; }).success
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-attrs-inherit-literal.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-attrs-inherit-literal.exp
new file mode 100644
index 0000000000..60d3b2f4a4
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-attrs-inherit-literal.exp
@@ -0,0 +1 @@
+15
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-attrs-inherit-literal.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-attrs-inherit-literal.nix
new file mode 100644
index 0000000000..9ecb4e9880
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-attrs-inherit-literal.nix
@@ -0,0 +1,2 @@
+# the 'from' part of an `inherit` can be any expression.
+{ inherit ({ a = 15; }) a; }.a
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-attrs-simple-inherit.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-attrs-simple-inherit.exp
new file mode 100644
index 0000000000..a779fce51a
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-attrs-simple-inherit.exp
@@ -0,0 +1 @@
+{ a = 1; }
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-attrs-simple-inherit.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-attrs-simple-inherit.nix
new file mode 100644
index 0000000000..68880bcfd8
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-attrs-simple-inherit.nix
@@ -0,0 +1,4 @@
+let
+  a = 1;
+in
+{ inherit a; }
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-attrs-update-empty-lhs.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-attrs-update-empty-lhs.exp
new file mode 100644
index 0000000000..fedf8f25a6
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-attrs-update-empty-lhs.exp
@@ -0,0 +1 @@
+{ a = "ok"; }
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-attrs-update-empty-lhs.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-attrs-update-empty-lhs.nix
new file mode 100644
index 0000000000..973170cdd5
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-attrs-update-empty-lhs.nix
@@ -0,0 +1 @@
+{ } // { a = "ok"; }
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-attrs-update-empty-rhs.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-attrs-update-empty-rhs.exp
new file mode 100644
index 0000000000..fedf8f25a6
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-attrs-update-empty-rhs.exp
@@ -0,0 +1 @@
+{ a = "ok"; }
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-attrs-update-empty-rhs.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-attrs-update-empty-rhs.nix
new file mode 100644
index 0000000000..f51b88e93a
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-attrs-update-empty-rhs.nix
@@ -0,0 +1 @@
+{ a = "ok"; } // { }
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-attrs-update-kv-lhs.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-attrs-update-kv-lhs.exp
new file mode 100644
index 0000000000..c2234a47e2
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-attrs-update-kv-lhs.exp
@@ -0,0 +1 @@
+{ name = "foo"; other = 42; value = "bar"; }
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-attrs-update-kv-lhs.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-attrs-update-kv-lhs.nix
new file mode 100644
index 0000000000..6f71684902
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-attrs-update-kv-lhs.nix
@@ -0,0 +1 @@
+{ name = "foo"; value = "bar"; } // { other = 42; }
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-attrs-update.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-attrs-update.exp
new file mode 100644
index 0000000000..57f4d541bd
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-attrs-update.exp
@@ -0,0 +1 @@
+{ a = 15; b = "works"; }
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-attrs-update.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-attrs-update.nix
new file mode 100644
index 0000000000..735602fe02
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-attrs-update.nix
@@ -0,0 +1 @@
+{ a = 15; } // { b = "works"; }
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-basenameof-propagate-catchables.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-basenameof-propagate-catchables.exp
new file mode 100644
index 0000000000..c508d5366f
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-basenameof-propagate-catchables.exp
@@ -0,0 +1 @@
+false
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-basenameof-propagate-catchables.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-basenameof-propagate-catchables.nix
new file mode 100644
index 0000000000..240736b12a
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-basenameof-propagate-catchables.nix
@@ -0,0 +1 @@
+(builtins.tryEval (builtins.baseNameOf (throw "jill"))).success
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-basenameof.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-basenameof.exp
new file mode 100644
index 0000000000..60a773f4af
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-basenameof.exp
@@ -0,0 +1 @@
+[ "bar" "foo" "" "bar" "." "" "" "" ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-basenameof.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-basenameof.nix
new file mode 100644
index 0000000000..bc59613f54
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-basenameof.nix
@@ -0,0 +1,10 @@
+[
+  (builtins.baseNameOf /foo/bar)
+  (builtins.baseNameOf "foo")
+  (builtins.baseNameOf "foo///")
+  (builtins.baseNameOf "foo/bar")
+  (builtins.baseNameOf "./.")
+  (builtins.baseNameOf "")
+  (builtins.baseNameOf /.)
+  (builtins.toString (builtins.baseNameOf /.))
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-add.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-add.exp
new file mode 100644
index 0000000000..c3ac813de6
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-add.exp
@@ -0,0 +1 @@
+[ 18 18.9 18.9 19.1 19 42 ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-add.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-add.nix
new file mode 100644
index 0000000000..b04b1d1fa6
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-add.nix
@@ -0,0 +1,8 @@
+[
+  (builtins.add 7 11)
+  (builtins.add 7.9 11)
+  (builtins.add 7 11.9)
+  (builtins.add 7.2 11.9)
+  (builtins.add 7.1 11.9)
+  (builtins.add (builtins.add 21 10) 11)
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-all-propagate-catchable.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-all-propagate-catchable.exp
new file mode 100644
index 0000000000..48e341f4d8
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-all-propagate-catchable.exp
@@ -0,0 +1 @@
+[ false false false ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-all-propagate-catchable.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-all-propagate-catchable.nix
new file mode 100644
index 0000000000..8902e27c45
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-all-propagate-catchable.nix
@@ -0,0 +1 @@
+map (e: (builtins.tryEval e).success) [ (builtins.all (builtins.throw "a") [ "" ]) (builtins.all (x: true) (builtins.throw "b")) (builtins.all (_: builtins.throw "x") [ "" ]) ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-all.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-all.exp
new file mode 100644
index 0000000000..82ca7e6b6d
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-all.exp
@@ -0,0 +1 @@
+[ true true false false false false true true false ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-all.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-all.nix
new file mode 100644
index 0000000000..12d62632dd
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-all.nix
@@ -0,0 +1,15 @@
+[
+  (builtins.all (x: x) [ ])
+  (builtins.all (x: x) [ true true true ])
+  (builtins.all (x: x) [ false false false ])
+  (builtins.all (x: x) [ true true false ])
+  (builtins.all (x: x) [ false true true ])
+
+  # evaluation should short-circuit
+  (builtins.all (x: x) [ true false (builtins.abort "should be unreachable") ])
+
+  # arbitrary functions supported
+  (builtins.all (x: x * 2 == 42) [ ])
+  (builtins.all (x: x * 2 == 42) [ 21 21 21 ])
+  (builtins.all (x: x * 2 == 42) [ 1 2 3 ])
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-any-propagate-catchable.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-any-propagate-catchable.exp
new file mode 100644
index 0000000000..48e341f4d8
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-any-propagate-catchable.exp
@@ -0,0 +1 @@
+[ false false false ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-any-propagate-catchable.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-any-propagate-catchable.nix
new file mode 100644
index 0000000000..8db5c0c6dc
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-any-propagate-catchable.nix
@@ -0,0 +1 @@
+map (e: (builtins.tryEval e).success) [ (builtins.any (builtins.throw "a") [ "" ]) (builtins.any (x: true) (builtins.throw "b")) (builtins.any (_: builtins.throw "a") [ "" ]) ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-any.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-any.exp
new file mode 100644
index 0000000000..d6846ac3f7
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-any.exp
@@ -0,0 +1 @@
+[ false true false true true true false true false ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-any.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-any.nix
new file mode 100644
index 0000000000..2c659f130b
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-any.nix
@@ -0,0 +1,15 @@
+[
+  (builtins.any (x: x) [ ])
+  (builtins.any (x: x) [ true true true ])
+  (builtins.any (x: x) [ false false false ])
+  (builtins.any (x: x) [ true true false ])
+  (builtins.any (x: x) [ false true true ])
+
+  # evaluation should short-circuit
+  (builtins.any (x: x) [ false true (builtins.abort "should be unreachable") ])
+
+  # arbitrary functions supported
+  (builtins.any (x: x * 2 == 42) [ ])
+  (builtins.any (x: x * 2 == 42) [ 7 21 42 ])
+  (builtins.any (x: x * 2 == 42) [ 1 2 3 ])
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-attrnames.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-attrnames.exp
new file mode 100644
index 0000000000..6521066a8e
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-attrnames.exp
@@ -0,0 +1 @@
+[ [ ] [ "bar" "baz" "foo" ] [ "Baz" "Foo" "bar" ] [ "Eric Idle" "Graham Chapman" "John Cleese" "Michael Palin" "Terry Gilliam" "Terry Jones" ] ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-attrnames.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-attrnames.nix
new file mode 100644
index 0000000000..82240a0647
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-attrnames.nix
@@ -0,0 +1,13 @@
+[
+  (builtins.attrNames { })
+  (builtins.attrNames { foo = 1; bar = 2; baz = 3; })
+  (builtins.attrNames { Foo = 1; bar = 2; Baz = 3; })
+  (builtins.attrNames {
+    "Graham Chapman" = true;
+    "John Cleese" = true;
+    "Terry Gilliam" = true;
+    "Eric Idle" = true;
+    "Terry Jones" = true;
+    "Michael Palin" = true;
+  })
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-attrvalues-propagate-catchable.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-attrvalues-propagate-catchable.exp
new file mode 100644
index 0000000000..c508d5366f
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-attrvalues-propagate-catchable.exp
@@ -0,0 +1 @@
+false
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-attrvalues-propagate-catchable.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-attrvalues-propagate-catchable.nix
new file mode 100644
index 0000000000..b8c15c8748
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-attrvalues-propagate-catchable.nix
@@ -0,0 +1 @@
+(builtins.tryEval (builtins.attrValues (builtins.throw "a"))).success
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-attrvalues.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-attrvalues.exp
new file mode 100644
index 0000000000..35c3697720
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-attrvalues.exp
@@ -0,0 +1 @@
+[ [ ] [ 2 3 1 ] ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-attrvalues.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-attrvalues.nix
new file mode 100644
index 0000000000..ce6c5c3816
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-attrvalues.nix
@@ -0,0 +1,4 @@
+[
+  (builtins.attrValues { })
+  (builtins.attrValues { foo = 1; bar = 2; baz = 3; })
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-bitand.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-bitand.exp
new file mode 100644
index 0000000000..30b348853e
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-bitand.exp
@@ -0,0 +1 @@
+[ 0 0 0 1 8 8 8 8 ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-bitand.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-bitand.nix
new file mode 100644
index 0000000000..af40005ed9
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-bitand.nix
@@ -0,0 +1,10 @@
+[
+  (builtins.bitAnd 0 0)
+  (builtins.bitAnd 0 1)
+  (builtins.bitAnd 1 0)
+  (builtins.bitAnd 1 1)
+  (builtins.bitAnd 8 8)
+  (builtins.bitAnd 8 (builtins.add 4 4))
+  (builtins.bitAnd (builtins.add 4 4) 8)
+  (builtins.bitAnd (builtins.add 4 4) (builtins.add 4 4))
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-bitor.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-bitor.exp
new file mode 100644
index 0000000000..2556b4183c
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-bitor.exp
@@ -0,0 +1 @@
+[ 0 1 1 1 8 8 8 8 ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-bitor.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-bitor.nix
new file mode 100644
index 0000000000..9c28f6d7ac
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-bitor.nix
@@ -0,0 +1,10 @@
+[
+  (builtins.bitOr 0 0)
+  (builtins.bitOr 1 0)
+  (builtins.bitOr 0 1)
+  (builtins.bitOr 1 1)
+  (builtins.bitOr 8 8)
+  (builtins.bitOr 8 (builtins.add 4 4))
+  (builtins.bitOr (builtins.add 4 4) 8)
+  (builtins.bitOr (builtins.add 4 4) (builtins.add 4 4))
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-bitxor.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-bitxor.exp
new file mode 100644
index 0000000000..457157d459
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-bitxor.exp
@@ -0,0 +1 @@
+[ 0 1 1 0 8 8 0 0 0 0 ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-bitxor.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-bitxor.nix
new file mode 100644
index 0000000000..80e363fb07
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-bitxor.nix
@@ -0,0 +1,12 @@
+[
+  (builtins.bitXor 0 0)
+  (builtins.bitXor 1 0)
+  (builtins.bitXor 0 1)
+  (builtins.bitXor 1 1)
+  (builtins.bitXor 8 0)
+  (builtins.bitXor 0 8)
+  (builtins.bitXor 8 8)
+  (builtins.bitXor 8 (builtins.add 4 4))
+  (builtins.bitXor (builtins.add 4 4) 8)
+  (builtins.bitXor (builtins.add 4 4) (builtins.add 4 4))
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-builtins.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-builtins.exp
new file mode 100644
index 0000000000..27ba77ddaf
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-builtins.exp
@@ -0,0 +1 @@
+true
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-builtins.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-builtins.nix
new file mode 100644
index 0000000000..434ccf8049
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-builtins.nix
@@ -0,0 +1 @@
+[ builtins ] == [ builtins.builtins ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-catAttrs.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-catAttrs.exp
new file mode 100644
index 0000000000..f8c0b2de5f
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-catAttrs.exp
@@ -0,0 +1 @@
+[ 21 "+" 21 "=" 42 ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-catAttrs.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-catAttrs.nix
new file mode 100644
index 0000000000..edac76d446
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-catAttrs.nix
@@ -0,0 +1,10 @@
+builtins.catAttrs "foo" [
+  { foo = 21; }
+  { bar = 23; foo = "+"; }
+  { }
+  { bar = 12; }
+  { foo = 21 + 0; }
+  { foo = "="; }
+  ({ bar = 13; } // { baz = 89; })
+  { foo = 42; bar = 33; }
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-catattrs-propagate-catchable.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-catattrs-propagate-catchable.exp
new file mode 100644
index 0000000000..c3bb809c9f
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-catattrs-propagate-catchable.exp
@@ -0,0 +1 @@
+[ false false ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-catattrs-propagate-catchable.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-catattrs-propagate-catchable.nix
new file mode 100644
index 0000000000..5385591f77
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-catattrs-propagate-catchable.nix
@@ -0,0 +1 @@
+map (e: (builtins.tryEval e).success) [ (builtins.catAttrs "a" (builtins.throw "b")) (builtins.catAttrs (builtins.throw "a") { a = 1; }) ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-compareVersions.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-compareVersions.exp
new file mode 100644
index 0000000000..e69498c3e1
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-compareVersions.exp
@@ -0,0 +1 @@
+[ 0 -1 -1 0 0 0 1 1 -1 1 -1 1 -1 -1 -1 -1 0 1 -1 -1 1 -1 -1 0 1 1 1 1 -1 -1 -1 -1 -1 ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-compareVersions.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-compareVersions.nix
new file mode 100644
index 0000000000..40a90b5070
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-compareVersions.nix
@@ -0,0 +1,46 @@
+let
+  cmp = a: b:
+    let
+      ord1 = builtins.compareVersions a b;
+      ord2 = builtins.compareVersions b a;
+    in
+    assert ord1 == -ord2; ord1;
+in
+
+[
+  (cmp "1.2.3" "1.2.3")
+  (cmp "1.2.2" "1.2.3")
+  (cmp "1.2.3" "1.2.40")
+  (cmp "1.2.3" ".1.2.3")
+  (cmp "1.2.3" "1..2.3")
+  (cmp "1.2.3" "1.2.3.")
+  (cmp "1.2.3" "1.2")
+  (cmp "1.2.3" "1.2.a")
+  (cmp "1a.b" "1a.2")
+  (cmp "1" "")
+  (cmp "1.0" "1.0.0")
+  (cmp "2.3" "2.3pre")
+  (cmp "2.3" "2.3.0pre")
+  (cmp "2.3pre" "2.3.0pre")
+  (cmp "2.3" "2.3prepre")
+  (cmp "2.3pre" "2.3prepre")
+  (cmp "2.3prepre" "2.3prepre")
+  # check that the plain word comparison (via Ord) behaves the same
+  (cmp "foo" "bar")
+  (cmp "FoO" "fOo")
+  (cmp "foo" "fooo")
+  (cmp "foopre" "foo")
+  # Subset of test cases from eval-okay-versions.nix shipped by C++ Nix
+  (cmp "1.0" "2.3")
+  (cmp "2.1" "2.3")
+  (cmp "2.3" "2.3")
+  (cmp "2.5" "2.3")
+  (cmp "3.1" "2.3")
+  (cmp "2.3.1" "2.3")
+  (cmp "2.3.1" "2.3a")
+  (cmp "2.3pre1" "2.3")
+  (cmp "2.3pre3" "2.3pre12")
+  (cmp "2.3a" "2.3c")
+  (cmp "2.3pre1" "2.3c")
+  (cmp "2.3pre1" "2.3q")
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-concat-lists-propagate-catchable.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-concat-lists-propagate-catchable.exp
new file mode 100644
index 0000000000..c82ddd8a80
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-concat-lists-propagate-catchable.exp
@@ -0,0 +1 @@
+[ false false true ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-concat-lists-propagate-catchable.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-concat-lists-propagate-catchable.nix
new file mode 100644
index 0000000000..8071daf7fb
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-concat-lists-propagate-catchable.nix
@@ -0,0 +1 @@
+map (e: (builtins.tryEval e).success) [ (builtins.concatLists (builtins.throw "a")) (builtins.concatLists [ [ ] (builtins.throw "a") ]) (builtins.concatLists [ [ ] [ ] [ builtins.throw "a" ] ]) ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-concat-lists.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-concat-lists.exp
new file mode 100644
index 0000000000..64ae529ac2
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-concat-lists.exp
@@ -0,0 +1 @@
+[ [ ] [ 1 2 3 4 5 6 ] [ [ 1 ] [ 2 ] [ 3 ] ] [ 1 2 3 4 5 6 ] ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-concat-lists.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-concat-lists.nix
new file mode 100644
index 0000000000..19ef5eba11
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-concat-lists.nix
@@ -0,0 +1,6 @@
+[
+  (builtins.concatLists [ ])
+  (builtins.concatLists [ [ 1 2 ] [ 3 4 ] [ 5 6 ] ])
+  (builtins.concatLists [ [ [ 1 ] [ 2 ] ] [ [ 3 ] ] [ ] ])
+  (builtins.concatLists [ [ 1 2 ] [ ] [ 3 4 ] [ ] [ 5 6 ] ])
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-concat-map-propagate-catchable.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-concat-map-propagate-catchable.exp
new file mode 100644
index 0000000000..48e341f4d8
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-concat-map-propagate-catchable.exp
@@ -0,0 +1 @@
+[ false false false ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-concat-map-propagate-catchable.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-concat-map-propagate-catchable.nix
new file mode 100644
index 0000000000..740f0d3fbc
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-concat-map-propagate-catchable.nix
@@ -0,0 +1 @@
+map (e: (builtins.tryEval e).success) [ (builtins.concatMap (builtins.throw "a") [ "" ]) (builtins.concatMap (_: builtins.throw "x") [ "" ]) (builtins.concatMap (_: [ ]) (builtins.throw "a")) ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-concat-strings-sep-propagate-catchable.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-concat-strings-sep-propagate-catchable.exp
new file mode 100644
index 0000000000..48e341f4d8
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-concat-strings-sep-propagate-catchable.exp
@@ -0,0 +1 @@
+[ false false false ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-concat-strings-sep-propagate-catchable.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-concat-strings-sep-propagate-catchable.nix
new file mode 100644
index 0000000000..ce5ce4170f
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-concat-strings-sep-propagate-catchable.nix
@@ -0,0 +1 @@
+map (e: (builtins.tryEval e).success) [ (builtins.concatStringsSep (builtins.throw "a") [ "" ]) (builtins.concatStringsSep "," (builtins.throw "a")) (builtins.concatStringsSep "," [ "a" (builtins.throw "a") ]) ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-div.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-div.exp
new file mode 100644
index 0000000000..44154ba6a6
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-div.exp
@@ -0,0 +1 @@
+[ 3 7 0 1 0 0.5 0.5 0.5 42 1 1 -1 -1 -1 1 1 -1 -1 -1 1 1 -1 -1 -1 -74711 -74711 -74711 ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-div.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-div.nix
new file mode 100644
index 0000000000..dc6ce27815
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-div.nix
@@ -0,0 +1,34 @@
+[
+  (builtins.div 9 3)
+  (builtins.div 7 1)
+  (builtins.div 3 9)
+  (builtins.div 4 4)
+  (builtins.div 1 2)
+  (builtins.div 1.0 2)
+  (builtins.div 1 2.0)
+  (builtins.div 1.0 2.0)
+  (builtins.div (builtins.div 84 4) 0.5)
+
+  # builtins.div should truncate towards 0
+  (builtins.div 3 2)
+  (builtins.div (-3) (-2))
+  (builtins.div (-3) 2)
+  (builtins.div 3 (-2))
+  (-(builtins.div 3 2))
+
+  (builtins.div 4 3)
+  (builtins.div (-4) (-3))
+  (builtins.div (-4) 3)
+  (builtins.div 4 (-3))
+  (-(builtins.div 4 3))
+
+  (builtins.div 5 3)
+  (builtins.div (-5) (-3))
+  (builtins.div (-5) 3)
+  (builtins.div 5 (-3))
+  (-(builtins.div 5 3))
+
+  (builtins.div 2147812578 (-28748))
+  (builtins.div (-2147812578) 28748)
+  (-(builtins.div 2147812578 28748))
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-elemAt-catchable.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-elemAt-catchable.exp
new file mode 100644
index 0000000000..c508d5366f
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-elemAt-catchable.exp
@@ -0,0 +1 @@
+false
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-elemAt-catchable.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-elemAt-catchable.nix
new file mode 100644
index 0000000000..97be4b013c
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-elemAt-catchable.nix
@@ -0,0 +1 @@
+(builtins.tryEval (builtins.elemAt (throw "fred") 0)).success
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-elemat.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-elemat.exp
new file mode 100644
index 0000000000..3701c9d75f
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-elemat.exp
@@ -0,0 +1 @@
+[ "foo" "bar" "baz" ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-elemat.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-elemat.nix
new file mode 100644
index 0000000000..762adeebbf
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-elemat.nix
@@ -0,0 +1,5 @@
+[
+  (builtins.elemAt [ "foo" "bar" "baz" ] 0)
+  (builtins.elemAt [ "foo" "bar" "baz" ] 1)
+  (builtins.elemAt [ "foo" "bar" "baz" ] 2)
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-filter-catchable.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-filter-catchable.exp
new file mode 100644
index 0000000000..c508d5366f
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-filter-catchable.exp
@@ -0,0 +1 @@
+false
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-filter-catchable.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-filter-catchable.nix
new file mode 100644
index 0000000000..98d90b01bb
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-filter-catchable.nix
@@ -0,0 +1 @@
+(builtins.tryEval (builtins.filter (_: throw "fred") [ 3 ])).success
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-filter-propagate-catchable.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-filter-propagate-catchable.exp
new file mode 100644
index 0000000000..48e341f4d8
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-filter-propagate-catchable.exp
@@ -0,0 +1 @@
+[ false false false ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-filter-propagate-catchable.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-filter-propagate-catchable.nix
new file mode 100644
index 0000000000..715a0ce34f
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-filter-propagate-catchable.nix
@@ -0,0 +1 @@
+map (e: (builtins.tryEval e).success) [ (builtins.filter (builtins.throw "a") [ "" ]) (builtins.filter (x: true) (builtins.throw "b")) (builtins.filter (_: builtins.throw "x") [ "" ]) ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-filter.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-filter.exp
new file mode 100644
index 0000000000..fb94ebaa49
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-filter.exp
@@ -0,0 +1 @@
+[ [ 1 2 3 4 5 ] [ ] [ 2 2 2 ] [ [ 1 2 ] [ 3 4 ] ] ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-filter.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-filter.nix
new file mode 100644
index 0000000000..b621fdb43e
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-filter.nix
@@ -0,0 +1,13 @@
+[
+  (builtins.filter (_: true) [ 1 2 3 4 5 ])
+  (builtins.filter (_: false) [ 1 2 3 4 5 ])
+  (builtins.filter (x: x == 2) [ 1 2 1 2 1 2 ])
+
+  (builtins.filter (x: (builtins.length x) > 0) [
+    [ ]
+    [ 1 2 ]
+    [ ]
+    [ ]
+    [ 3 4 ]
+  ])
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-foldl-propagate-catchable.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-foldl-propagate-catchable.exp
new file mode 100644
index 0000000000..7bf6c63466
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-foldl-propagate-catchable.exp
@@ -0,0 +1 @@
+[ false false false false true true ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-foldl-propagate-catchable.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-foldl-propagate-catchable.nix
new file mode 100644
index 0000000000..c11a21ce1e
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-foldl-propagate-catchable.nix
@@ -0,0 +1,8 @@
+map (e: (builtins.tryEval e).success) [
+  (builtins.foldl' (builtins.throw "a") { } [{ } { } { }])
+  (builtins.foldl' (x: y: x // y) { } (builtins.throw "b"))
+  (builtins.foldl' (_: _: builtins.throw "x") { } [{ }])
+  (builtins.foldl' (x: y: x // y) (builtins.throw "x") [{ }])
+  (builtins.foldl' (x: y: x // y) { } [{ } { a = builtins.throw "z"; } { }])
+  (builtins.foldl' (x: y: x // y) { } [{ } { b = 3; a = builtins.throw "u"; } { }])
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-from-json-propagate-catchable.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-from-json-propagate-catchable.exp
new file mode 100644
index 0000000000..c508d5366f
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-from-json-propagate-catchable.exp
@@ -0,0 +1 @@
+false
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-from-json-propagate-catchable.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-from-json-propagate-catchable.nix
new file mode 100644
index 0000000000..aa973c1352
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-from-json-propagate-catchable.nix
@@ -0,0 +1 @@
+(builtins.tryEval (builtins.fromJSON (builtins.throw "a"))).success
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-function-args-propagate-catchable.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-function-args-propagate-catchable.exp
new file mode 100644
index 0000000000..bd52bff2fa
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-function-args-propagate-catchable.exp
@@ -0,0 +1 @@
+[ true false ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-function-args-propagate-catchable.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-function-args-propagate-catchable.nix
new file mode 100644
index 0000000000..ca3e6772f2
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-function-args-propagate-catchable.nix
@@ -0,0 +1,4 @@
+map (e: (builtins.tryEval e).success) [
+  (builtins.functionArgs (_: builtins.throw "a"))
+  (builtins.functionArgs (builtins.throw "b"))
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-gen-list-propagate-catchable.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-gen-list-propagate-catchable.exp
new file mode 100644
index 0000000000..652df3d4da
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-gen-list-propagate-catchable.exp
@@ -0,0 +1 @@
+[ true false true ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-gen-list-propagate-catchable.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-gen-list-propagate-catchable.nix
new file mode 100644
index 0000000000..3d4739966e
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-gen-list-propagate-catchable.nix
@@ -0,0 +1,5 @@
+map (e: (builtins.tryEval e).success) [
+  (builtins.genList (builtins.throw "a") 10)
+  (builtins.genList (i: "") (builtins.throw "b"))
+  (builtins.genList (i: builtins.throw "x") 5)
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-genList-function-strictness.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-genList-function-strictness.exp
new file mode 100644
index 0000000000..06712ebc33
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-genList-function-strictness.exp
@@ -0,0 +1 @@
+[ <LAMBDA> 0 1 2 ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-genList-function-strictness.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-genList-function-strictness.nix
new file mode 100644
index 0000000000..e161e3b4af
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-genList-function-strictness.nix
@@ -0,0 +1,8 @@
+let
+  self =
+    let
+      l = builtins.genList (builtins.head self) 3;
+    in
+    [ (x: x) ] ++ l;
+in
+self
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-genericClosure-pointer-equality.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-genericClosure-pointer-equality.exp
new file mode 100644
index 0000000000..87977137a5
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-genericClosure-pointer-equality.exp
@@ -0,0 +1 @@
+[ { key = [ { foo = <LAMBDA>; } ]; val = null; } ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-genericClosure-pointer-equality.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-genericClosure-pointer-equality.nix
new file mode 100644
index 0000000000..f6ca340c09
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-genericClosure-pointer-equality.nix
@@ -0,0 +1,15 @@
+let
+  foo = x: x;
+in
+
+# key needs to be a list since it uses comparison, not equality checks:
+  # lists are comparable in Nix if all non-comparable items in them are equal (e.g.
+  # functions, attribute sets).
+builtins.genericClosure {
+  startSet = [
+    { key = [{ inherit foo; }]; val = null; }
+  ];
+  operator = { val, ... }: if val != null then [ ] else [
+    { key = [{ inherit foo; }]; val = throw "no pointer equality? 🥺👉👈"; }
+  ];
+}
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-genericClosure-propagate-catchable.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-genericClosure-propagate-catchable.exp
new file mode 100644
index 0000000000..6c89e78fc4
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-genericClosure-propagate-catchable.exp
@@ -0,0 +1 @@
+{ success = false; value = false; }
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-genericClosure-propagate-catchable.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-genericClosure-propagate-catchable.nix
new file mode 100644
index 0000000000..1dfc0bb04f
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-genericClosure-propagate-catchable.nix
@@ -0,0 +1 @@
+builtins.tryEval (builtins.genericClosure { operator = (_: [{ key = throw "lol"; }]); startSet = [{ key = "lol"; }]; })
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-getAttr-catchable.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-getAttr-catchable.exp
new file mode 100644
index 0000000000..c508d5366f
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-getAttr-catchable.exp
@@ -0,0 +1 @@
+false
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-getAttr-catchable.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-getAttr-catchable.nix
new file mode 100644
index 0000000000..ef4a042ffb
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-getAttr-catchable.nix
@@ -0,0 +1 @@
+(builtins.tryEval (builtins.getAttr (throw "fred") "bob")).success
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-getContext-propagate-catchable.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-getContext-propagate-catchable.exp
new file mode 100644
index 0000000000..c508d5366f
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-getContext-propagate-catchable.exp
@@ -0,0 +1 @@
+false
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-getContext-propagate-catchable.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-getContext-propagate-catchable.nix
new file mode 100644
index 0000000000..70521665ca
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-getContext-propagate-catchable.nix
@@ -0,0 +1 @@
+(builtins.tryEval (builtins.getContext (builtins.throw "a"))).success
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-getattr.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-getattr.exp
new file mode 100644
index 0000000000..89fa6c6810
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-getattr.exp
@@ -0,0 +1 @@
+[ 1 2 3 { bar = { baz = 3; }; } ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-getattr.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-getattr.nix
new file mode 100644
index 0000000000..87a2adbcd3
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-getattr.nix
@@ -0,0 +1,6 @@
+[
+  (builtins.getAttr "foo" { foo = 1; bar = 2; baz = 3; })
+  (builtins.getAttr "bar" { foo = 1; bar = 2; baz = 3; })
+  (builtins.getAttr "baz" { foo = 1; bar = 2; baz = 3; })
+  (builtins.getAttr "foo" { foo = { bar = { baz = 3; }; }; })
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-group-by-propagate-catchable.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-group-by-propagate-catchable.exp
new file mode 100644
index 0000000000..48e341f4d8
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-group-by-propagate-catchable.exp
@@ -0,0 +1 @@
+[ false false false ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-group-by-propagate-catchable.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-group-by-propagate-catchable.nix
new file mode 100644
index 0000000000..182601abb1
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-group-by-propagate-catchable.nix
@@ -0,0 +1,5 @@
+map (e: (builtins.tryEval e).success) [
+  (builtins.groupBy (builtins.throw "a") [ "" ])
+  (builtins.groupBy (x: true) (builtins.throw "b"))
+  (builtins.groupBy (_: builtins.throw "x") [ "" ])
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-groupby-thunk.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-groupby-thunk.exp
new file mode 100644
index 0000000000..94649819ca
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-groupby-thunk.exp
@@ -0,0 +1 @@
+{ fred = [ { x = "fred"; y = "fred"; } ]; }
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-groupby-thunk.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-groupby-thunk.nix
new file mode 100644
index 0000000000..eaf48045f2
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-groupby-thunk.nix
@@ -0,0 +1,5 @@
+builtins.groupBy
+  (v: v.x)
+  [ (rec { y = x; x = "fred"; }) ]
+
+
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-hasContext-propagate-catchable.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-hasContext-propagate-catchable.exp
new file mode 100644
index 0000000000..c508d5366f
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-hasContext-propagate-catchable.exp
@@ -0,0 +1 @@
+false
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-hasContext-propagate-catchable.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-hasContext-propagate-catchable.nix
new file mode 100644
index 0000000000..0c02a82730
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-hasContext-propagate-catchable.nix
@@ -0,0 +1 @@
+(builtins.tryEval (builtins.hasContext (builtins.throw "a"))).success
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-hasattr.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-hasattr.exp
new file mode 100644
index 0000000000..541fe347cb
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-hasattr.exp
@@ -0,0 +1 @@
+[ true true true false false true false ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-hasattr.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-hasattr.nix
new file mode 100644
index 0000000000..fb786b4f09
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-hasattr.nix
@@ -0,0 +1,9 @@
+[
+  (builtins.hasAttr "foo" { foo = 1; bar = 2; baz = 3; })
+  (builtins.hasAttr "bar" { foo = 1; bar = 2; baz = 3; })
+  (builtins.hasAttr "baz" { foo = 1; bar = 2; baz = 3; })
+  (builtins.hasAttr "FOO" { foo = 1; bar = 2; baz = 3; })
+  (builtins.hasAttr "foo" { })
+  (builtins.hasAttr ("f" + "o" + "o") { foo = 1; })
+  (builtins.hasAttr ("b" + "a" + "r") { foo = 1; })
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-hashString.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-hashString.exp
new file mode 100644
index 0000000000..e00b80e561
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-hashString.exp
@@ -0,0 +1 @@
+[ "8a0614b4eaa4cffb7515ec101847e198" "8bd218cf61321d8aa05b3602b99f90d2d8cef3d6" "80ac06d74cb6c5d14af718ce8c3c1255969a1a595b76a3cf92354a95331a879a" "0edac513b6b0454705b553deda4c9b055da0939d26d2f73548862817ebeac5378cf64ff7a752ce1a0590a736735d3bbd9e8a7f04d93617cdf514313f5ab5baa4" ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-hashString.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-hashString.nix
new file mode 100644
index 0000000000..aed723d367
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-hashString.nix
@@ -0,0 +1,6 @@
+[
+  (builtins.hashString "md5" "tvix")
+  (builtins.hashString "sha1" "tvix")
+  (builtins.hashString "sha256" "tvix")
+  (builtins.hashString "sha512" "tvix")
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-head-propagate-catchable.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-head-propagate-catchable.exp
new file mode 100644
index 0000000000..c3bb809c9f
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-head-propagate-catchable.exp
@@ -0,0 +1 @@
+[ false false ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-head-propagate-catchable.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-head-propagate-catchable.nix
new file mode 100644
index 0000000000..0e69f2f6fc
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-head-propagate-catchable.nix
@@ -0,0 +1,4 @@
+map (e: (builtins.tryEval e).success) [
+  (builtins.head (builtins.throw "a"))
+  (builtins.head [ (builtins.throw "a") ])
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-head.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-head.exp
new file mode 100644
index 0000000000..afe288459f
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-head.exp
@@ -0,0 +1 @@
+[ "foo" 1 ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-head.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-head.nix
new file mode 100644
index 0000000000..1741a7aac4
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-head.nix
@@ -0,0 +1,4 @@
+[
+  (builtins.head [ "foo" ])
+  (builtins.head [ 1 2 3 ])
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-isType-propagate-catchable.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-isType-propagate-catchable.exp
new file mode 100644
index 0000000000..cefd8652b4
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-isType-propagate-catchable.exp
@@ -0,0 +1 @@
+[ false false false false false false false false false ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-isType-propagate-catchable.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-isType-propagate-catchable.nix
new file mode 100644
index 0000000000..28cf2351f6
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-isType-propagate-catchable.nix
@@ -0,0 +1,14 @@
+let
+  isTypeFns = [
+    builtins.isAttrs
+    builtins.isBool
+    builtins.isFloat
+    builtins.isFunction
+    builtins.isInt
+    builtins.isList
+    builtins.isNull
+    builtins.isPath
+    builtins.isString
+  ];
+in
+map (fn: (builtins.tryEval (fn (builtins.throw "is type"))).success) isTypeFns
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-length-catchable.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-length-catchable.exp
new file mode 100644
index 0000000000..c508d5366f
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-length-catchable.exp
@@ -0,0 +1 @@
+false
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-length-catchable.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-length-catchable.nix
new file mode 100644
index 0000000000..037a4911ac
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-length-catchable.nix
@@ -0,0 +1 @@
+(builtins.tryEval (builtins.length (throw "fred"))).success
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-length.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-length.exp
new file mode 100644
index 0000000000..e80eb6ef14
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-length.exp
@@ -0,0 +1 @@
+[ 0 1 3 ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-length.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-length.nix
new file mode 100644
index 0000000000..6af6915f97
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-length.nix
@@ -0,0 +1,5 @@
+[
+  (builtins.length [ ])
+  (builtins.length [ 1 ])
+  (builtins.length [ "one" "two" "three" ])
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-lessThan.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-lessThan.exp
new file mode 100644
index 0000000000..31f4598bb5
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-lessThan.exp
@@ -0,0 +1 @@
+[ true true true true false false false false true true true true false ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-lessThan.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-lessThan.nix
new file mode 100644
index 0000000000..cd2d0c209c
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-lessThan.nix
@@ -0,0 +1,15 @@
+[
+  (builtins.lessThan 2 3)
+  (builtins.lessThan 2.0 3)
+  (builtins.lessThan 2 3.0)
+  (builtins.lessThan 2.0 3.0)
+  (builtins.lessThan 3 2)
+  (builtins.lessThan 3.0 2)
+  (builtins.lessThan 3 2.0)
+  (builtins.lessThan 3.0 2.0)
+  (builtins.lessThan 10 (builtins.add 9 2))
+  (builtins.lessThan (builtins.add 9 1) 11)
+  (builtins.lessThan (builtins.add 9 1) (builtins.add 9 2))
+  (builtins.lessThan "a" "b")
+  (builtins.lessThan "b" "a")
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-list-to-attrs-propagate-catchable.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-list-to-attrs-propagate-catchable.exp
new file mode 100644
index 0000000000..5ee59ced83
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-list-to-attrs-propagate-catchable.exp
@@ -0,0 +1 @@
+[ false true true false false ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-list-to-attrs-propagate-catchable.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-list-to-attrs-propagate-catchable.nix
new file mode 100644
index 0000000000..91b9f889bb
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-list-to-attrs-propagate-catchable.nix
@@ -0,0 +1,7 @@
+map (e: (builtins.tryEval e).success) [
+  (builtins.listToAttrs [{ name = builtins.throw "a"; value = "b"; }])
+  (builtins.listToAttrs [{ name = "a"; value = builtins.throw "b"; }])
+  (builtins.listToAttrs [{ name = "a"; value = "b"; } { name = "c"; value = builtins.throw "d"; }])
+  (builtins.listToAttrs [{ name = "a"; value = "b"; } (builtins.throw "e")])
+  (builtins.listToAttrs (builtins.throw "f"))
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-map-function-strictness.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-map-function-strictness.exp
new file mode 100644
index 0000000000..050c2c4de5
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-map-function-strictness.exp
@@ -0,0 +1 @@
+[ <LAMBDA> 2 "." 18 "https://github.com/NixOS/nix/issues/9779" "-.-" ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-map-function-strictness.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-map-function-strictness.nix
new file mode 100644
index 0000000000..932d3d0eae
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-map-function-strictness.nix
@@ -0,0 +1,8 @@
+let
+  self =
+    let
+      l = builtins.map (builtins.head self) [ 2 "." 18 https://github.com/NixOS/nix/issues/9779 "-.-" ];
+    in
+    [ (x: x) ] ++ l;
+in
+self
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-map-propagate-catchable.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-map-propagate-catchable.exp
new file mode 100644
index 0000000000..652df3d4da
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-map-propagate-catchable.exp
@@ -0,0 +1 @@
+[ true false true ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-map-propagate-catchable.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-map-propagate-catchable.nix
new file mode 100644
index 0000000000..3ebb006c3f
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-map-propagate-catchable.nix
@@ -0,0 +1,5 @@
+map (e: (builtins.tryEval e).success) [
+  (builtins.map (builtins.throw "a") [ "" ])
+  (builtins.map (x: true) (builtins.throw "b"))
+  (builtins.map (_: builtins.throw "x") [ "" ])
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-map.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-map.exp
new file mode 100644
index 0000000000..6cf5304032
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-map.exp
@@ -0,0 +1 @@
+[ [ 1 2 3 4 5 ] [ 2 4 6 8 10 ] [ 2 4 6 8 10 ] [ 2 4 6 8 10 ] [ 1 2 3 4 5 ] ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-map.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-map.nix
new file mode 100644
index 0000000000..71b351fd55
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-map.nix
@@ -0,0 +1,19 @@
+[
+  # identity function
+  (builtins.map (x: x) [ 1 2 3 4 5 ])
+
+  # double stuff
+  (builtins.map (x: x * 2) [ 1 2 3 4 5 ])
+
+  # same but with a closure this time
+  (
+    let n = 2;
+    in builtins.map (x: x * n) [ 1 2 3 4 5 ]
+  )
+
+  # same, but with a builtin
+  (builtins.map (builtins.mul 2) [ 1 2 3 4 5 ])
+
+  # from global scope
+  (map (x: x) [ 1 2 3 4 5 ])
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-mapAttrs-function-strictness.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-mapAttrs-function-strictness.exp
new file mode 100644
index 0000000000..7e70748ffd
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-mapAttrs-function-strictness.exp
@@ -0,0 +1 @@
+{ a = 1; b = 2; f = <LAMBDA>; }
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-mapAttrs-function-strictness.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-mapAttrs-function-strictness.nix
new file mode 100644
index 0000000000..2946d6de17
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-mapAttrs-function-strictness.nix
@@ -0,0 +1,8 @@
+let
+  self =
+    let
+      s = builtins.mapAttrs self.f { a = 1; b = 2; };
+    in
+    { f = _: x: x; } // s;
+in
+self
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-match-propagate-catchables.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-match-propagate-catchables.exp
new file mode 100644
index 0000000000..c508d5366f
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-match-propagate-catchables.exp
@@ -0,0 +1 @@
+false
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-match-propagate-catchables.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-match-propagate-catchables.nix
new file mode 100644
index 0000000000..8d00994b60
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-match-propagate-catchables.nix
@@ -0,0 +1 @@
+(builtins.tryEval (builtins.match (throw "foo") ".*")).success
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-mul.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-mul.exp
new file mode 100644
index 0000000000..e3e0f03a8a
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-mul.exp
@@ -0,0 +1 @@
+[ 36 0 0 14 42 ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-mul.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-mul.nix
new file mode 100644
index 0000000000..2a8d6c4214
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-mul.nix
@@ -0,0 +1,7 @@
+[
+  (builtins.mul 4 9)
+  (builtins.mul 0 7)
+  (builtins.mul 7 0)
+  (builtins.mul 7 2)
+  (builtins.mul (builtins.mul 4 0.5) 21)
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-parse-drv-name-propagate-catchable.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-parse-drv-name-propagate-catchable.exp
new file mode 100644
index 0000000000..c508d5366f
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-parse-drv-name-propagate-catchable.exp
@@ -0,0 +1 @@
+false
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-parse-drv-name-propagate-catchable.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-parse-drv-name-propagate-catchable.nix
new file mode 100644
index 0000000000..c718727e74
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-parse-drv-name-propagate-catchable.nix
@@ -0,0 +1 @@
+(builtins.tryEval (builtins.parseDrvName (builtins.throw "a"))).success
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-partition-propagate-catchable.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-partition-propagate-catchable.exp
new file mode 100644
index 0000000000..48e341f4d8
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-partition-propagate-catchable.exp
@@ -0,0 +1 @@
+[ false false false ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-partition-propagate-catchable.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-partition-propagate-catchable.nix
new file mode 100644
index 0000000000..1960b1790f
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-partition-propagate-catchable.nix
@@ -0,0 +1,5 @@
+map (e: (builtins.tryEval e).success) [
+  (builtins.partition (builtins.throw "a") [ "" ])
+  (builtins.partition (x: true) (builtins.throw "b"))
+  (builtins.partition (_: builtins.throw "x") [ "" ])
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-partition.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-partition.exp
new file mode 100644
index 0000000000..d2390db4f5
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-partition.exp
@@ -0,0 +1 @@
+[ { right = [ 1 2 3 4 5 ]; wrong = [ ]; } { right = [ ]; wrong = [ 1 2 3 4 5 ]; } { right = [ 2 ]; wrong = [ 1 3 4 5 ]; } { right = [ [ 1 2 ] [ 3 4 ] ]; wrong = [ [ 1 ] [ 2 ] [ 3 ] ]; } ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-partition.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-partition.nix
new file mode 100644
index 0000000000..44022a9e0c
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-partition.nix
@@ -0,0 +1,13 @@
+[
+  (builtins.partition (_: true) [ 1 2 3 4 5 ])
+  (builtins.partition (_: false) [ 1 2 3 4 5 ])
+  (builtins.partition (x: x == 2) [ 1 2 3 4 5 ])
+
+  (builtins.partition (x: (builtins.length x) > 1) [
+    [ 1 ]
+    [ 1 2 ]
+    [ 2 ]
+    [ 3 ]
+    [ 3 4 ]
+  ])
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-remove-attrs-propagate-catchable.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-remove-attrs-propagate-catchable.exp
new file mode 100644
index 0000000000..1e950b5aa2
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-remove-attrs-propagate-catchable.exp
@@ -0,0 +1 @@
+[ false false true false ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-remove-attrs-propagate-catchable.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-remove-attrs-propagate-catchable.nix
new file mode 100644
index 0000000000..8ca8a414aa
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-remove-attrs-propagate-catchable.nix
@@ -0,0 +1,6 @@
+map (e: (builtins.tryEval e).success) [
+  (builtins.removeAttrs (builtins.throw "a") [ "a" ])
+  (builtins.removeAttrs { a = { }; } (builtins.throw "b"))
+  (builtins.removeAttrs { a = builtins.throw "b"; } [ "a" ])
+  (builtins.removeAttrs { "${builtins.throw "c"}" = "b"; } [ "c" ])
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-replace-strings-propagate-catchable.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-replace-strings-propagate-catchable.exp
new file mode 100644
index 0000000000..cefd8652b4
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-replace-strings-propagate-catchable.exp
@@ -0,0 +1 @@
+[ false false false false false false false false false ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-replace-strings-propagate-catchable.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-replace-strings-propagate-catchable.nix
new file mode 100644
index 0000000000..ad9734ba9a
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-replace-strings-propagate-catchable.nix
@@ -0,0 +1,16 @@
+map (e: (builtins.tryEval e).success) [
+  # This one may be hard to read for non-experts.
+  # Replace strings is a special built-in compared to others in the sense
+  # it might attempt to lazily evaluate things upon successful replacements,
+  # so it would not be surprising that some of the non-replacements which could throw
+  # could be ignored by laziness. It is not the case though.
+  (builtins.replaceStrings [ "a" (builtins.throw "b") ] [ "c" "d" ] "ab")
+  (builtins.replaceStrings [ "a" (builtins.throw "b") ] [ "c" "d" ] "a")
+  (builtins.replaceStrings [ "a" "b" ] [ "c" (builtins.throw "d") ] "a")
+  (builtins.replaceStrings [ "a" "b" ] [ "c" (builtins.throw "d") ] "ab")
+  (builtins.replaceStrings [ "" ] [ (builtins.throw "d") ] "ab")
+  (builtins.replaceStrings [ "a" "" ] [ "b" (builtins.throw "d") ] "ab")
+  (builtins.replaceStrings (builtins.throw "z") [ ] "ab")
+  (builtins.replaceStrings [ ] (builtins.throw "z") "ab")
+  (builtins.replaceStrings [ ] [ ] (builtins.throw "z"))
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-replaceStrings.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-replaceStrings.exp
new file mode 100644
index 0000000000..9f20496c7a
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-replaceStrings.exp
@@ -0,0 +1 @@
+[ "fabir" "a" "1a1" "ABC" ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-replaceStrings.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-replaceStrings.nix
new file mode 100644
index 0000000000..eea3f87a2f
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-replaceStrings.nix
@@ -0,0 +1,6 @@
+[
+  (builtins.replaceStrings [ "oo" "a" ] [ "a" "i" ] "foobar")
+  (builtins.replaceStrings [ "o" ] [ "a" ] "a")
+  (builtins.replaceStrings [ "" "" ] [ "1" "2" ] "a")
+  (builtins.replaceStrings [ "a" "b" "c" ] [ "A" "B" "C" ] "abc")
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-sort-propagate-catchable.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-sort-propagate-catchable.exp
new file mode 100644
index 0000000000..1e950b5aa2
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-sort-propagate-catchable.exp
@@ -0,0 +1 @@
+[ false false true false ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-sort-propagate-catchable.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-sort-propagate-catchable.nix
new file mode 100644
index 0000000000..66ca4b98ed
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-sort-propagate-catchable.nix
@@ -0,0 +1,6 @@
+map (e: (builtins.tryEval e).success) [
+  (builtins.sort (builtins.throw "a") [ "" ])
+  (builtins.sort (x: y: true) (builtins.throw "b"))
+  (builtins.sort (_: _: builtins.throw "x") [ "" ])
+  (builtins.sort (_: _: builtins.throw "x") [ "" "" ])
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-split-propagate-catchable.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-split-propagate-catchable.exp
new file mode 100644
index 0000000000..c3bb809c9f
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-split-propagate-catchable.exp
@@ -0,0 +1 @@
+[ false false ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-split-propagate-catchable.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-split-propagate-catchable.nix
new file mode 100644
index 0000000000..1c86e9d6f8
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-split-propagate-catchable.nix
@@ -0,0 +1,4 @@
+map (e: (builtins.tryEval e).success) [
+  (builtins.split (builtins.throw "regex") "abc")
+  (builtins.split "[^/]" (builtins.throw "string"))
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-splitVersion.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-splitVersion.exp
new file mode 100644
index 0000000000..222a0093f5
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-splitVersion.exp
@@ -0,0 +1 @@
+[ [ "1" "2" "3" ] [ "2" "3" "16" ] [ "22" "11" "pre" "408963" "823" "e" "2" "c" "9" "b" "0" "a" "0" ] [ "9" "4" "1" "rc" "1" ] [ "9" "4" "0" "20220721" ] [ "0" "1" "alpha" ] [ "unstable" "2022" "09" "20" ] [ "30" "pre" "9" ] [ "0" "pre+date=" "2021" "11" "30" ] [ "1" "2" "0" "_pre" "23" ] [ "0" "1" "0" "pre" "71" "_" "170" "f" "840" ] ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-splitVersion.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-splitVersion.nix
new file mode 100644
index 0000000000..4083e86714
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-splitVersion.nix
@@ -0,0 +1,13 @@
+[
+  (builtins.splitVersion "1.2.3")
+  (builtins.splitVersion "2.3.16")
+  (builtins.splitVersion "22.11pre408963.823e2c9b0a0")
+  (builtins.splitVersion "9.4.1-rc1")
+  (builtins.splitVersion "9.4.0.20220721")
+  (builtins.splitVersion "0.1-alpha")
+  (builtins.splitVersion "unstable-2022-09-20")
+  (builtins.splitVersion "30.pre9")
+  (builtins.splitVersion "0.pre+date=2021-11-30")
+  (builtins.splitVersion "1.2.0_pre23")
+  (builtins.splitVersion "0.1.0pre71_170f840")
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-splitversion-catchable.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-splitversion-catchable.exp
new file mode 100644
index 0000000000..c508d5366f
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-splitversion-catchable.exp
@@ -0,0 +1 @@
+false
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-splitversion-catchable.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-splitversion-catchable.nix
new file mode 100644
index 0000000000..0668ec7de2
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-splitversion-catchable.nix
@@ -0,0 +1 @@
+(builtins.tryEval (builtins.splitVersion (throw "fred"))).success
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-string-length-propagate-catchable.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-string-length-propagate-catchable.exp
new file mode 100644
index 0000000000..d181ebcff6
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-string-length-propagate-catchable.exp
@@ -0,0 +1 @@
+[ false ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-string-length-propagate-catchable.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-string-length-propagate-catchable.nix
new file mode 100644
index 0000000000..0904acd114
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-string-length-propagate-catchable.nix
@@ -0,0 +1,4 @@
+map (e: (builtins.tryEval e).success) [
+  (builtins.stringLength (builtins.throw "a"))
+  # FIXME(raitobezarius): test coercions too.
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-string-length.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-string-length.exp
new file mode 100644
index 0000000000..b019be4bfd
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-string-length.exp
@@ -0,0 +1 @@
+[ 3 "hello" 9 4 ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-string-length.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-string-length.nix
new file mode 100644
index 0000000000..b7d51db3c5
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-string-length.nix
@@ -0,0 +1,10 @@
+[
+  (builtins.stringLength "foo")
+  (let s = "hello"; in (builtins.substring 0 (builtins.stringLength s) s))
+  (builtins.stringLength ("foo" + "${"bar" + "baz"}"))
+
+  # feel free to delete this test case at any time, it's just to show: This is a
+  # thing at the moment. We may want to break compatibility with this aspect of
+  # the C++ Nix implementation at any time.
+  (builtins.stringLength "😀")
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-sub.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-sub.exp
new file mode 100644
index 0000000000..51842eccfa
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-sub.exp
@@ -0,0 +1 @@
+[ -4 -3.1 -4.9 -4.7 -4 42 ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-sub.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-sub.nix
new file mode 100644
index 0000000000..2929c4dddd
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-sub.nix
@@ -0,0 +1,8 @@
+[
+  (builtins.sub 7 11)
+  (builtins.sub 7.9 11)
+  (builtins.sub 7 11.9)
+  (builtins.sub 7.2 11.9)
+  (builtins.sub 7.9 11.9)
+  (builtins.sub (builtins.sub 123 23) 58)
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-substring-coerce.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-substring-coerce.exp
new file mode 100644
index 0000000000..192548e949
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-substring-coerce.exp
@@ -0,0 +1 @@
+"42"
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-substring-coerce.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-substring-coerce.nix
new file mode 100644
index 0000000000..626ae1d1be
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-substring-coerce.nix
@@ -0,0 +1,5 @@
+# builtins.substring uses string coercion internally
+
+builtins.substring 0 2 {
+  __toString = _: "4200";
+}
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-substring-negative-length.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-substring-negative-length.exp
new file mode 100644
index 0000000000..e614d49940
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-substring-negative-length.exp
@@ -0,0 +1 @@
+[ "SIP dial" "Lounge" " Lounge" ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-substring-negative-length.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-substring-negative-length.nix
new file mode 100644
index 0000000000..062e2c0581
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-substring-negative-length.nix
@@ -0,0 +1,5 @@
+[
+  (builtins.substring 0 (-1) "SIP dial")
+  (builtins.substring 13 (-1) "Nichtraucher Lounge")
+  (builtins.substring 12 (-2) "Nichtraucher Lounge")
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-substring.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-substring.exp
new file mode 100644
index 0000000000..1682760228
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-substring.exp
@@ -0,0 +1 @@
+[ "tes" "testing" "" "estin" "ting" "" "" "" "" "est" "est" "est" "est" "est" "est" "" ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-substring.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-substring.nix
new file mode 100644
index 0000000000..f4ee82e273
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-substring.nix
@@ -0,0 +1,18 @@
+[
+  (builtins.substring 0 3 "testing")
+  (builtins.substring 0 300 "testing")
+  (builtins.substring 3 0 "testing")
+  (builtins.substring 1 5 "testing")
+  (builtins.substring 3 5 "testing")
+  (builtins.substring 300 300 "testing")
+  (builtins.substring 301 300 "testing")
+  (builtins.substring 0 0 "")
+  (builtins.substring 0 1 "")
+  (builtins.substring (builtins.add 0 1) 3 "testing")
+  (builtins.substring 1 (builtins.add 3 0) "testing")
+  (builtins.substring (builtins.add 0 1) (builtins.add 3 0) "testing")
+  (builtins.substring (builtins.add 0 1) (builtins.add 3 0) "testing")
+  (builtins.substring (builtins.add 0 1) (builtins.add 3 0) ("test" + "ing"))
+  (builtins.substring (builtins.add 0 1) (builtins.add 3 0) ("test" + "ing"))
+  (builtins.substring 300 (-10) "testing")
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-tail-propagate-catchable.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-tail-propagate-catchable.exp
new file mode 100644
index 0000000000..d4cd584d22
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-tail-propagate-catchable.exp
@@ -0,0 +1 @@
+[ false true true true ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-tail-propagate-catchable.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-tail-propagate-catchable.nix
new file mode 100644
index 0000000000..b4e461e12b
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-tail-propagate-catchable.nix
@@ -0,0 +1,6 @@
+map (e: (builtins.tryEval e).success) [
+  (builtins.tail (builtins.throw "a"))
+  (builtins.tail [ (builtins.throw "a") ])
+  (builtins.tail [ (builtins.throw "a") "a" ])
+  (builtins.tail [ (builtins.throw "a") (builtins.throw "a") ])
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-tail.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-tail.exp
new file mode 100644
index 0000000000..b9e3aa1ef7
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-tail.exp
@@ -0,0 +1 @@
+[ [ ] [ 2 3 ] ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-tail.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-tail.nix
new file mode 100644
index 0000000000..2be9496a98
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-tail.nix
@@ -0,0 +1,4 @@
+[
+  (builtins.tail [ "foo" ])
+  (builtins.tail [ 1 2 3 ])
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-thunked-function-calls.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-thunked-function-calls.exp
new file mode 100644
index 0000000000..3d4204d5a8
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-thunked-function-calls.exp
@@ -0,0 +1 @@
+[ 2 [ "Hans" "James" "Joachim" ] 2 [ "Clawdia" "Mynheer" ] 981 3 2 2 ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-thunked-function-calls.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-thunked-function-calls.nix
new file mode 100644
index 0000000000..3a1c0b9821
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-thunked-function-calls.nix
@@ -0,0 +1,31 @@
+[
+  # This is independent of builtins
+  (builtins.length [ (builtins.throw "Ferge") (builtins.throw "Wehsal") ])
+  (builtins.attrNames {
+    Hans = throw "Castorp";
+    Joachim = throw "Ziemßen";
+    James = "Tienappel";
+  })
+
+  (builtins.length (builtins.map builtins.throw [ "Settembrini" "Naphta" ]))
+
+  (builtins.attrNames (builtins.mapAttrs builtins.throw {
+    Clawdia = "Chauchat";
+    Mynheer = "Peeperkorn";
+  }))
+
+  (builtins.length (builtins.genList (builtins.add "Marusja") 981))
+  (builtins.length (builtins.genList builtins.throw 3))
+
+  # These are hard to get wrong since the outer layer needs to be forced anyways
+  (builtins.length (builtins.genericClosure {
+    startSet = [
+      { key = 1; initial = true; }
+    ];
+    operator = { key, initial, ... }:
+      if initial
+      then [{ key = key - 1; initial = false; value = throw "lol"; }]
+      else [ ];
+  }))
+  (builtins.length (builtins.concatMap (m: [ m (builtins.throw m) ]) [ "Marusja" ]))
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-to-json-propagate-catchable.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-to-json-propagate-catchable.exp
new file mode 100644
index 0000000000..ca00e3c049
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-to-json-propagate-catchable.exp
@@ -0,0 +1 @@
+[ false false false false ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-to-json-propagate-catchable.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-to-json-propagate-catchable.nix
new file mode 100644
index 0000000000..8ae5e48e97
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-to-json-propagate-catchable.nix
@@ -0,0 +1,14 @@
+map (e: (builtins.tryEval (builtins.toJSON e)).success) [
+  (builtins.throw "a")
+  {
+    a = builtins.throw "attribute a";
+  }
+  {
+    a.b.c.d.e.f.g.h.i = builtins.throw "deep i";
+  }
+  {
+    x = 32;
+    y = builtins.throw "second argument";
+  }
+  # FIXME(raitobezarius): we would like to test coercions, i.e. `toFile` and `derivation` containing throwables.
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-to-path-propagate-catchable.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-to-path-propagate-catchable.exp
new file mode 100644
index 0000000000..c3bb809c9f
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-to-path-propagate-catchable.exp
@@ -0,0 +1 @@
+[ false false ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-to-path-propagate-catchable.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-to-path-propagate-catchable.nix
new file mode 100644
index 0000000000..808fb8c46e
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-to-path-propagate-catchable.nix
@@ -0,0 +1,5 @@
+map (e: (builtins.tryEval (builtins.toPath e)).success) [
+  (builtins.throw "a")
+  (./xyz + (builtins.throw "p"))
+  # FIXME: test derivations and files.
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-to-string-propagate-catchable.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-to-string-propagate-catchable.exp
new file mode 100644
index 0000000000..ca00e3c049
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-to-string-propagate-catchable.exp
@@ -0,0 +1 @@
+[ false false false false ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-to-string-propagate-catchable.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-to-string-propagate-catchable.nix
new file mode 100644
index 0000000000..7b19ff53c3
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-to-string-propagate-catchable.nix
@@ -0,0 +1,7 @@
+map (e: (builtins.tryEval (builtins.toString e)).success) [
+  (builtins.throw "a")
+  [ (builtins.throw "a") ]
+  [ "abc" (builtins.throw "a") ]
+  "abc${builtins.throw "c"}"
+  # FIXME: test derivations and files.
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-to-xml-propagate-catchable.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-to-xml-propagate-catchable.exp
new file mode 100644
index 0000000000..366f7adf0d
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-to-xml-propagate-catchable.exp
@@ -0,0 +1 @@
+[ false false false false true false false ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-to-xml-propagate-catchable.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-to-xml-propagate-catchable.nix
new file mode 100644
index 0000000000..80d9e688be
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-to-xml-propagate-catchable.nix
@@ -0,0 +1,15 @@
+map (e: (builtins.tryEval (builtins.toXML e)).success) [
+  (builtins.throw "a")
+  [ (builtins.throw "a") ]
+  [ "abc" (builtins.throw "a") ]
+  "abc${builtins.throw "c"}"
+  (_: builtins.throw "d")
+  {
+    u = builtins.throw "x";
+    v = "a";
+  }
+  {
+    u.i.w.x.z = builtins.throw "n";
+  }
+  # FIXME: test derivations and files.
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-toString.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-toString.exp
new file mode 100644
index 0000000000..cd5a6c0d54
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-toString.exp
@@ -0,0 +1 @@
+[ "" " " " /deep/thought" " 2  3" " flat" "1" "4.200000" "" "" "1" "foo" "/etc" "Hello World" "Hello World" "1" "out" "2" "    /deep/thought  2  3  flat 1 4.200000   1 foo /etc Hello World Hello World 1 out 2" ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-toString.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-toString.nix
new file mode 100644
index 0000000000..eb8011158f
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-toString.nix
@@ -0,0 +1,28 @@
+let
+  toStringableSet = {
+    __toString = self: self.content;
+    content = "Hello World";
+  };
+
+  toStringExamples = [
+    null
+    [ null false ]
+    [ null /deep/thought ]
+    [ [ null 2 ] null 3 ]
+    [ false "flat" ]
+    1
+    4.2
+    null
+    false
+    true
+    "foo"
+    /etc
+    toStringableSet
+    { __toString = _: toStringableSet; }
+    { __toString = _: true; }
+    { outPath = "out"; }
+    { outPath = { outPath = { __toString = _: 2; }; }; }
+  ];
+in
+
+(builtins.map toString toStringExamples) ++ [ (toString toStringExamples) ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-tojson-literals.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-tojson-literals.exp
new file mode 100644
index 0000000000..0a274c201f
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-tojson-literals.exp
@@ -0,0 +1 @@
+"[42,\"hello\",13.37,[],[1,2,3],{},{\"name\":\"foo\",\"value\":42},{\"foo\":42}]"
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-tojson-literals.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-tojson-literals.nix
new file mode 100644
index 0000000000..12e8c03b17
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-tojson-literals.nix
@@ -0,0 +1,11 @@
+# tests serialisation of literal data
+builtins.toJSON [
+  42
+  "hello"
+  13.37
+  [ ]
+  [ 1 2 3 ]
+  { }
+  { name = "foo"; value = 42; }
+  { foo = 42; }
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-tojson-outpath-nested.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-tojson-outpath-nested.exp
new file mode 100644
index 0000000000..69667de5a1
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-tojson-outpath-nested.exp
@@ -0,0 +1 @@
+"{\"a\":40,\"b\":2}"
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-tojson-outpath-nested.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-tojson-outpath-nested.nix
new file mode 100644
index 0000000000..70755c8c6d
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-tojson-outpath-nested.nix
@@ -0,0 +1,8 @@
+# Attribute sets with an `outPath` can contain _any_ serialisable
+# value in that field.
+builtins.toJSON {
+  outPath = {
+    a = 40;
+    b = 2;
+  };
+}
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-tojson-outpath.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-tojson-outpath.exp
new file mode 100644
index 0000000000..82dd081798
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-tojson-outpath.exp
@@ -0,0 +1 @@
+"\"/nix/store/jzka5ndnygkkfjfvpqwjipqp75lhz138-emacs-28.2\""
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-tojson-outpath.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-tojson-outpath.nix
new file mode 100644
index 0000000000..7f9d95ac60
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-tojson-outpath.nix
@@ -0,0 +1,5 @@
+# Attribute sets with an `outPath` have that outPath itself serialised
+# to string.
+builtins.toJSON {
+  outPath = "/nix/store/jzka5ndnygkkfjfvpqwjipqp75lhz138-emacs-28.2";
+}
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-tojson-thunks.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-tojson-thunks.exp
new file mode 100644
index 0000000000..9ccd94224b
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-tojson-thunks.exp
@@ -0,0 +1 @@
+"[42,42,\"42\"]"
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-tojson-thunks.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-tojson-thunks.nix
new file mode 100644
index 0000000000..16234ab451
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-tojson-thunks.nix
@@ -0,0 +1,9 @@
+let
+  a = b * 2;
+  b = 21;
+in
+builtins.toJSON [
+  a
+  ((n: n * 2) 21)
+  (builtins.toJSON a)
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-tojson-tostring.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-tojson-tostring.exp
new file mode 100644
index 0000000000..2661fd257b
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-tojson-tostring.exp
@@ -0,0 +1 @@
+"\"it's 42\""
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-tojson-tostring.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-tojson-tostring.nix
new file mode 100644
index 0000000000..ec6f8d947c
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-tojson-tostring.nix
@@ -0,0 +1,8 @@
+# Attribute sets with a `__toString` attribute JSON-serialise with a
+# string coercion of the function call result.
+
+builtins.toJSON {
+  __toString = self: "it's " + (builtins.toString (self.x * self.y));
+  x = 21;
+  y = 2;
+}
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-type-of-propagate-catchable.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-type-of-propagate-catchable.exp
new file mode 100644
index 0000000000..41f22d3ee4
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-type-of-propagate-catchable.exp
@@ -0,0 +1 @@
+[ false true true false ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-type-of-propagate-catchable.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-type-of-propagate-catchable.nix
new file mode 100644
index 0000000000..4b70609bbf
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-type-of-propagate-catchable.nix
@@ -0,0 +1,9 @@
+map (e: (builtins.tryEval (builtins.typeOf e)).success) [
+  (builtins.throw "a")
+  {
+    a = builtins.throw "b";
+  }
+  [ (builtins.throw "c") ]
+  (./xyz + (builtins.throw "p"))
+  # FIXME: test derivations and files.
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-type-of.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-type-of.exp
new file mode 100644
index 0000000000..1ea054fc2d
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-type-of.exp
@@ -0,0 +1 @@
+[ "null" "bool" "bool" "int" "int" "float" "string" "string" "set" "set" "list" "lambda" "path" ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-type-of.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-type-of.nix
new file mode 100644
index 0000000000..fa42c6008e
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-type-of.nix
@@ -0,0 +1,22 @@
+let
+  fix = f: let x = f x; in x;
+in
+
+fix (self:
+[
+  (builtins.typeOf null)
+  (builtins.typeOf true)
+  (builtins.typeOf (true && false))
+  (builtins.typeOf 12)
+  (builtins.typeOf (builtins.add 21 21))
+  (builtins.typeOf 1.2)
+  (builtins.typeOf "foo")
+  (builtins.typeOf "${"foo" + "bar"}baz")
+  (builtins.typeOf { })
+  # (builtins.typeOf { foo.bar = 32; }.foo) # TODO: re-enable when nested keys are done
+  (builtins.typeOf ({ name = "foo"; value = 13; } // { name = "bar"; }))
+  (builtins.typeOf self)
+  (builtins.typeOf fix)
+  (builtins.typeOf /nix/store)
+]
+)
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-type-predicates.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-type-predicates.exp
new file mode 100644
index 0000000000..724c1f9c34
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-type-predicates.exp
@@ -0,0 +1 @@
+[ true true false true true false true true false true true false true true false true true false true true false true true false true true true false ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-type-predicates.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-type-predicates.nix
new file mode 100644
index 0000000000..e67b219159
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-type-predicates.nix
@@ -0,0 +1,34 @@
+let
+  # apply is thunked, so we can create a thunked value using the identity function
+  thunk = x: x;
+in
+[
+  (builtins.isAttrs { bar = throw "baz"; })
+  (builtins.isAttrs (thunk { foo = 13; }))
+  (builtins.isAttrs (thunk 123))
+  (builtins.isBool true)
+  (builtins.isBool (thunk false))
+  (builtins.isBool (thunk "lol"))
+  (builtins.isFloat 1.2)
+  (builtins.isFloat (thunk (1 * 1.0)))
+  (builtins.isFloat 1)
+  (builtins.isFunction thunk)
+  (builtins.isFunction (thunk thunk))
+  (builtins.isFunction { })
+  (builtins.isInt 1)
+  (builtins.isInt (thunk 42))
+  (builtins.isInt 1.0)
+  (builtins.isList [ (throw "oh no") (abort "it's over") ])
+  (builtins.isList (thunk [ 21 21 ]))
+  (builtins.isList (thunk { }))
+  (builtins.isNull null)
+  (builtins.isNull (thunk null))
+  (builtins.isNull 42)
+  (builtins.isPath ./relative)
+  (builtins.isPath (thunk /absolute))
+  (builtins.isPath "/not/a/path")
+  (builtins.isString "simple")
+  (builtins.isString "${{ outPath = "coerced"; }}")
+  (builtins.isString "hello ${"interpolation"}")
+  (builtins.isString true)
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-unsafe-discard-string-context-propagate-catchable.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-unsafe-discard-string-context-propagate-catchable.exp
new file mode 100644
index 0000000000..d181ebcff6
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-unsafe-discard-string-context-propagate-catchable.exp
@@ -0,0 +1 @@
+[ false ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-unsafe-discard-string-context-propagate-catchable.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-unsafe-discard-string-context-propagate-catchable.nix
new file mode 100644
index 0000000000..8ef5f35a17
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-unsafe-discard-string-context-propagate-catchable.nix
@@ -0,0 +1,4 @@
+map (e: (builtins.tryEval (builtins.unsafeDiscardStringContext e)).success) [
+  (builtins.throw "a")
+  # FIXME: test derivations with throwables.
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-catchable-double-throw.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-catchable-double-throw.exp
new file mode 100644
index 0000000000..c508d5366f
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-catchable-double-throw.exp
@@ -0,0 +1 @@
+false
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-catchable-double-throw.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-catchable-double-throw.nix
new file mode 100644
index 0000000000..7c2132b502
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-catchable-double-throw.nix
@@ -0,0 +1 @@
+(builtins.tryEval (builtins.throw (builtins.throw "a"))).success
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-catchable-in-attrNames.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-catchable-in-attrNames.exp
new file mode 100644
index 0000000000..c508d5366f
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-catchable-in-attrNames.exp
@@ -0,0 +1 @@
+false
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-catchable-in-attrNames.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-catchable-in-attrNames.nix
new file mode 100644
index 0000000000..75531d56a3
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-catchable-in-attrNames.nix
@@ -0,0 +1 @@
+(builtins.tryEval (builtins.attrNames (throw "fred"))).success
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-catchable-in-inequality.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-catchable-in-inequality.exp
new file mode 100644
index 0000000000..c508d5366f
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-catchable-in-inequality.exp
@@ -0,0 +1 @@
+false
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-catchable-in-inequality.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-catchable-in-inequality.nix
new file mode 100644
index 0000000000..93836bd8fe
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-catchable-in-inequality.nix
@@ -0,0 +1 @@
+(builtins.tryEval (throw "bob" != 3)).success
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-catchable-in-intersectattrs.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-catchable-in-intersectattrs.exp
new file mode 100644
index 0000000000..c508d5366f
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-catchable-in-intersectattrs.exp
@@ -0,0 +1 @@
+false
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-catchable-in-intersectattrs.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-catchable-in-intersectattrs.nix
new file mode 100644
index 0000000000..a06a383342
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-catchable-in-intersectattrs.nix
@@ -0,0 +1 @@
+(builtins.tryEval (builtins.intersectAttrs (throw "fred") { })).success
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-catchable-in-string-interpolation.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-catchable-in-string-interpolation.exp
new file mode 100644
index 0000000000..c508d5366f
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-catchable-in-string-interpolation.exp
@@ -0,0 +1 @@
+false
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-catchable-in-string-interpolation.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-catchable-in-string-interpolation.nix
new file mode 100644
index 0000000000..7a0cf16709
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-catchable-in-string-interpolation.nix
@@ -0,0 +1 @@
+(builtins.tryEval ("${toString 3}  ${throw "bob"}")).success
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-catchable-in-update-attrs.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-catchable-in-update-attrs.exp
new file mode 100644
index 0000000000..c508d5366f
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-catchable-in-update-attrs.exp
@@ -0,0 +1 @@
+false
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-catchable-in-update-attrs.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-catchable-in-update-attrs.nix
new file mode 100644
index 0000000000..38a9169034
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-catchable-in-update-attrs.nix
@@ -0,0 +1 @@
+(builtins.tryEval (throw "bob" // { })).success
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-catchable-passed-to-function-with-formals.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-catchable-passed-to-function-with-formals.exp
new file mode 100644
index 0000000000..c508d5366f
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-catchable-passed-to-function-with-formals.exp
@@ -0,0 +1 @@
+false
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-catchable-passed-to-function-with-formals.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-catchable-passed-to-function-with-formals.nix
new file mode 100644
index 0000000000..df6726db76
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-catchable-passed-to-function-with-formals.nix
@@ -0,0 +1 @@
+(builtins.tryEval (({ fred }: "bob") (throw "3"))).success
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-ceil.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-ceil.exp
new file mode 100644
index 0000000000..dffbbe59f0
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-ceil.exp
@@ -0,0 +1 @@
+[ 4 ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-ceil.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-ceil.nix
new file mode 100644
index 0000000000..5835bf829b
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-ceil.nix
@@ -0,0 +1 @@
+[ (builtins.ceil 3.4) ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-closure-pointer-compare.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-closure-pointer-compare.exp
new file mode 100644
index 0000000000..c3bb809c9f
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-closure-pointer-compare.exp
@@ -0,0 +1 @@
+[ false false ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-closure-pointer-compare.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-closure-pointer-compare.nix
new file mode 100644
index 0000000000..639191be5d
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-closure-pointer-compare.nix
@@ -0,0 +1,14 @@
+# For an explanation of this behavior see //tvix/docs/value-pointer-equality.md
+let
+  g = x:
+    owo: "th" + x;
+in
+[
+  (
+    { q = g "ia"; } == { q = g ("i" + "a"); }
+  )
+
+  (
+    [ (g "ia") ] == [ (g ("i" + "a")) ]
+  )
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-closure-self.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-closure-self.exp
new file mode 100644
index 0000000000..be54b4b4e3
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-closure-self.exp
@@ -0,0 +1 @@
+"done"
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-closure-self.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-closure-self.nix
new file mode 100644
index 0000000000..7be6660009
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-closure-self.nix
@@ -0,0 +1,5 @@
+let
+  # self-recursive function should be able to close over itself
+  f = n: if n <= 0 then "done" else f (n - 1);
+in
+f 10
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-closure-with-shadowing.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-closure-with-shadowing.exp
new file mode 100644
index 0000000000..d00491fd7e
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-closure-with-shadowing.exp
@@ -0,0 +1 @@
+1
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-closure-with-shadowing.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-closure-with-shadowing.nix
new file mode 100644
index 0000000000..2c4de65e76
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-closure-with-shadowing.nix
@@ -0,0 +1,14 @@
+# If a closure closes over a variable that is statically known *and*
+# available dynamically through `with`, the statically known one must
+# have precedence.
+
+let
+  # introduce statically known `a` (this should be the result)
+  a = 1;
+in
+
+# introduce some closure depth to force both kinds of upvalue
+  # resolution, and introduce a dynamically known `a` within the
+  # closures
+let f = b: with { a = 2; }; c: a + b + c;
+in f 0 0
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-float-false.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-float-false.exp
new file mode 100644
index 0000000000..95a0e7378b
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-float-false.exp
@@ -0,0 +1 @@
+{ eq = false; ge = false; gt = false; le = false; lt = false; ne = false; }
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-float-false.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-float-false.nix
new file mode 100644
index 0000000000..2b511f56ee
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-float-false.nix
@@ -0,0 +1,8 @@
+{
+  eq = 6.9 == 4.2;
+  ne = 4.2 != 4.2;
+  lt = 2.5 < 1.5;
+  le = 2.5 <= 1.5;
+  gt = 1.5 > 2.5;
+  ge = 1.5 >= 2.5;
+}
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-float-true.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-float-true.exp
new file mode 100644
index 0000000000..9160829dde
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-float-true.exp
@@ -0,0 +1 @@
+{ eq = true; ge = true; gt = true; le = true; lt = true; ne = true; }
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-float-true.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-float-true.nix
new file mode 100644
index 0000000000..c505a85b1f
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-float-true.nix
@@ -0,0 +1,8 @@
+{
+  eq = 4.2 == 4.2;
+  ne = 6.9 != 4.2;
+  lt = 1.5 < 2.5;
+  le = 2.5 <= 2.5;
+  gt = 2.3 > 1.2;
+  ge = 2.3 >= 2.3;
+}
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-int-false.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-int-false.exp
new file mode 100644
index 0000000000..95a0e7378b
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-int-false.exp
@@ -0,0 +1 @@
+{ eq = false; ge = false; gt = false; le = false; lt = false; ne = false; }
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-int-false.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-int-false.nix
new file mode 100644
index 0000000000..7d6b30419f
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-int-false.nix
@@ -0,0 +1,8 @@
+{
+  eq = 69 == 42;
+  ne = 42 != 42;
+  lt = 2 < 1;
+  le = 2 <= 1;
+  gt = 1 > 2;
+  ge = 1 >= 2;
+}
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-int-true.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-int-true.exp
new file mode 100644
index 0000000000..9160829dde
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-int-true.exp
@@ -0,0 +1 @@
+{ eq = true; ge = true; gt = true; le = true; lt = true; ne = true; }
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-int-true.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-int-true.nix
new file mode 100644
index 0000000000..0bf474e53f
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-int-true.nix
@@ -0,0 +1,8 @@
+{
+  eq = 42 == 42;
+  ne = 69 != 42;
+  lt = 1 < 2;
+  le = 2 <= 2;
+  gt = 2 > 1;
+  ge = 2 >= 2;
+}
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-num-false.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-num-false.exp
new file mode 100644
index 0000000000..95a0e7378b
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-num-false.exp
@@ -0,0 +1 @@
+{ eq = false; ge = false; gt = false; le = false; lt = false; ne = false; }
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-num-false.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-num-false.nix
new file mode 100644
index 0000000000..61b206c033
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-num-false.nix
@@ -0,0 +1,8 @@
+{
+  eq = 6.9 == 4;
+  ne = 4.0 != 4;
+  lt = 2.5 < 1;
+  le = 2 <= 1.5;
+  gt = 1 > 1.1;
+  ge = 1.5 >= 2;
+}
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-num-true.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-num-true.exp
new file mode 100644
index 0000000000..9160829dde
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-num-true.exp
@@ -0,0 +1 @@
+{ eq = true; ge = true; gt = true; le = true; lt = true; ne = true; }
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-num-true.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-num-true.nix
new file mode 100644
index 0000000000..ad77074710
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-num-true.nix
@@ -0,0 +1,8 @@
+{
+  eq = 42.0 == 42;
+  ne = 6.9 != 4;
+  lt = 1.5 < 2;
+  le = 2.0 <= 2.0;
+  gt = 1.1 > 1;
+  ge = 2.3 >= 2.3;
+}
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-str-false.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-str-false.exp
new file mode 100644
index 0000000000..95a0e7378b
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-str-false.exp
@@ -0,0 +1 @@
+{ eq = false; ge = false; gt = false; le = false; lt = false; ne = false; }
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-str-false.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-str-false.nix
new file mode 100644
index 0000000000..b5773a21d3
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-str-false.nix
@@ -0,0 +1,8 @@
+{
+  eq = "test" == "not test";
+  ne = "test" != "test";
+  lt = "bcd" < "abc";
+  le = "bcd" <= "abc";
+  gt = "abc" > "bcd";
+  ge = "abc" >= "bcd";
+}
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-str-true.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-str-true.exp
new file mode 100644
index 0000000000..9160829dde
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-str-true.exp
@@ -0,0 +1 @@
+{ eq = true; ge = true; gt = true; le = true; lt = true; ne = true; }
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-str-true.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-str-true.nix
new file mode 100644
index 0000000000..172d2237e9
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-cmp-str-true.nix
@@ -0,0 +1,8 @@
+{
+  eq = "test" == "test";
+  ne = "test" != "not test";
+  lt = "abc" < "bcd";
+  le = "bcd" <= "bcd";
+  gt = "bcd" > "abc";
+  ge = "bcd" >= "bcd";
+}
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-coerce-opadd.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-coerce-opadd.exp
new file mode 100644
index 0000000000..d874518a37
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-coerce-opadd.exp
@@ -0,0 +1 @@
+[ "lordnikon" "zerocool" /tmp/31337h4x0r "fooblah" "blahfoo" ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-coerce-opadd.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-coerce-opadd.nix
new file mode 100644
index 0000000000..e79e521f8a
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-coerce-opadd.nix
@@ -0,0 +1,7 @@
+[
+  ({ __toString = _: "lord"; } + "nikon")
+  ("zero" + { __toString = _: "cool"; })
+  (/tmp/31337 + "h4x0r")
+  ("foo" + { outPath = "blah"; })
+  ({ outPath = "blah"; } + "foo")
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-compare-lists.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-compare-lists.exp
new file mode 100644
index 0000000000..3b7fd39819
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-compare-lists.exp
@@ -0,0 +1 @@
+[ false true true true false true false false false true false false false true true ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-compare-lists.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-compare-lists.nix
new file mode 100644
index 0000000000..1837f4d820
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-compare-lists.nix
@@ -0,0 +1,17 @@
+[
+  ([ 1 2 ] < [ 1 ])
+  ([ 1 2 ] < [ 2 3 ])
+  ([ 1 2 ] < [ 2 ])
+  ([ 1 2 ] < [ 1 2 3 ])
+  ([ 3 4 ] < [ 1 ])
+  ([ 1 2 ] > [ 1 ])
+  ([ 1 2 ] > [ 2 3 ])
+  ([ 1 2 ] > [ 2 ])
+  ([ 1 2 ] > [ 1 2 3 ])
+  ([ 3 4 ] > [ 1 ])
+  ([ 1 2 ] <= [ 1 ])
+  ([ 1 2 ] >= [ 2 3 ])
+  ([ 1 2 ] >= [ 2 ])
+  ([ 1 2 ] <= [ 1 2 3 ])
+  ([ 3 4 ] >= [ 1 ])
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-compare-ordering-catchable.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-compare-ordering-catchable.exp
new file mode 100644
index 0000000000..c508d5366f
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-compare-ordering-catchable.exp
@@ -0,0 +1 @@
+false
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-compare-ordering-catchable.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-compare-ordering-catchable.nix
new file mode 100644
index 0000000000..9000160e57
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-compare-ordering-catchable.nix
@@ -0,0 +1 @@
+(builtins.tryEval ((throw "x") < 3)).success
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-concat-lists.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-concat-lists.exp
new file mode 100644
index 0000000000..3bed31f76e
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-concat-lists.exp
@@ -0,0 +1 @@
+[ 1 2 3 4 ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-concat-lists.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-concat-lists.nix
new file mode 100644
index 0000000000..de332cd29f
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-concat-lists.nix
@@ -0,0 +1 @@
+[ 1 2 ] ++ [ 3 4 ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-concat-strings.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-concat-strings.exp
new file mode 100644
index 0000000000..cd4bc1ab64
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-concat-strings.exp
@@ -0,0 +1 @@
+"hello world"
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-concat-strings.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-concat-strings.nix
new file mode 100644
index 0000000000..1fc7089299
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-concat-strings.nix
@@ -0,0 +1 @@
+"hello " + "world"
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-concatmap.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-concatmap.exp
new file mode 100644
index 0000000000..14d804aa22
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-concatmap.exp
@@ -0,0 +1 @@
+[ "a" "z" "b" "z" ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-concatmap.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-concatmap.nix
new file mode 100644
index 0000000000..cff39b05e6
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-concatmap.nix
@@ -0,0 +1 @@
+(builtins.concatMap (x: [ x ] ++ [ "z" ]) [ "a" "b" ])
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-concatstringssep.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-concatstringssep.exp
new file mode 100644
index 0000000000..93987647ff
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-concatstringssep.exp
@@ -0,0 +1 @@
+[ "" "foobarxyzzy" "foo, bar, xyzzy" "foo" "" ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-concatstringssep.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-concatstringssep.nix
new file mode 100644
index 0000000000..cd94ca99b4
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-concatstringssep.nix
@@ -0,0 +1,9 @@
+with builtins;
+
+[
+  (concatStringsSep "" [ ])
+  (concatStringsSep "" [ "foo" "bar" "xyzzy" ])
+  (concatStringsSep ", " [ "foo" "bar" "xyzzy" ])
+  (concatStringsSep ", " [ "foo" ])
+  (concatStringsSep ", " [ ])
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-contains-nested-non-set.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-contains-nested-non-set.exp
new file mode 100644
index 0000000000..c508d5366f
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-contains-nested-non-set.exp
@@ -0,0 +1 @@
+false
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-contains-nested-non-set.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-contains-nested-non-set.nix
new file mode 100644
index 0000000000..361ba91445
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-contains-nested-non-set.nix
@@ -0,0 +1,3 @@
+# ? operator should work even if encountering a non-set value on the
+# walk
+{ a.b = 42; } ? a.b.c
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-contains-non-set.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-contains-non-set.exp
new file mode 100644
index 0000000000..ca00e3c049
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-contains-non-set.exp
@@ -0,0 +1 @@
+[ false false false false ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-contains-non-set.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-contains-non-set.nix
new file mode 100644
index 0000000000..c086759f45
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-contains-non-set.nix
@@ -0,0 +1,3 @@
+# Nix allows using the ? operator on non-set types, in which case it
+# should always return false.
+[ (123 ? key) ("foo" ? key) (null ? key) ([ "key" ] ? key) ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-deeply-nested-attrs.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-deeply-nested-attrs.exp
new file mode 100644
index 0000000000..7cf54d9596
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-deeply-nested-attrs.exp
@@ -0,0 +1 @@
+{ a = { b = { c = { d = { e = { f = { g = "deep!"; }; }; }; }; }; }; }
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-deeply-nested-attrs.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-deeply-nested-attrs.nix
new file mode 100644
index 0000000000..91649d0c6d
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-deeply-nested-attrs.nix
@@ -0,0 +1 @@
+{ a.b.c.d.e.f.g = "deep!"; }
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-deeply-nested-with-closure.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-deeply-nested-with-closure.exp
new file mode 100644
index 0000000000..3bed31f76e
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-deeply-nested-with-closure.exp
@@ -0,0 +1 @@
+[ 1 2 3 4 ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-deeply-nested-with-closure.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-deeply-nested-with-closure.nix
new file mode 100644
index 0000000000..f65e8ee537
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-deeply-nested-with-closure.nix
@@ -0,0 +1,21 @@
+# This convoluted test constructs a situation in which dynamically
+# resolved upvalues refer `with` blocks introduced at different lambda
+# context boundaries, i.e. the access to a, b in the innermost closure
+# must be threaded through upvalues in several levels.
+
+(_:
+with { a = 1; b = 1; };
+
+_:
+with { b = 2; c = 2; };
+
+_:
+with { c = 3; d = 3; };
+
+_:
+with { d = 4; };
+
+[ a b c d ]) null
+  null
+  null
+  null
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-deeply-nested-with.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-deeply-nested-with.exp
new file mode 100644
index 0000000000..3bed31f76e
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-deeply-nested-with.exp
@@ -0,0 +1 @@
+[ 1 2 3 4 ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-deeply-nested-with.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-deeply-nested-with.nix
new file mode 100644
index 0000000000..7f1128b670
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-deeply-nested-with.nix
@@ -0,0 +1,6 @@
+with { a = 1; b = 1; };
+with { b = 2; c = 2; };
+with { c = 3; d = 3; };
+with { d = 4; };
+
+[ a b c d ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-deepseq.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-deepseq.exp
new file mode 100644
index 0000000000..8d38505c16
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-deepseq.exp
@@ -0,0 +1 @@
+456
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-deepseq.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-deepseq.nix
new file mode 100644
index 0000000000..53aa4b1dc2
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-deepseq.nix
@@ -0,0 +1 @@
+builtins.deepSeq (let as = { x = 123; y = as; }; in as) 456
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-deferred-unary-formals.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-deferred-unary-formals.exp
new file mode 100644
index 0000000000..5993db7ccc
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-deferred-unary-formals.exp
@@ -0,0 +1 @@
+[ false -2 ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-deferred-unary-formals.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-deferred-unary-formals.nix
new file mode 100644
index 0000000000..1fbb3e853a
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-deferred-unary-formals.nix
@@ -0,0 +1,6 @@
+# Application of unary operators on deferred formals arguments (via
+# defaulting), see also b/255.
+[
+  (({ b ? !a, a }: b) { a = true; })
+  (({ b ? -a, a }: b) { a = 2; })
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-deferred-with.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-deferred-with.exp
new file mode 100644
index 0000000000..d81cc0710e
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-deferred-with.exp
@@ -0,0 +1 @@
+42
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-deferred-with.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-deferred-with.nix
new file mode 100644
index 0000000000..ccafdf74ce
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-deferred-with.nix
@@ -0,0 +1,9 @@
+# Tests using `with` on a set that does not yet exist on the stack.
+
+let
+  result = with set; value;
+  set = {
+    value = 42;
+  };
+in
+result
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-dirof.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-dirof.exp
new file mode 100644
index 0000000000..ff464e4c30
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-dirof.exp
@@ -0,0 +1 @@
+[ /foo "." "foo//" "foo" "." "." / "/" ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-dirof.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-dirof.nix
new file mode 100644
index 0000000000..13cf473205
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-dirof.nix
@@ -0,0 +1,10 @@
+[
+  (builtins.dirOf /foo/bar)
+  (builtins.dirOf "foo")
+  (builtins.dirOf "foo///")
+  (builtins.dirOf "foo/bar")
+  (builtins.dirOf "./.")
+  (builtins.dirOf "")
+  (builtins.dirOf /.)
+  (builtins.toString (builtins.dirOf /.))
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-elem.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-elem.exp
new file mode 100644
index 0000000000..3cf6c0e962
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-elem.exp
@@ -0,0 +1 @@
+[ true false 30 ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-elem.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-elem.nix
new file mode 100644
index 0000000000..71ea7a4ed0
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-elem.nix
@@ -0,0 +1,6 @@
+with import ./lib.nix;
+
+let xs = range 10 40; in
+
+[ (builtins.elem 23 xs) (builtins.elem 42 xs) (builtins.elemAt xs 20) ]
+
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-empty-rec-inherit.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-empty-rec-inherit.exp
new file mode 100644
index 0000000000..ffcd4415b0
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-empty-rec-inherit.exp
@@ -0,0 +1 @@
+{ }
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-empty-rec-inherit.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-empty-rec-inherit.nix
new file mode 100644
index 0000000000..a1181431de
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-empty-rec-inherit.nix
@@ -0,0 +1 @@
+rec { inherit; }
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-eq-float.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-eq-float.exp
new file mode 100644
index 0000000000..27ba77ddaf
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-eq-float.exp
@@ -0,0 +1 @@
+true
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-eq-float.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-eq-float.nix
new file mode 100644
index 0000000000..398f4a9dfc
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-eq-float.nix
@@ -0,0 +1 @@
+4.2 == 4.2
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-eq-int.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-eq-int.exp
new file mode 100644
index 0000000000..27ba77ddaf
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-eq-int.exp
@@ -0,0 +1 @@
+true
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-eq-int.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-eq-int.nix
new file mode 100644
index 0000000000..dc52ba112a
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-eq-int.nix
@@ -0,0 +1 @@
+42 == 42
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-eq-nested-list.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-eq-nested-list.exp
new file mode 100644
index 0000000000..27ba77ddaf
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-eq-nested-list.exp
@@ -0,0 +1 @@
+true
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-eq-nested-list.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-eq-nested-list.nix
new file mode 100644
index 0000000000..cc39e2415f
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-eq-nested-list.nix
@@ -0,0 +1 @@
+[ [ "f" "" ] ] == [ [ "f" "" ] ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-equality-tolerate-catchable-in-type-field.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-equality-tolerate-catchable-in-type-field.exp
new file mode 100644
index 0000000000..c508d5366f
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-equality-tolerate-catchable-in-type-field.exp
@@ -0,0 +1 @@
+false
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-equality-tolerate-catchable-in-type-field.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-equality-tolerate-catchable-in-type-field.nix
new file mode 100644
index 0000000000..6bd018b68d
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-equality-tolerate-catchable-in-type-field.nix
@@ -0,0 +1 @@
+(builtins.tryEval (builtins.elem { type = rec { x = throw "fred"; }.x; } [{ type = 3; }])).success
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-escape-string-correct-char-boundaries.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-escape-string-correct-char-boundaries.exp
new file mode 100644
index 0000000000..d889063f9a
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-escape-string-correct-char-boundaries.exp
@@ -0,0 +1 @@
+"💭(\":thonking:\")"
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-escape-string-correct-char-boundaries.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-escape-string-correct-char-boundaries.nix
new file mode 100644
index 0000000000..49f4b62731
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-escape-string-correct-char-boundaries.nix
@@ -0,0 +1,6 @@
+# Regression test for a bug where tvix would crash in nix_escape_string
+# because it counted the string position by unicode code point count,
+# but then used it as a byte index for slicing. Consequently, it would
+# try slicing 💭 in half, thinking the first element to be escaped was
+# at byte index 2 (i.e. the quote).
+"💭(\":thonking:\")"
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-escapify-integer-keys.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-escapify-integer-keys.exp
new file mode 100644
index 0000000000..aa98a082a8
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-escapify-integer-keys.exp
@@ -0,0 +1 @@
+{ "3" = 3; }
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-escapify-integer-keys.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-escapify-integer-keys.nix
new file mode 100644
index 0000000000..aa98a082a8
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-escapify-integer-keys.nix
@@ -0,0 +1 @@
+{ "3" = 3; }
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-fib.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-fib.exp
new file mode 100644
index 0000000000..8643cf6deb
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-fib.exp
@@ -0,0 +1 @@
+89
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-fib.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-fib.nix
new file mode 100644
index 0000000000..04cb52e033
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-fib.nix
@@ -0,0 +1,9 @@
+let
+  fib' = i: n: m:
+    if i == 0
+    then n
+    else fib' (i - 1) m (n + m);
+
+  fib = n: fib' n 1 1;
+in
+fib 10
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-fix.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-fix.exp
new file mode 100644
index 0000000000..c158154351
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-fix.exp
@@ -0,0 +1 @@
+{ a = 1; b = 21; c = 42; }
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-fix.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-fix.nix
new file mode 100644
index 0000000000..6069950194
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-fix.nix
@@ -0,0 +1,8 @@
+let
+  fix = f: let x = f x; in x;
+in
+fix (self: {
+  a = 1;
+  b = self.a + 20;
+  c = self.b * 2;
+})
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-float-repr.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-float-repr.exp
new file mode 100644
index 0000000000..c55d2be717
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-float-repr.exp
@@ -0,0 +1 @@
+1.23457
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-float-repr.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-float-repr.nix
new file mode 100644
index 0000000000..447bd5af7f
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-float-repr.nix
@@ -0,0 +1,2 @@
+# Floats are displayed with a maximum of 5 digits
+1.23456789
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-floor.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-floor.exp
new file mode 100644
index 0000000000..6f98a7f48f
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-floor.exp
@@ -0,0 +1 @@
+[ 3 ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-floor.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-floor.nix
new file mode 100644
index 0000000000..c6b79c91a1
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-floor.nix
@@ -0,0 +1 @@
+[ (builtins.floor 3.4) ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-foldlStrict-lazy-elements.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-foldlStrict-lazy-elements.exp
new file mode 100644
index 0000000000..d81cc0710e
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-foldlStrict-lazy-elements.exp
@@ -0,0 +1 @@
+42
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-foldlStrict-lazy-elements.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-foldlStrict-lazy-elements.nix
new file mode 100644
index 0000000000..fc4129a254
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-foldlStrict-lazy-elements.nix
@@ -0,0 +1,8 @@
+let
+  lst = builtins.foldl'
+    (acc: x: acc ++ [ x ])
+    [ ]
+    [ 42 (throw "this shouldn't be evaluated") ];
+in
+
+builtins.head lst
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-foldlStrict-lazy-initial-accumulator.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-foldlStrict-lazy-initial-accumulator.exp
new file mode 100644
index 0000000000..d81cc0710e
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-foldlStrict-lazy-initial-accumulator.exp
@@ -0,0 +1 @@
+42
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-foldlStrict-lazy-initial-accumulator.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-foldlStrict-lazy-initial-accumulator.nix
new file mode 100644
index 0000000000..59fd29b552
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-foldlStrict-lazy-initial-accumulator.nix
@@ -0,0 +1,4 @@
+builtins.foldl'
+  (_: x: x)
+  (throw "This is never forced")
+  [ "but the results of applying op are" 42 ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-foldlStrict.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-foldlStrict.exp
new file mode 100644
index 0000000000..8d683a20fa
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-foldlStrict.exp
@@ -0,0 +1 @@
+[ 6 [ 0 1 2 3 ] 2 ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-foldlStrict.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-foldlStrict.nix
new file mode 100644
index 0000000000..aadf5e1121
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-foldlStrict.nix
@@ -0,0 +1,5 @@
+[
+  (builtins.foldl' builtins.add 0 [ 1 2 3 ])
+  (builtins.foldl' (l1: l2: l1 ++ l2) [ 0 ] [ [ 1 ] [ 2 3 ] ])
+  (builtins.foldl' (x: y: if x == 0 then y else x * y) 0 [ 1 2 ])
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-formals-miscompilation-b-261-regression.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-formals-miscompilation-b-261-regression.exp
new file mode 100644
index 0000000000..721a052bcc
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-formals-miscompilation-b-261-regression.exp
@@ -0,0 +1 @@
+[ true null ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-formals-miscompilation-b-261-regression.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-formals-miscompilation-b-261-regression.nix
new file mode 100644
index 0000000000..772fa6f386
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-formals-miscompilation-b-261-regression.nix
@@ -0,0 +1,20 @@
+# This is a regression test for https://b.tvl.fyi/261.
+#
+# The bug occurred when Tvix would unconditionally finalise the stack slot of
+# `finalise` (as its default expression needs a finaliser): Finalising an
+# manually provided, already forced thunk would cause the VM to crash.
+let
+  thunk = x: x;
+  bomb = thunk true;
+  f =
+    { finalise ? later == null
+    , later ? null
+    }:
+    [ finalise later ];
+in
+
+# Note that the crash did not occur if the offending expression was the rhs
+  # argument to `builtins.seq`, hence we need to put the assert in between.
+assert builtins.seq bomb true;
+
+f { finalise = bomb; }
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-fromjson-escapes.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-fromjson-escapes.exp
new file mode 100644
index 0000000000..add5505a82
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-fromjson-escapes.exp
@@ -0,0 +1 @@
+"quote \" reverse solidus \\ solidus / backspace  formfeed  newline \n carriage return \r horizontal tab \t 1 char unicode encoded backspace  1 char unicode encoded e with accent é 2 char unicode encoded s with caron š 3 char unicode encoded rightwards arrow →"
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-fromjson-escapes.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-fromjson-escapes.nix
new file mode 100644
index 0000000000..f007135077
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-fromjson-escapes.nix
@@ -0,0 +1,3 @@
+# This string contains all supported escapes in a JSON string, per json.org
+# \b and \f are not supported by Nix
+builtins.fromJSON ''"quote \" reverse solidus \\ solidus \/ backspace \b formfeed \f newline \n carriage return \r horizontal tab \t 1 char unicode encoded backspace \u0008 1 char unicode encoded e with accent \u00e9 2 char unicode encoded s with caron \u0161 3 char unicode encoded rightwards arrow \u2192"''
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-fromjson.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-fromjson.exp
new file mode 100644
index 0000000000..24aa21d78f
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-fromjson.exp
@@ -0,0 +1 @@
+[ { Image = { Animated = false; Height = 600; IDs = [ 116 943 234 38793 true false null -100 ]; Latitude = 37.7668; Longitude = -122.396; Thumbnail = { Height = 125; Url = "http://www.example.com/image/481989943"; Width = 100; }; Title = "View from 15th Floor"; Width = 800; }; } { name = "a"; value = "b"; } [ 1 2 3 4 ] ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-fromjson.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-fromjson.nix
new file mode 100644
index 0000000000..1083919af8
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-fromjson.nix
@@ -0,0 +1,24 @@
+[
+  # RFC 7159, section 13.
+  (builtins.fromJSON
+    ''
+      {
+        "Image": {
+            "Width":  800,
+            "Height": 600,
+            "Title":  "View from 15th Floor",
+            "Thumbnail": {
+                "Url":    "http://www.example.com/image/481989943",
+                "Height": 125,
+                "Width":  100
+            },
+            "Animated" : false,
+            "IDs": [116, 943, 234, 38793, true  ,false,null, -100],
+            "Latitude":  37.7668,
+            "Longitude": -122.396
+        }
+      }
+    '')
+  (builtins.fromJSON ''{"name": "a", "value": "b"}'')
+  (builtins.fromJSON "[ 1, 2, 3, 4 ]")
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-functionargs.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-functionargs.exp
new file mode 100644
index 0000000000..c1c9f8ffaf
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-functionargs.exp
@@ -0,0 +1 @@
+[ "stdenv" "fetchurl" "aterm-stdenv" "aterm-stdenv2" "libX11" "libXv" "mplayer-stdenv2.libXv-libX11" "mplayer-stdenv2.libXv-libX11_2" "nix-stdenv-aterm-stdenv" "nix-stdenv2-aterm2-stdenv2" ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-functionargs.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-functionargs.nix
new file mode 100644
index 0000000000..6db04c562c
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-functionargs.nix
@@ -0,0 +1,83 @@
+let
+
+  stdenvFun = {}: { name = "stdenv"; };
+  stdenv2Fun = {}: { name = "stdenv2"; };
+  fetchurlFun = { stdenv }: assert stdenv.name == "stdenv"; { name = "fetchurl"; };
+  atermFun = { stdenv, fetchurl }: { name = "aterm-${stdenv.name}"; };
+  aterm2Fun = { stdenv, fetchurl }: { name = "aterm2-${stdenv.name}"; };
+  nixFun = { stdenv, fetchurl, aterm }: { name = "nix-${stdenv.name}-${aterm.name}"; };
+
+  mplayerFun =
+    { stdenv, fetchurl, enableX11 ? false, xorg ? null, enableFoo ? true, foo ? null }:
+      assert stdenv.name == "stdenv2";
+      assert enableX11 -> xorg.libXv.name == "libXv";
+      assert enableFoo -> foo != null;
+      { name = "mplayer-${stdenv.name}.${xorg.libXv.name}-${xorg.libX11.name}"; };
+
+  makeOverridable = f: origArgs: f origArgs //
+    {
+      override = newArgs:
+        makeOverridable f (origArgs // (if builtins.isFunction newArgs then newArgs origArgs else newArgs));
+    };
+
+  callPackage_ = pkgs: f: args:
+    makeOverridable f ((builtins.intersectAttrs (builtins.functionArgs f) pkgs) // args);
+
+  allPackages =
+    { overrides ? (pkgs: pkgsPrev: { }) }:
+    let
+      callPackage = callPackage_ pkgs;
+      pkgs = pkgsStd // (overrides pkgs pkgsStd);
+      pkgsStd = {
+        inherit pkgs;
+        stdenv = callPackage stdenvFun { };
+        stdenv2 = callPackage stdenv2Fun { };
+        fetchurl = callPackage fetchurlFun { };
+        aterm = callPackage atermFun { };
+        xorg = callPackage xorgFun { };
+        mplayer = callPackage mplayerFun { stdenv = pkgs.stdenv2; enableFoo = false; };
+        nix = callPackage nixFun { };
+      };
+    in
+    pkgs;
+
+  libX11Fun = { stdenv, fetchurl }: { name = "libX11"; };
+  libX11_2Fun = { stdenv, fetchurl }: { name = "libX11_2"; };
+  libXvFun = { stdenv, fetchurl, libX11 }: { name = "libXv"; };
+
+  xorgFun =
+    { pkgs }:
+    let callPackage = callPackage_ (pkgs // pkgs.xorg); in
+    {
+      libX11 = callPackage libX11Fun { };
+      libXv = callPackage libXvFun { };
+    };
+
+in
+
+let
+
+  pkgs = allPackages { };
+
+  pkgs2 = allPackages {
+    overrides = pkgs: pkgsPrev: {
+      stdenv = pkgs.stdenv2;
+      nix = pkgsPrev.nix.override { aterm = aterm2Fun { inherit (pkgs) stdenv fetchurl; }; };
+      xorg = pkgsPrev.xorg // { libX11 = libX11_2Fun { inherit (pkgs) stdenv fetchurl; }; };
+    };
+  };
+
+in
+
+[
+  pkgs.stdenv.name
+  pkgs.fetchurl.name
+  pkgs.aterm.name
+  pkgs2.aterm.name
+  pkgs.xorg.libX11.name
+  pkgs.xorg.libXv.name
+  pkgs.mplayer.name
+  pkgs2.mplayer.name
+  pkgs.nix.name
+  pkgs2.nix.name
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-functor-call.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-functor-call.exp
new file mode 100644
index 0000000000..d81cc0710e
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-functor-call.exp
@@ -0,0 +1 @@
+42
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-functor-call.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-functor-call.nix
new file mode 100644
index 0000000000..80ae345d83
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-functor-call.nix
@@ -0,0 +1 @@
+{ x = 21; __functor = self: y: self.x * y; } 2
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-genlist.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-genlist.exp
new file mode 100644
index 0000000000..cd4ca34f14
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-genlist.exp
@@ -0,0 +1 @@
+[ 0 1 4 9 16 ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-genlist.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-genlist.nix
new file mode 100644
index 0000000000..2c4dfba203
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-genlist.nix
@@ -0,0 +1 @@
+builtins.genList (x: x * x) 5
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-hasattr-catchable.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-hasattr-catchable.exp
new file mode 100644
index 0000000000..c508d5366f
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-hasattr-catchable.exp
@@ -0,0 +1 @@
+false
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-hasattr-catchable.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-hasattr-catchable.nix
new file mode 100644
index 0000000000..ba85d6b776
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-hasattr-catchable.nix
@@ -0,0 +1 @@
+(builtins.tryEval ((throw "fred") ? bob)).success
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-identifier-formatting.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-identifier-formatting.exp
new file mode 100644
index 0000000000..9800c675fc
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-identifier-formatting.exp
@@ -0,0 +1 @@
+{ "'quoted'" = false; "-20°" = false; "2normal" = false; "45 44 43-'3 2 1" = false; "9front" = false; Very2Normal = true; VeryNormal = true; _'12 = true; "_'12.5" = false; __internal = true; _internal = true; abort = true; "assert" = false; "attr.path" = false; "else" = false; false = true; foldl' = true; "if" = false; "in" = false; "inherit" = false; "let" = false; normal = true; normal2 = true; null = true; or = true; "rec" = false; "then" = false; throw = true; true = true; "with" = false; x = true; x' = true; x'' = true; "😀" = false; }
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-identifier-formatting.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-identifier-formatting.nix
new file mode 100644
index 0000000000..58af3d6d16
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-identifier-formatting.nix
@@ -0,0 +1,42 @@
+# Note: the attribute values in this set aren't just dummies!  They
+# are booleans which indicate whether or not the corresponding
+# attrname is valid without quotification.
+{
+  __internal = true;
+  _internal = true;
+  normal = true;
+  VeryNormal = true;
+  normal2 = true;
+  Very2Normal = true;
+  _'12 = true;
+  foldl' = true;
+  x = true;
+  x' = true;
+  x'' = true;
+
+  true = true;
+  false = true;
+  null = true;
+  or = true;
+  "assert" = false;
+  throw = true;
+  abort = true;
+
+  "9front" = false;
+  "2normal" = false;
+  "-20°" = false;
+  "45 44 43-'3 2 1" = false;
+  "attr.path" = false;
+  "'quoted'" = false;
+  "_'12.5" = false;
+  "😀" = false;
+
+  "if" = false;
+  "then" = false;
+  "else" = false;
+  "with" = false;
+  "let" = false;
+  "in" = false;
+  "rec" = false;
+  "inherit" = false;
+}
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-import-display.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-import-display.exp
new file mode 100644
index 0000000000..15d838950e
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-import-display.exp
@@ -0,0 +1 @@
+<PRIMOP>
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-import-display.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-import-display.nix
new file mode 100644
index 0000000000..411f3cd6ef
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-import-display.nix
@@ -0,0 +1,2 @@
+# In C++ Nix 2.3 this used to be <PRIMOP-APP>
+import
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-import.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-import.exp
new file mode 100644
index 0000000000..5ba7f64d78
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-import.exp
@@ -0,0 +1 @@
+[ 42 42 ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-import.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-import.nix
new file mode 100644
index 0000000000..49cd244f06
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-import.nix
@@ -0,0 +1,4 @@
+[
+  (import ./directory)
+  (import ./directory/default.nix)
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-inherit-string-ident.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-inherit-string-ident.exp
new file mode 100644
index 0000000000..d81cc0710e
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-inherit-string-ident.exp
@@ -0,0 +1 @@
+42
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-inherit-string-ident.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-inherit-string-ident.nix
new file mode 100644
index 0000000000..75794d3337
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-inherit-string-ident.nix
@@ -0,0 +1,8 @@
+# identifiers in inherits can be string-like expressions
+
+let
+  set = {
+    inherit ({ value = 42; }) "value";
+  };
+in
+set.value
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-internal-formals-deferred.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-internal-formals-deferred.exp
new file mode 100644
index 0000000000..d81cc0710e
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-internal-formals-deferred.exp
@@ -0,0 +1 @@
+42
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-internal-formals-deferred.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-internal-formals-deferred.nix
new file mode 100644
index 0000000000..5c6702120f
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-internal-formals-deferred.nix
@@ -0,0 +1,3 @@
+# Tests formals which have internal default values that must be deferred.
+
+({ optional ? defaultValue, defaultValue }: optional) { defaultValue = 42; }
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-internal-formals.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-internal-formals.exp
new file mode 100644
index 0000000000..d81cc0710e
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-internal-formals.exp
@@ -0,0 +1 @@
+42
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-internal-formals.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-internal-formals.nix
new file mode 100644
index 0000000000..c6dd5e9d54
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-internal-formals.nix
@@ -0,0 +1,3 @@
+# Tests formals which have internal default values.
+
+({ defaultValue, optional ? defaultValue }: optional) { defaultValue = 42; }
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-intersectattrs.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-intersectattrs.exp
new file mode 100644
index 0000000000..25001b211f
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-intersectattrs.exp
@@ -0,0 +1 @@
+{ a = 100; b = 200; }
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-intersectattrs.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-intersectattrs.nix
new file mode 100644
index 0000000000..f02d963226
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-intersectattrs.nix
@@ -0,0 +1,3 @@
+builtins.intersectAttrs
+{ a = 1; b = 2; c = 3; }
+{ a = 100; b = 200; d = 5; }
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-lambda-identity.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-lambda-identity.exp
new file mode 100644
index 0000000000..d81cc0710e
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-lambda-identity.exp
@@ -0,0 +1 @@
+42
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-lambda-identity.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-lambda-identity.nix
new file mode 100644
index 0000000000..f2ee49df80
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-lambda-identity.nix
@@ -0,0 +1,2 @@
+# Identity function is the simplest possible function.
+(x: x) 42
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-late-binding-closure.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-late-binding-closure.exp
new file mode 100644
index 0000000000..d81cc0710e
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-late-binding-closure.exp
@@ -0,0 +1 @@
+42
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-late-binding-closure.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-late-binding-closure.nix
new file mode 100644
index 0000000000..4312ec9a52
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-late-binding-closure.nix
@@ -0,0 +1,5 @@
+let
+  f = n: n + a;
+  a = 2;
+in
+f 40
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-late-binding.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-late-binding.exp
new file mode 100644
index 0000000000..d81cc0710e
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-late-binding.exp
@@ -0,0 +1 @@
+42
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-late-binding.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-late-binding.nix
new file mode 100644
index 0000000000..6b6875cf1a
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-late-binding.nix
@@ -0,0 +1,5 @@
+let
+  a = b;
+  b = 42;
+in
+a
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-lazy-assert.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-lazy-assert.exp
new file mode 100644
index 0000000000..48082f72f0
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-lazy-assert.exp
@@ -0,0 +1 @@
+12
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-lazy-assert.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-lazy-assert.nix
new file mode 100644
index 0000000000..5a36964976
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-lazy-assert.nix
@@ -0,0 +1,8 @@
+assert true;
+
+let
+  x = assert false; 13;
+  y = 12;
+in
+
+{ inherit x y; }.y
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-lazy-equality.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-lazy-equality.exp
new file mode 100644
index 0000000000..1c70d1bcf1
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-lazy-equality.exp
@@ -0,0 +1 @@
+[ true true false true true ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-lazy-equality.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-lazy-equality.nix
new file mode 100644
index 0000000000..92363245f8
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-lazy-equality.nix
@@ -0,0 +1,16 @@
+let
+  attrs1 = { x = 1 + 2; };
+  attrs2 = { x = 2 + 1; };
+  list1 = [ (1 + 2) ];
+  list2 = [ (2 + 1) ];
+  list3 = [ (2 + 2) ];
+  list4 = [ (2 + 2) ];
+  list5 = [ (2 + 2) ];
+in
+[
+  (attrs1 == attrs2)
+  (list1 == list2)
+  (list3 == list2)
+  (list4 == [ 4 ])
+  ([ 4 ] == list5)
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-lazy-with-nested.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-lazy-with-nested.exp
new file mode 100644
index 0000000000..d81cc0710e
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-lazy-with-nested.exp
@@ -0,0 +1 @@
+42
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-lazy-with-nested.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-lazy-with-nested.nix
new file mode 100644
index 0000000000..22ac14b3f1
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-lazy-with-nested.nix
@@ -0,0 +1,5 @@
+# The 'namespace' of a with should only be evaluated if an identifier
+# from it is actually accessed.
+
+with (abort "should not be evaluated");
+let a = dynamic; in 42
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-lazy-with.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-lazy-with.exp
new file mode 100644
index 0000000000..d81cc0710e
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-lazy-with.exp
@@ -0,0 +1 @@
+42
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-lazy-with.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-lazy-with.nix
new file mode 100644
index 0000000000..8b1a0191dc
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-lazy-with.nix
@@ -0,0 +1,6 @@
+# The 'namespace' of a with should only be evaluated if an identifier
+# from it is actually accessed.
+
+with (abort "should not be evaluated");
+
+42
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-legacy-let-fix.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-legacy-let-fix.exp
new file mode 100644
index 0000000000..5d2955ffd5
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-legacy-let-fix.exp
@@ -0,0 +1 @@
+{ one = 42; two = 42; }
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-legacy-let-fix.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-legacy-let-fix.nix
new file mode 100644
index 0000000000..a411b1c4a4
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-legacy-let-fix.nix
@@ -0,0 +1,9 @@
+let {
+a = 21;
+b = body.one;
+
+body = {
+  one = a * 2;
+  two = b;
+};
+}
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-legacy-let-in-with.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-legacy-let-in-with.exp
new file mode 100644
index 0000000000..d81cc0710e
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-legacy-let-in-with.exp
@@ -0,0 +1 @@
+42
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-legacy-let-in-with.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-legacy-let-in-with.nix
new file mode 100644
index 0000000000..7d95efa5c3
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-legacy-let-in-with.nix
@@ -0,0 +1 @@
+with { }; let { body = 42; }
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-legacy-let.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-legacy-let.exp
new file mode 100644
index 0000000000..d81cc0710e
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-legacy-let.exp
@@ -0,0 +1 @@
+42
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-legacy-let.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-legacy-let.nix
new file mode 100644
index 0000000000..faabe25457
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-legacy-let.nix
@@ -0,0 +1,4 @@
+let {
+a = 21;
+body = a * 2;
+}
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-let-identifiers.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-let-identifiers.exp
new file mode 100644
index 0000000000..5776134d0e
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-let-identifiers.exp
@@ -0,0 +1 @@
+[ 1 2 3 ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-let-identifiers.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-let-identifiers.nix
new file mode 100644
index 0000000000..ce588be069
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-let-identifiers.nix
@@ -0,0 +1,6 @@
+let
+  a = 1;
+  "b" = 2;
+  ${"c"} = 3;
+in
+[ a b c ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-let-inherit-from-later-bound.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-let-inherit-from-later-bound.exp
new file mode 100644
index 0000000000..409940768f
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-let-inherit-from-later-bound.exp
@@ -0,0 +1 @@
+23
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-let-inherit-from-later-bound.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-let-inherit-from-later-bound.nix
new file mode 100644
index 0000000000..21196f48bc
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-let-inherit-from-later-bound.nix
@@ -0,0 +1,13 @@
+let
+  inherit (c) d;
+  inherit (a) b c;
+
+  a = {
+    b = 20;
+    c = {
+      d = 3;
+    };
+  };
+in
+
+b + d
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-let-inherit.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-let-inherit.exp
new file mode 100644
index 0000000000..0cfbf08886
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-let-inherit.exp
@@ -0,0 +1 @@
+2
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-let-inherit.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-let-inherit.nix
new file mode 100644
index 0000000000..3aa7c0f8d2
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-let-inherit.nix
@@ -0,0 +1,13 @@
+let
+  set = {
+    a = 1;
+  };
+in
+let
+  set2 = {
+    b = 1;
+  };
+  inherit (set) a;
+  inherit (set2) b;
+in
+a + b
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-let-sibling-access.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-let-sibling-access.exp
new file mode 100644
index 0000000000..00750edc07
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-let-sibling-access.exp
@@ -0,0 +1 @@
+3
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-let-sibling-access.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-let-sibling-access.nix
new file mode 100644
index 0000000000..faad81a213
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-let-sibling-access.nix
@@ -0,0 +1,6 @@
+let
+  a = 1;
+  b = 2;
+  c = a + b;
+in
+c
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-let-useful-plain-inherit-mixed.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-let-useful-plain-inherit-mixed.exp
new file mode 100644
index 0000000000..3bed31f76e
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-let-useful-plain-inherit-mixed.exp
@@ -0,0 +1 @@
+[ 1 2 3 4 ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-let-useful-plain-inherit-mixed.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-let-useful-plain-inherit-mixed.nix
new file mode 100644
index 0000000000..30981099cb
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-let-useful-plain-inherit-mixed.nix
@@ -0,0 +1,20 @@
+# This test mixes different ways of creating bindings in a let … in expression
+# to make sure that the compiler initialises the locals in the same order as
+# they are declared.
+
+let
+  d = 4;
+in
+
+# Trick to allow useless inherits in the following let
+with { _unused = null; };
+
+let
+  set = { b = 2; };
+  a = 1;
+  inherit (set) b;
+  c = 3;
+  inherit d;
+in
+
+[ a b c d ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-let-useful-plain-inherit.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-let-useful-plain-inherit.exp
new file mode 100644
index 0000000000..d00491fd7e
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-let-useful-plain-inherit.exp
@@ -0,0 +1 @@
+1
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-let-useful-plain-inherit.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-let-useful-plain-inherit.nix
new file mode 100644
index 0000000000..3d1c46b10b
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-let-useful-plain-inherit.nix
@@ -0,0 +1,9 @@
+with { a = 1; };
+
+let
+  inherit a;
+in
+
+with { a = 2; };
+
+a
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-list-comparison.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-list-comparison.exp
new file mode 100644
index 0000000000..c508d5366f
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-list-comparison.exp
@@ -0,0 +1 @@
+false
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-list-comparison.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-list-comparison.nix
new file mode 100644
index 0000000000..7796fe4cbb
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-list-comparison.nix
@@ -0,0 +1 @@
+[ 1 2 ] > [ ((rec{ x = 1; }).x) 2 ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-listtoattrs.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-listtoattrs.exp
new file mode 100644
index 0000000000..74abef7bc6
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-listtoattrs.exp
@@ -0,0 +1 @@
+"AAbar"
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-listtoattrs.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-listtoattrs.nix
new file mode 100644
index 0000000000..551db72cb0
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-listtoattrs.nix
@@ -0,0 +1,16 @@
+with builtins;
+let
+  fold = op: nul: list:
+    if list == [ ]
+    then nul
+    else op (head list) (fold op nul (tail list));
+  concat =
+    fold (x: y: x + y) "";
+  asi = name: value: { inherit name value; };
+  list = [ (asi "a" "A") (asi "b" "B") ];
+  a = builtins.listToAttrs list;
+  b = builtins.listToAttrs (list ++ list);
+  r = builtins.listToAttrs [ (asi "result" [ a b ]) (asi "throw" (throw "this should not be thrown")) ];
+  x = builtins.listToAttrs [ (asi "foo" "bar") (asi "foo" "bla") ];
+in
+concat (map (x: x.a) r.result) + x.foo
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-logical-and-catchable.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-logical-and-catchable.exp
new file mode 100644
index 0000000000..c508d5366f
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-logical-and-catchable.exp
@@ -0,0 +1 @@
+false
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-logical-and-catchable.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-logical-and-catchable.nix
new file mode 100644
index 0000000000..dd2a9baa75
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-logical-and-catchable.nix
@@ -0,0 +1 @@
+(builtins.tryEval ((throw "fred") && (throw "jill"))).success
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-logical-or-catchable.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-logical-or-catchable.exp
new file mode 100644
index 0000000000..c508d5366f
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-logical-or-catchable.exp
@@ -0,0 +1 @@
+false
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-logical-or-catchable.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-logical-or-catchable.nix
new file mode 100644
index 0000000000..3adccfa441
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-logical-or-catchable.nix
@@ -0,0 +1 @@
+(builtins.tryEval ((throw "fred") || (throw "jill"))).success
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-manual-rec.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-manual-rec.exp
new file mode 100644
index 0000000000..d81cc0710e
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-manual-rec.exp
@@ -0,0 +1 @@
+42
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-manual-rec.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-manual-rec.nix
new file mode 100644
index 0000000000..23459e384a
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-manual-rec.nix
@@ -0,0 +1,10 @@
+# Manual desugaring of something similar to `rec`, to test lower level
+# recursion primitives.
+
+let
+  set = with set; {
+    a = 21;
+    b = a * 2;
+  };
+in
+set.b
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-merge-nested-attrs.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-merge-nested-attrs.exp
new file mode 100644
index 0000000000..911ab51de5
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-merge-nested-attrs.exp
@@ -0,0 +1 @@
+{ set = { a = 1; b = 2; }; }
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-merge-nested-attrs.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-merge-nested-attrs.nix
new file mode 100644
index 0000000000..78b28909a2
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-merge-nested-attrs.nix
@@ -0,0 +1,9 @@
+{
+  set = {
+    a = 1;
+  };
+
+  set = {
+    b = 2;
+  };
+}
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-merge-nested-rec-attrs.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-merge-nested-rec-attrs.exp
new file mode 100644
index 0000000000..768eaae61c
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-merge-nested-rec-attrs.exp
@@ -0,0 +1 @@
+{ set = { a = 21; b = 42; }; }
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-merge-nested-rec-attrs.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-merge-nested-rec-attrs.nix
new file mode 100644
index 0000000000..cea4cb1b4f
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-merge-nested-rec-attrs.nix
@@ -0,0 +1,12 @@
+{
+  set = rec {
+    a = 21;
+  };
+
+  set = {
+    # Fun fact: This might be the only case in Nix where a lexical
+    # resolution of an identifier can only be resolved by looking at
+    # *siblings* in the AST.
+    b = 2 * a;
+  };
+}
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-multiline-string.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-multiline-string.exp
new file mode 100644
index 0000000000..9839e480b7
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-multiline-string.exp
@@ -0,0 +1 @@
+"hello\nworld"
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-multiline-string.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-multiline-string.nix
new file mode 100644
index 0000000000..84beb22ed5
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-multiline-string.nix
@@ -0,0 +1,2 @@
+''hello
+world''
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-multiple-nested-attrs.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-multiple-nested-attrs.exp
new file mode 100644
index 0000000000..b5c707cf46
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-multiple-nested-attrs.exp
@@ -0,0 +1 @@
+{ a = { b = 15; }; b = { c = "test"; }; }
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-multiple-nested-attrs.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-multiple-nested-attrs.nix
new file mode 100644
index 0000000000..5d611930ca
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-multiple-nested-attrs.nix
@@ -0,0 +1 @@
+{ a.b = 15; b.c = "test"; }
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-mutually-recursive-let-binding.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-mutually-recursive-let-binding.exp
new file mode 100644
index 0000000000..edca9baca9
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-mutually-recursive-let-binding.exp
@@ -0,0 +1 @@
+{ a = 1; b = 2; c = 3; }
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-mutually-recursive-let-binding.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-mutually-recursive-let-binding.nix
new file mode 100644
index 0000000000..1b3feda432
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-mutually-recursive-let-binding.nix
@@ -0,0 +1,14 @@
+let
+  a = {
+    a = 3;
+    b = b.b;
+  };
+
+  b = {
+    a = a.a - 2;
+    b = 2;
+    c = a.c or 3;
+  };
+in
+
+a // b
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-ne-int.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-ne-int.exp
new file mode 100644
index 0000000000..27ba77ddaf
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-ne-int.exp
@@ -0,0 +1 @@
+true
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-ne-int.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-ne-int.nix
new file mode 100644
index 0000000000..e06b571a28
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-ne-int.nix
@@ -0,0 +1 @@
+42 != 69
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-ne-string.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-ne-string.exp
new file mode 100644
index 0000000000..27ba77ddaf
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-ne-string.exp
@@ -0,0 +1 @@
+true
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-ne-string.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-ne-string.nix
new file mode 100644
index 0000000000..a83471e500
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-ne-string.nix
@@ -0,0 +1 @@
+"this" != "that"
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-nested-assertions.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-nested-assertions.exp
new file mode 100644
index 0000000000..c508d5366f
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-nested-assertions.exp
@@ -0,0 +1 @@
+false
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-nested-assertions.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-nested-assertions.nix
new file mode 100644
index 0000000000..b0397e268e
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-nested-assertions.nix
@@ -0,0 +1 @@
+(builtins.tryEval (assert (assert false; true); true)).success
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-nested-closure.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-nested-closure.exp
new file mode 100644
index 0000000000..b6a7d89c68
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-nested-closure.exp
@@ -0,0 +1 @@
+16
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-nested-closure.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-nested-closure.nix
new file mode 100644
index 0000000000..97bff7f077
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-nested-closure.nix
@@ -0,0 +1 @@
+(a: b: c: d: a + b + c + d) 1 3 5 7
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-nested-deferred-upvalue.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-nested-deferred-upvalue.exp
new file mode 100644
index 0000000000..209e3ef4b6
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-nested-deferred-upvalue.exp
@@ -0,0 +1 @@
+20
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-nested-deferred-upvalue.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-nested-deferred-upvalue.nix
new file mode 100644
index 0000000000..3fa1d3ed05
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-nested-deferred-upvalue.nix
@@ -0,0 +1,10 @@
+let
+  doubler = n: outer n;
+  outer =
+    let
+      inner = n: a * n;
+      a = 2;
+    in
+    inner;
+in
+doubler 10
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-nested-has-attrs.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-nested-has-attrs.exp
new file mode 100644
index 0000000000..d2c1c04da3
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-nested-has-attrs.exp
@@ -0,0 +1 @@
+[ true true true true true true true false false false false false ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-nested-has-attrs.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-nested-has-attrs.nix
new file mode 100644
index 0000000000..47dcec7a95
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-nested-has-attrs.nix
@@ -0,0 +1,26 @@
+let
+  set = {
+    a.b.c = 123;
+    foo = {
+      bar = 23;
+    };
+    baz = 1;
+  };
+
+  tes = "random value";
+in
+
+[
+  (set ? a)
+  (set ? a.b)
+  (set ? a.b.c)
+  (set ? foo)
+  (set ? foo.bar)
+  (set.foo ? bar)
+  (set ? baz)
+  (set ? x)
+  (set ? x.y.z)
+  (tes ? bar)
+  (tes ? x.y.z)
+  (null ? null)
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-nested-keys-let.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-nested-keys-let.exp
new file mode 100644
index 0000000000..6db47b033e
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-nested-keys-let.exp
@@ -0,0 +1 @@
+{ a = { b = 42; }; }
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-nested-keys-let.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-nested-keys-let.nix
new file mode 100644
index 0000000000..c99ac748e6
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-nested-keys-let.nix
@@ -0,0 +1,5 @@
+let
+  inner = 21;
+  set.a.b = inner * 2;
+in
+set
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-nested-keys-rec.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-nested-keys-rec.exp
new file mode 100644
index 0000000000..77eb325dde
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-nested-keys-rec.exp
@@ -0,0 +1 @@
+{ a = { b = { c = 42; }; }; outer = 21; }
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-nested-keys-rec.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-nested-keys-rec.nix
new file mode 100644
index 0000000000..797d11108f
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-nested-keys-rec.nix
@@ -0,0 +1,4 @@
+rec {
+  outer = 21;
+  a.b.c = outer * 2;
+}
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-nested-let-slots.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-nested-let-slots.exp
new file mode 100644
index 0000000000..e45ef1da2f
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-nested-let-slots.exp
@@ -0,0 +1 @@
+[ 1 2 3 4 5 6 7 ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-nested-let-slots.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-nested-let-slots.nix
new file mode 100644
index 0000000000..eec5940875
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-nested-let-slots.nix
@@ -0,0 +1,22 @@
+# This test deals with a tricky edge-case around scopes, where the
+# stack slot accounting must correctly account for the position at
+# which the body of a let expression is being initialised when
+# resolving upvalues.
+
+let
+  a = 1;
+  b = 2;
+  outer =
+    let
+      c = 3;
+      d = 4;
+      inner =
+        let
+          e = 5;
+          f = 6;
+        in
+        g: [ a b c d e f g ];
+    in
+    inner;
+in
+outer 7
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-nested-let.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-nested-let.exp
new file mode 100644
index 0000000000..7f8f011eb7
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-nested-let.exp
@@ -0,0 +1 @@
+7
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-nested-let.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-nested-let.nix
new file mode 100644
index 0000000000..f40c04b139
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-nested-let.nix
@@ -0,0 +1,10 @@
+let
+  a =
+    let
+      b = 1;
+      c = 2;
+    in
+    b + c;
+  b = 4;
+in
+a + b
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-nested-poisoning.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-nested-poisoning.exp
new file mode 100644
index 0000000000..d81cc0710e
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-nested-poisoning.exp
@@ -0,0 +1 @@
+42
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-nested-poisoning.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-nested-poisoning.nix
new file mode 100644
index 0000000000..0fd22a671c
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-nested-poisoning.nix
@@ -0,0 +1,5 @@
+let
+  null = 1;
+  f = n: n + null;
+in
+f 41
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-nested-set-thunks.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-nested-set-thunks.exp
new file mode 100644
index 0000000000..d81cc0710e
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-nested-set-thunks.exp
@@ -0,0 +1 @@
+42
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-nested-set-thunks.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-nested-set-thunks.nix
new file mode 100644
index 0000000000..f3ad829354
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-nested-set-thunks.nix
@@ -0,0 +1,5 @@
+({
+  x = {
+    y = 42;
+  };
+}).x.y
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-nested-siblings.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-nested-siblings.exp
new file mode 100644
index 0000000000..d757cae1f5
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-nested-siblings.exp
@@ -0,0 +1 @@
+{ outer = 42; sibling = 42; }
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-nested-siblings.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-nested-siblings.nix
new file mode 100644
index 0000000000..31111d8081
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-nested-siblings.nix
@@ -0,0 +1,7 @@
+rec {
+  outer =
+    let inner = sibling;
+    in inner;
+
+  sibling = 42;
+}
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-nested-thunks.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-nested-thunks.exp
new file mode 100644
index 0000000000..d81cc0710e
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-nested-thunks.exp
@@ -0,0 +1 @@
+42
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-nested-thunks.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-nested-thunks.nix
new file mode 100644
index 0000000000..2519221e97
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-nested-thunks.nix
@@ -0,0 +1,8 @@
+# If a thunk yields another thunk, OpForce should keep forcing until
+# there is a value.
+let
+  a = b;
+  b = c;
+  c = 42;
+in
+a
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-nested-with.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-nested-with.exp
new file mode 100644
index 0000000000..0cfbf08886
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-nested-with.exp
@@ -0,0 +1 @@
+2
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-nested-with.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-nested-with.nix
new file mode 100644
index 0000000000..fa832b2099
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-nested-with.nix
@@ -0,0 +1,5 @@
+let
+  set1 = { a = 1; };
+  set2 = { a = 2; };
+in
+with set1; with set2; a
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-nix-version-cmp.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-nix-version-cmp.exp
new file mode 100644
index 0000000000..3a2e3f4984
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-nix-version-cmp.exp
@@ -0,0 +1 @@
+-1
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-nix-version-cmp.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-nix-version-cmp.nix
new file mode 100644
index 0000000000..6f35305612
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-nix-version-cmp.nix
@@ -0,0 +1,5 @@
+# nixpkgs checks against the `builtins.nixVersion` and fails if it
+# doesn't like what it sees. To work around this we have a "user-agent
+# style" version (see cl/6858) that ensures compatibility.
+
+builtins.compareVersions "2.3" builtins.nixVersion
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-observable-eval-cache.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-observable-eval-cache.exp
new file mode 100644
index 0000000000..aaa53b6025
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-observable-eval-cache.exp
@@ -0,0 +1 @@
+[ true true false false true ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-observable-eval-cache.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-observable-eval-cache.nix
new file mode 100644
index 0000000000..24003d0637
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-observable-eval-cache.nix
@@ -0,0 +1,7 @@
+[
+  (import ./observable-eval-cache1.nix == import ./observable-eval-cache1.nix)
+  (import ./observable-eval-cache1.nix == import ./observable-eval-cache2.nix)
+  (import ./observable-eval-cache1.nix == import ./observable-eval-cache3.nix)
+  (import ./observable-eval-cache2.nix == import ./observable-eval-cache3.nix)
+  (import ./observable-eval-cache3.nix == import ./observable-eval-cache3.nix)
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-observe-infinite-attrs.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-observe-infinite-attrs.exp
new file mode 100644
index 0000000000..bbb332a5ee
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-observe-infinite-attrs.exp
@@ -0,0 +1 @@
+[ "x" "y" ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-observe-infinite-attrs.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-observe-infinite-attrs.nix
new file mode 100644
index 0000000000..684c88f800
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-observe-infinite-attrs.nix
@@ -0,0 +1,4 @@
+# The below attribute set is infinitely large, but we should be able
+# to observe it as long as we don't access its entire value.
+
+let as = { x = 123; y = as; }; in builtins.attrNames as.y.y
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-optimised-bools.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-optimised-bools.exp
new file mode 100644
index 0000000000..9d9185fcd1
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-optimised-bools.exp
@@ -0,0 +1 @@
+[ true true false false true true false false ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-optimised-bools.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-optimised-bools.nix
new file mode 100644
index 0000000000..650d7f028d
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-optimised-bools.nix
@@ -0,0 +1,21 @@
+let
+  makeTrue = _: true;
+  makeFalse = _: false;
+in
+[
+  # useless `false`
+  (false || makeTrue null) # true
+  (makeTrue null || false) # true
+
+  # useless `true`
+  (true && makeFalse null) # false
+  (makeFalse null && true) # false
+
+  # useless `||`
+  (true || makeFalse null) # true
+  (makeFalse null || true) # true
+
+  # useless `&&`
+  (false && makeTrue null) # false
+  (makeTrue null && false) # false
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-or-operator-default.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-or-operator-default.exp
new file mode 100644
index 0000000000..d00491fd7e
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-or-operator-default.exp
@@ -0,0 +1 @@
+1
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-or-operator-default.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-or-operator-default.nix
new file mode 100644
index 0000000000..444f270af6
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-or-operator-default.nix
@@ -0,0 +1 @@
+{ b = 1; }.b or 2
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-or-operator-nested-default.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-or-operator-nested-default.exp
new file mode 100644
index 0000000000..0cfbf08886
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-or-operator-nested-default.exp
@@ -0,0 +1 @@
+2
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-or-operator-nested-default.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-or-operator-nested-default.nix
new file mode 100644
index 0000000000..ceffd0697b
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-or-operator-nested-default.nix
@@ -0,0 +1 @@
+{ a.b = 1; }.a.c or 2
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-or-operator-nested.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-or-operator-nested.exp
new file mode 100644
index 0000000000..d00491fd7e
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-or-operator-nested.exp
@@ -0,0 +1 @@
+1
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-or-operator-nested.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-or-operator-nested.nix
new file mode 100644
index 0000000000..1a76594546
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-or-operator-nested.nix
@@ -0,0 +1 @@
+{ a.b = 1; }.a.b or 2
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-or-operator-non-set.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-or-operator-non-set.exp
new file mode 100644
index 0000000000..a833e32892
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-or-operator-non-set.exp
@@ -0,0 +1 @@
+"works fine"
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-or-operator-non-set.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-or-operator-non-set.nix
new file mode 100644
index 0000000000..fd09bfee64
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-or-operator-non-set.nix
@@ -0,0 +1,2 @@
+# `or` operator should keep working if it encounters a non-set type.
+{ a.b = 42; }.a.b.c or "works fine"
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-or-operator.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-or-operator.exp
new file mode 100644
index 0000000000..d00491fd7e
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-or-operator.exp
@@ -0,0 +1 @@
+1
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-or-operator.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-or-operator.nix
new file mode 100644
index 0000000000..ce1e6e67c2
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-or-operator.nix
@@ -0,0 +1 @@
+{ a = 1; }.a or 2
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-overlapping-nested-attrs.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-overlapping-nested-attrs.exp
new file mode 100644
index 0000000000..2483a27183
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-overlapping-nested-attrs.exp
@@ -0,0 +1 @@
+{ a = { b = 15; c = "test"; }; }
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-overlapping-nested-attrs.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-overlapping-nested-attrs.nix
new file mode 100644
index 0000000000..4154ff9da2
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-overlapping-nested-attrs.nix
@@ -0,0 +1,4 @@
+{
+  a.b = 15;
+  a.c = "test";
+}
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-parsedrvname.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-parsedrvname.exp
new file mode 100644
index 0000000000..27ba77ddaf
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-parsedrvname.exp
@@ -0,0 +1 @@
+true
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-parsedrvname.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-parsedrvname.nix
new file mode 100644
index 0000000000..5997c99b47
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-parsedrvname.nix
@@ -0,0 +1,11 @@
+# the first dash followed by a non-alphabetic character separates
+# the "name" from the "version"
+
+assert builtins.parseDrvName "ripgrep-1.2" == { name = "ripgrep"; version = "1.2"; };
+assert builtins.parseDrvName "rip-grep-1.2" == { name = "rip-grep"; version = "1.2"; };
+assert builtins.parseDrvName "7zip_archiver-0.2" == { name = "7zip_archiver"; version = "0.2"; };
+assert builtins.parseDrvName "gcc-1-2" == { name = "gcc"; version = "1-2"; };
+assert builtins.parseDrvName "bash--1-2" == { name = "bash"; version = "-1-2"; };
+assert builtins.parseDrvName "xvidtune-?1-2" == { name = "xvidtune"; version = "?1-2"; };
+
+true
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-pathexists.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-pathexists.exp
new file mode 100644
index 0000000000..27ba77ddaf
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-pathexists.exp
@@ -0,0 +1 @@
+true
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-pathexists.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-pathexists.nix
new file mode 100644
index 0000000000..c9eedb44ff
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-pathexists.nix
@@ -0,0 +1,2 @@
+builtins.pathExists ./lib.nix
+  && !builtins.pathExists ./bla.nix
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-poisoned-scopes.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-poisoned-scopes.exp
new file mode 100644
index 0000000000..5776134d0e
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-poisoned-scopes.exp
@@ -0,0 +1 @@
+[ 1 2 3 ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-poisoned-scopes.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-poisoned-scopes.nix
new file mode 100644
index 0000000000..81f03d9e2b
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-poisoned-scopes.nix
@@ -0,0 +1,6 @@
+let
+  true = 1;
+  false = 2;
+  null = 3;
+in
+[ true false null ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-readDir.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-readDir.exp
new file mode 100644
index 0000000000..bf8d2c14ea
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-readDir.exp
@@ -0,0 +1 @@
+{ bar = "regular"; foo = "directory"; }
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-readDir.nix.disabled b/tvix/eval/src/tests/tvix_tests/eval-okay-readDir.nix.disabled
new file mode 100644
index 0000000000..a7ec9292aa
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-readDir.nix.disabled
@@ -0,0 +1 @@
+builtins.readDir ./readDir
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-readfile.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-readfile.exp
new file mode 100644
index 0000000000..a2c87d0c43
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-readfile.exp
@@ -0,0 +1 @@
+"builtins.readFile ./eval-okay-readfile.nix\n"
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-readfile.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-readfile.nix
new file mode 100644
index 0000000000..82f7cb1743
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-readfile.nix
@@ -0,0 +1 @@
+builtins.readFile ./eval-okay-readfile.nix
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-rec-dynamic-keys.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-rec-dynamic-keys.exp
new file mode 100644
index 0000000000..ac8d062a69
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-rec-dynamic-keys.exp
@@ -0,0 +1 @@
+{ barbaz = 42; foobar = 42; val = 21; }
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-rec-dynamic-keys.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-rec-dynamic-keys.nix
new file mode 100644
index 0000000000..8d7a8cef8e
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-rec-dynamic-keys.nix
@@ -0,0 +1,5 @@
+rec {
+  val = 21;
+  ${"foo" + "bar"} = 42;
+  ${"bar" + "baz"} = val * 2;
+}
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-rec-nested-access.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-rec-nested-access.exp
new file mode 100644
index 0000000000..a1dca9bb68
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-rec-nested-access.exp
@@ -0,0 +1 @@
+{ a = { b = 1; c = 2; }; }
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-rec-nested-access.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-rec-nested-access.nix
new file mode 100644
index 0000000000..7d037c6b37
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-rec-nested-access.nix
@@ -0,0 +1,4 @@
+rec {
+  a.b = 1;
+  a.c = a.b * 2;
+}
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-recursive-attrs-all-features.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-recursive-attrs-all-features.exp
new file mode 100644
index 0000000000..d81cc0710e
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-recursive-attrs-all-features.exp
@@ -0,0 +1 @@
+42
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-recursive-attrs-all-features.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-recursive-attrs-all-features.nix
new file mode 100644
index 0000000000..a234705b5e
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-recursive-attrs-all-features.nix
@@ -0,0 +1,13 @@
+let a = 1;
+in
+(rec {
+  inherit a;
+
+  b = {
+    c = a + 20;
+  };
+
+  inherit (b) c;
+
+  d = c * 2;
+}).d
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-regex-match.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-regex-match.exp
new file mode 100644
index 0000000000..9501035391
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-regex-match.exp
@@ -0,0 +1 @@
+[ true true false true true true true false false true false [ "foobar" ] [ "FOO" ] [ "/path/to/" "/path/to" "foobar" "nix" ] [ null null "foobar" "cc" ] ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-regex-match.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-regex-match.nix
new file mode 100644
index 0000000000..f774e00a21
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-regex-match.nix
@@ -0,0 +1,29 @@
+with builtins;
+
+let
+
+  matches = pat: s: match pat s != null;
+
+  splitFN = match "((.*)/)?([^/]*)\\.(nix|cc)";
+
+in
+
+[
+  (matches "foobar" "foobar")
+  (matches "fo*" "f")
+  (matches "fo+" "f")
+  (matches "fo*" "fo")
+  (matches "fo*" "foo")
+  (matches "fo+" "foo")
+  (matches "fo{1,2}" "foo")
+  (matches "fo{1,2}" "fooo")
+  (matches "fo*" "foobar")
+  (matches "[[:space:]]+([^[:space:]]+)[[:space:]]+" "  foo   ")
+  (matches "[[:space:]]+([[:upper:]]+)[[:space:]]+" "  foo   ")
+
+  (match "(.*)\\.nix" "foobar.nix")
+  (match "[[:space:]]+([[:upper:]]+)[[:space:]]+" "  FOO   ")
+
+  (splitFN "/path/to/foobar.nix")
+  (splitFN "foobar.cc")
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-remove.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-remove.exp
new file mode 100644
index 0000000000..8d38505c16
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-remove.exp
@@ -0,0 +1 @@
+456
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-remove.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-remove.nix
new file mode 100644
index 0000000000..62c5aa1fd4
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-remove.nix
@@ -0,0 +1,5 @@
+let {
+attrs = { x = 123; y = 456; };
+
+body = (removeAttrs attrs [ "x" ]).y;
+}
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-repeated-list-to-attrs.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-repeated-list-to-attrs.exp
new file mode 100644
index 0000000000..b4a1e66d6b
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-repeated-list-to-attrs.exp
@@ -0,0 +1 @@
+[ 1 2 ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-repeated-list-to-attrs.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-repeated-list-to-attrs.nix
new file mode 100644
index 0000000000..ed819d76c7
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-repeated-list-to-attrs.nix
@@ -0,0 +1,14 @@
+# Ensure that builtins.listToAttrs returns the first instance of a key.
+
+let
+  inherit (builtins) foldl' listToAttrs;
+
+  input = [{ name = "result"; value = 1; } { name = "result"; value = 2; }];
+
+  # foldl-based version of listToAttrs with the _opposite_ behaviour.
+  listToAttrs' = list: foldl' (acc: elem: acc // { ${elem.name} = elem.value; }) { } list;
+in
+[
+  (listToAttrs input).result
+  (listToAttrs' input).result
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-seq.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-seq.exp
new file mode 100644
index 0000000000..d81cc0710e
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-seq.exp
@@ -0,0 +1 @@
+42
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-seq.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-seq.nix
new file mode 100644
index 0000000000..fd0806c199
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-seq.nix
@@ -0,0 +1 @@
+(builtins.seq 1 2) + (builtins.seq [ (throw "list") ] 20) + (builtins.seq { boing = throw "set"; } 20)
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-simple-closure.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-simple-closure.exp
new file mode 100644
index 0000000000..7f8f011eb7
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-simple-closure.exp
@@ -0,0 +1 @@
+7
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-simple-closure.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-simple-closure.nix
new file mode 100644
index 0000000000..56445454fe
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-simple-closure.nix
@@ -0,0 +1 @@
+(a: b: a + b) 2 5
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-simple-interpol.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-simple-interpol.exp
new file mode 100644
index 0000000000..cd4bc1ab64
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-simple-interpol.exp
@@ -0,0 +1 @@
+"hello world"
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-simple-interpol.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-simple-interpol.nix
new file mode 100644
index 0000000000..125b0859ac
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-simple-interpol.nix
@@ -0,0 +1 @@
+"hello ${"world"}"
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-simple-let.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-simple-let.exp
new file mode 100644
index 0000000000..00750edc07
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-simple-let.exp
@@ -0,0 +1 @@
+3
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-simple-let.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-simple-let.nix
new file mode 100644
index 0000000000..b4da0f824a
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-simple-let.nix
@@ -0,0 +1,5 @@
+let
+  a = 1;
+  b = 2;
+in
+a + b
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-simple-nested-attrs.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-simple-nested-attrs.exp
new file mode 100644
index 0000000000..6db47b033e
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-simple-nested-attrs.exp
@@ -0,0 +1 @@
+{ a = { b = 42; }; }
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-simple-nested-attrs.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-simple-nested-attrs.nix
new file mode 100644
index 0000000000..a97394d165
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-simple-nested-attrs.nix
@@ -0,0 +1 @@
+{ a.b = 42; }
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-simple-recursive-attrs.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-simple-recursive-attrs.exp
new file mode 100644
index 0000000000..d81cc0710e
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-simple-recursive-attrs.exp
@@ -0,0 +1 @@
+42
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-simple-recursive-attrs.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-simple-recursive-attrs.nix
new file mode 100644
index 0000000000..c86ff80383
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-simple-recursive-attrs.nix
@@ -0,0 +1,4 @@
+(rec {
+  a = 21;
+  b = a * 2;
+}).b
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-simple-with.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-simple-with.exp
new file mode 100644
index 0000000000..d00491fd7e
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-simple-with.exp
@@ -0,0 +1 @@
+1
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-simple-with.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-simple-with.nix
new file mode 100644
index 0000000000..3d375be4f9
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-simple-with.nix
@@ -0,0 +1,6 @@
+let
+  set = {
+    a = 1;
+  };
+in
+with set; a
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-stable-sort.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-stable-sort.exp
new file mode 100644
index 0000000000..9d78376214
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-stable-sort.exp
@@ -0,0 +1 @@
+[ { index = 7; key = 0; } { index = 0; key = 1; } { index = 13; key = 1; } { index = 1; key = 2; } { index = 3; key = 2; } { index = 4; key = 2; } { index = 5; key = 2; } { index = 12; key = 2; } { index = 14; key = 2; } { index = 2; key = 3; } { index = 11; key = 3; } { index = 15; key = 3; } { index = 10; key = 4; } { index = 6; key = 5; } { index = 8; key = 5; } { index = 9; key = 5; } { index = 16; key = 22; } ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-stable-sort.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-stable-sort.nix
new file mode 100644
index 0000000000..9969e0a294
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-stable-sort.nix
@@ -0,0 +1,7 @@
+let
+  keys = [ 1 2 3 2 2 2 5 0 5 5 4 3 2 1 2 3 22 ];
+in
+
+builtins.sort
+  (a: b: a.key < b.key)
+  (builtins.genList (index: { inherit index; key = builtins.elemAt keys index; }) (builtins.length keys))
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-substring-propagate-catchables.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-substring-propagate-catchables.exp
new file mode 100644
index 0000000000..c508d5366f
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-substring-propagate-catchables.exp
@@ -0,0 +1 @@
+false
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-substring-propagate-catchables.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-substring-propagate-catchables.nix
new file mode 100644
index 0000000000..78d5dda38e
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-substring-propagate-catchables.nix
@@ -0,0 +1 @@
+(builtins.tryEval (builtins.substring 0 4 (throw "jill"))).success
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-test-catchables-in-default-args.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-test-catchables-in-default-args.exp
new file mode 100644
index 0000000000..c508d5366f
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-test-catchables-in-default-args.exp
@@ -0,0 +1 @@
+false
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-test-catchables-in-default-args.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-test-catchables-in-default-args.nix
new file mode 100644
index 0000000000..0523cf864c
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-test-catchables-in-default-args.nix
@@ -0,0 +1 @@
+(builtins.tryEval (({ foo ? throw "up" }: if foo then 1 else 2) { })).success
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-test-catchables-in-implications.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-test-catchables-in-implications.exp
new file mode 100644
index 0000000000..c508d5366f
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-test-catchables-in-implications.exp
@@ -0,0 +1 @@
+false
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-test-catchables-in-implications.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-test-catchables-in-implications.nix
new file mode 100644
index 0000000000..126738d883
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-test-catchables-in-implications.nix
@@ -0,0 +1 @@
+(builtins.tryEval (({ foo ? throw "up" }: foo -> true) { })).success
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-thunked-functor.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-thunked-functor.exp
new file mode 100644
index 0000000000..d81cc0710e
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-thunked-functor.exp
@@ -0,0 +1 @@
+42
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-thunked-functor.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-thunked-functor.nix
new file mode 100644
index 0000000000..568a5c5413
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-thunked-functor.nix
@@ -0,0 +1,8 @@
+let
+  __functor = f;
+  f = self: x: self.out * x;
+in
+{
+  inherit __functor;
+  out = 21;
+} 2
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-thunked-if.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-thunked-if.exp
new file mode 100644
index 0000000000..ffcd4415b0
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-thunked-if.exp
@@ -0,0 +1 @@
+{ }
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-thunked-if.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-thunked-if.nix
new file mode 100644
index 0000000000..3810ebe784
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-thunked-if.nix
@@ -0,0 +1,8 @@
+let
+  a = { };
+in
+let
+  c = if builtins.isFunction a then a b else a;
+  b = { };
+in
+c
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-thunked-string-interpolation.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-thunked-string-interpolation.exp
new file mode 100644
index 0000000000..fc2f21e930
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-thunked-string-interpolation.exp
@@ -0,0 +1 @@
+"strict literal"
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-thunked-string-interpolation.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-thunked-string-interpolation.nix
new file mode 100644
index 0000000000..bd3555bb24
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-thunked-string-interpolation.nix
@@ -0,0 +1,7 @@
+let
+  final = { text = "strict literal"; inherit x y; };
+  x = "lazy ${throw "interpolation"}";
+  y = "${throw "also lazy!"}";
+in
+
+final.text
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-thunked-with.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-thunked-with.exp
new file mode 100644
index 0000000000..d81cc0710e
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-thunked-with.exp
@@ -0,0 +1 @@
+42
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-thunked-with.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-thunked-with.nix
new file mode 100644
index 0000000000..799408b2e6
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-thunked-with.nix
@@ -0,0 +1,8 @@
+# Creates a `with` across multiple thunk boundaries.
+
+let
+  set = {
+    a = with { b = 42; }; b;
+  };
+in
+set.a
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-toplevel-finaliser.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-toplevel-finaliser.exp
new file mode 100644
index 0000000000..edca9baca9
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-toplevel-finaliser.exp
@@ -0,0 +1 @@
+{ a = 1; b = 2; c = 3; }
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-toplevel-finaliser.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-toplevel-finaliser.nix
new file mode 100644
index 0000000000..5f25f80671
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-toplevel-finaliser.nix
@@ -0,0 +1,11 @@
+# A simple expression with upvalue resolution beyond the target stack
+# index of the root expression.
+
+let
+  a = 1;
+  b = 2;
+  c = 3;
+in
+{
+  inherit a b c;
+}
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-tryeval-thunk-twice.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-tryeval-thunk-twice.exp
new file mode 100644
index 0000000000..b5ba0757c1
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-tryeval-thunk-twice.exp
@@ -0,0 +1 @@
+[ { success = false; value = false; } { success = false; value = false; } ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-tryeval-thunk-twice.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-tryeval-thunk-twice.nix
new file mode 100644
index 0000000000..1749643f82
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-tryeval-thunk-twice.nix
@@ -0,0 +1 @@
+let x = throw "lol"; in builtins.map (f: f x) [ builtins.tryEval builtins.tryEval ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-tryeval.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-tryeval.exp
new file mode 100644
index 0000000000..8b6ed7dbac
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-tryeval.exp
@@ -0,0 +1 @@
+{ v = false; w = { success = false; value = false; }; x = { success = true; value = "x"; }; y = { success = false; value = false; }; z = { success = false; value = false; }; }
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-tryeval.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-tryeval.nix
new file mode 100644
index 0000000000..e2357c7987
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-tryeval.nix
@@ -0,0 +1,7 @@
+{
+  v = (builtins.tryEval (toString <oink>)).value;
+  w = builtins.tryEval <nope>;
+  x = builtins.tryEval "x";
+  y = builtins.tryEval (assert false; "y");
+  z = builtins.tryEval (throw "bla");
+}
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-unpoison-scope.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-unpoison-scope.exp
new file mode 100644
index 0000000000..5462431496
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-unpoison-scope.exp
@@ -0,0 +1 @@
+[ true false null 1 2 3 ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-unpoison-scope.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-unpoison-scope.nix
new file mode 100644
index 0000000000..539735a8ef
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-unpoison-scope.nix
@@ -0,0 +1,10 @@
+let
+  poisoned =
+    let
+      true = 1;
+      false = 2;
+      null = 3;
+    in
+    [ true false null ];
+in
+[ true false null ] ++ poisoned
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-useless-inherit-with.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-useless-inherit-with.exp
new file mode 100644
index 0000000000..d00491fd7e
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-useless-inherit-with.exp
@@ -0,0 +1 @@
+1
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-useless-inherit-with.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-useless-inherit-with.nix
new file mode 100644
index 0000000000..dd768c1aca
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-useless-inherit-with.nix
@@ -0,0 +1,16 @@
+# Normally using an `inherit` without a source attribute set within a
+# `let` is a no-op, *unless* there is a with in-scope that might
+# provide the value.
+
+# Provide a dynamic `x` identifier in the scope.
+with ({ x = 1; });
+
+# inherit this `x` as a static identifier
+let inherit x;
+
+  # Provide another dynamic `x` identifier
+in
+with ({ x = 3; });
+
+# Inherited static identifier should have precedence
+x
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-value-display.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-value-display.exp
new file mode 100644
index 0000000000..c7e3fc6503
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-value-display.exp
@@ -0,0 +1 @@
+[ null true false 42 42 "foo\t\nbar" /home/arthur [ 1 2 3 ] <LAMBDA> <PRIMOP> <PRIMOP-APP> { hello = "world"; } ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-value-display.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-value-display.nix
new file mode 100644
index 0000000000..d34ed1697e
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-value-display.nix
@@ -0,0 +1,16 @@
+# Sanity check of how values are rendered by tvix vs. nix-instantiate(1).
+# Ensures that we can use this test suite to compare against C++ Nix.
+[
+  null
+  true
+  false
+  42
+  42.0
+  "foo\t\nbar"
+  /home/arthur
+  [ 1 2 3 ]
+  (x: x)
+  builtins.add
+  (builtins.substring 0 1)
+  { hello = "world"; }
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-value-pointer-compare.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-value-pointer-compare.exp
new file mode 100644
index 0000000000..27ba77ddaf
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-value-pointer-compare.exp
@@ -0,0 +1 @@
+true
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-value-pointer-compare.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-value-pointer-compare.nix
new file mode 100644
index 0000000000..c2ca913af2
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-value-pointer-compare.nix
@@ -0,0 +1,6 @@
+# For an explanation of this behavior see //tvix/docs/value-pointer-equality.md
+let
+  f = owo: "thia";
+in
+
+[ f 42 ] > [ f 21 ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-value-pointer-equality.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-value-pointer-equality.exp
new file mode 100644
index 0000000000..aec30f297a
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-value-pointer-equality.exp
@@ -0,0 +1 @@
+[ true true true true false false false true true true true true true true true true false false ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-value-pointer-equality.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-value-pointer-equality.nix
new file mode 100644
index 0000000000..b5cfbeb12e
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-value-pointer-equality.nix
@@ -0,0 +1,46 @@
+# For an explanation of this behavior see //tvix/docs/value-pointer-equality.md
+let
+  # Some incomparable values
+  f = MC: "Boing";
+  t = [ (throw "is a little blue man") ];
+  a = { "with" = abort "headphones and a big smile."; };
+
+  # Aliases
+  f' = f;
+  t' = t;
+  a' = a;
+
+  peq1 = a: b: [ a ] == [ b ];
+  peq2 = a: b: { x = a; } == { x = b; };
+in
+
+[
+  # pointer equality of functions
+  (peq1 f f)
+  (peq2 f f)
+  (peq1 f f')
+  (peq2 f f')
+
+  # encapsulation is necessary for pointer equality
+  (f == f)
+  (f == f')
+  # works with !=
+  ([ f ] != [ f' ])
+
+  # thunks that fail to evaluated wrapped in sets/lists
+  (peq1 t t)
+  (peq2 t t)
+  (peq1 a a)
+  (peq2 a a)
+  (peq1 t t')
+  (peq2 t t')
+  (peq1 a' a)
+  (peq2 a' a)
+
+  # function equality with builtins.elem
+  (builtins.elem f [ 21 f 42 ])
+
+  # pointer inequality
+  (peq1 f (x: x))
+  (peq2 (x: x) f)
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-with-closure.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-with-closure.exp
new file mode 100644
index 0000000000..fa8f08cb6f
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-with-closure.exp
@@ -0,0 +1 @@
+150
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-with-closure.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-with-closure.nix
new file mode 100644
index 0000000000..7e2f7c073b
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-with-closure.nix
@@ -0,0 +1,5 @@
+# Upvalues from `with` require special runtime handling. Do they work?
+let
+  f = with { a = 15; }; n: n * a;
+in
+f 10
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-with-in-dynamic-key.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-with-in-dynamic-key.exp
new file mode 100644
index 0000000000..d81cc0710e
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-with-in-dynamic-key.exp
@@ -0,0 +1 @@
+42
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-with-in-dynamic-key.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-with-in-dynamic-key.nix
new file mode 100644
index 0000000000..bf221746c0
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-with-in-dynamic-key.nix
@@ -0,0 +1,13 @@
+# Tests correct tracking of stack indices within construction of an
+# attribute set. Dynamic keys can be any expression, so something that
+# is extremely sensitive to stack offsets (like `with`) can be tricky.
+
+let
+  set1 = { key = "b"; };
+  set2 = {
+    a = 20;
+    ${with set1; key} = 20;
+    ${with { key = "c"; }; key} = 2;
+  };
+in
+set2.a + set2.b + set2.c
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-with-in-list.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-with-in-list.exp
new file mode 100644
index 0000000000..5776134d0e
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-with-in-list.exp
@@ -0,0 +1 @@
+[ 1 2 3 ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-with-in-list.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-with-in-list.nix
new file mode 100644
index 0000000000..3e85cbee45
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-with-in-list.nix
@@ -0,0 +1,14 @@
+# This code causes a situation where a list element causes an
+# additional phantom value to temporarily be placed on the locals
+# stack, which must be correctly accounted for by the compiler.
+
+let
+  set = {
+    value = 2;
+  };
+in
+[
+  1
+  (with set; value)
+  3
+]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-xml.exp.xml b/tvix/eval/src/tests/tvix_tests/eval-okay-xml.exp.xml
new file mode 100644
index 0000000000..1521bcc97a
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-xml.exp.xml
@@ -0,0 +1,41 @@
+<?xml version='1.0' encoding='utf-8'?>
+<expr>
+  <attrs>
+    <attr name="attrspat">
+      <function>
+        <attrspat name="args">
+          <attr name="x" />
+          <attr name="y" />
+          <attr name="z" />
+        </attrspat>
+      </function>
+    </attr>
+    <attr name="attrspat-ellipsis">
+      <function>
+        <attrspat ellipsis="1" name="args">
+          <attr name="x" />
+          <attr name="y" />
+          <attr name="z" />
+        </attrspat>
+      </function>
+    </attr>
+    <attr name="noattrspat">
+      <function>
+        <attrspat>
+          <attr name="x" />
+          <attr name="y" />
+          <attr name="z" />
+        </attrspat>
+      </function>
+    </attr>
+    <attr name="noattrspat-ellipsis">
+      <function>
+        <attrspat ellipsis="1">
+          <attr name="x" />
+          <attr name="y" />
+          <attr name="z" />
+        </attrspat>
+      </function>
+    </attr>
+  </attrs>
+</expr>
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-xml.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-xml.nix
new file mode 100644
index 0000000000..3cc5acf430
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-xml.nix
@@ -0,0 +1,7 @@
+{
+  attrspat = args@{ x, y, z }: x;
+  attrspat-ellipsis = args@{ x, y, z, ... }: x;
+
+  noattrspat = { x, y, z }: x;
+  noattrspat-ellipsis = { x, y, z, ... }: x;
+}
diff --git a/tvix/eval/src/tests/tvix_tests/identity-bool-false.nix b/tvix/eval/src/tests/tvix_tests/identity-bool-false.nix
new file mode 100644
index 0000000000..c508d5366f
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/identity-bool-false.nix
@@ -0,0 +1 @@
+false
diff --git a/tvix/eval/src/tests/tvix_tests/identity-bool-true.nix b/tvix/eval/src/tests/tvix_tests/identity-bool-true.nix
new file mode 100644
index 0000000000..27ba77ddaf
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/identity-bool-true.nix
@@ -0,0 +1 @@
+true
diff --git a/tvix/eval/src/tests/tvix_tests/identity-dollar-escape.nix b/tvix/eval/src/tests/tvix_tests/identity-dollar-escape.nix
new file mode 100644
index 0000000000..08951d7637
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/identity-dollar-escape.nix
@@ -0,0 +1 @@
+"\${foobar}"
diff --git a/tvix/eval/src/tests/tvix_tests/identity-empty-attrs.nix b/tvix/eval/src/tests/tvix_tests/identity-empty-attrs.nix
new file mode 100644
index 0000000000..ffcd4415b0
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/identity-empty-attrs.nix
@@ -0,0 +1 @@
+{ }
diff --git a/tvix/eval/src/tests/tvix_tests/identity-empty-list.nix b/tvix/eval/src/tests/tvix_tests/identity-empty-list.nix
new file mode 100644
index 0000000000..1e3ec7217a
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/identity-empty-list.nix
@@ -0,0 +1 @@
+[ ]
diff --git a/tvix/eval/src/tests/tvix_tests/identity-flat-attrs.nix b/tvix/eval/src/tests/tvix_tests/identity-flat-attrs.nix
new file mode 100644
index 0000000000..e7c2ae18a6
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/identity-flat-attrs.nix
@@ -0,0 +1 @@
+{ a = 15; b = "string"; c = null; }
diff --git a/tvix/eval/src/tests/tvix_tests/identity-float.nix b/tvix/eval/src/tests/tvix_tests/identity-float.nix
new file mode 100644
index 0000000000..bf77d54968
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/identity-float.nix
@@ -0,0 +1 @@
+4.2
diff --git a/tvix/eval/src/tests/tvix_tests/identity-heterogeneous-list.nix b/tvix/eval/src/tests/tvix_tests/identity-heterogeneous-list.nix
new file mode 100644
index 0000000000..87f7fb0d06
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/identity-heterogeneous-list.nix
@@ -0,0 +1 @@
+[ 1 2.1 "three" null ]
diff --git a/tvix/eval/src/tests/tvix_tests/identity-homogeneous-float-list.nix b/tvix/eval/src/tests/tvix_tests/identity-homogeneous-float-list.nix
new file mode 100644
index 0000000000..48e6655fb1
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/identity-homogeneous-float-list.nix
@@ -0,0 +1 @@
+[ 4.2 6.9 13.37 ]
diff --git a/tvix/eval/src/tests/tvix_tests/identity-homogeneous-int-list.nix b/tvix/eval/src/tests/tvix_tests/identity-homogeneous-int-list.nix
new file mode 100644
index 0000000000..d23a5c3814
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/identity-homogeneous-int-list.nix
@@ -0,0 +1 @@
+[ 0 1 2 3 4 5 7 8 9 ]
diff --git a/tvix/eval/src/tests/tvix_tests/identity-homogeneous-string-list.nix b/tvix/eval/src/tests/tvix_tests/identity-homogeneous-string-list.nix
new file mode 100644
index 0000000000..d78a54e5b0
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/identity-homogeneous-string-list.nix
@@ -0,0 +1 @@
+[ "string" "list" ]
diff --git a/tvix/eval/src/tests/tvix_tests/identity-int.nix b/tvix/eval/src/tests/tvix_tests/identity-int.nix
new file mode 100644
index 0000000000..d81cc0710e
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/identity-int.nix
@@ -0,0 +1 @@
+42
diff --git a/tvix/eval/src/tests/tvix_tests/identity-kv-attrs.nix b/tvix/eval/src/tests/tvix_tests/identity-kv-attrs.nix
new file mode 100644
index 0000000000..f1398b8d05
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/identity-kv-attrs.nix
@@ -0,0 +1 @@
+{ name = "foo"; value = 12; }
diff --git a/tvix/eval/src/tests/tvix_tests/identity-nested-attrs.nix b/tvix/eval/src/tests/tvix_tests/identity-nested-attrs.nix
new file mode 100644
index 0000000000..6a139452ef
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/identity-nested-attrs.nix
@@ -0,0 +1 @@
+{ a = { b = null; }; }
diff --git a/tvix/eval/src/tests/tvix_tests/identity-null.nix b/tvix/eval/src/tests/tvix_tests/identity-null.nix
new file mode 100644
index 0000000000..19765bd501
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/identity-null.nix
@@ -0,0 +1 @@
+null
diff --git a/tvix/eval/src/tests/tvix_tests/identity-quoted-attrname-assert.nix b/tvix/eval/src/tests/tvix_tests/identity-quoted-attrname-assert.nix
new file mode 100644
index 0000000000..575b1af588
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/identity-quoted-attrname-assert.nix
@@ -0,0 +1 @@
+{ "assert" = true; }
diff --git a/tvix/eval/src/tests/tvix_tests/identity-quoted-attrname-else.nix b/tvix/eval/src/tests/tvix_tests/identity-quoted-attrname-else.nix
new file mode 100644
index 0000000000..7601f14b32
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/identity-quoted-attrname-else.nix
@@ -0,0 +1 @@
+{ "else" = true; }
diff --git a/tvix/eval/src/tests/tvix_tests/identity-quoted-attrname-if.nix b/tvix/eval/src/tests/tvix_tests/identity-quoted-attrname-if.nix
new file mode 100644
index 0000000000..1c391fc9a3
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/identity-quoted-attrname-if.nix
@@ -0,0 +1 @@
+{ "if" = true; }
diff --git a/tvix/eval/src/tests/tvix_tests/identity-quoted-attrname-in.nix b/tvix/eval/src/tests/tvix_tests/identity-quoted-attrname-in.nix
new file mode 100644
index 0000000000..b4f238651d
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/identity-quoted-attrname-in.nix
@@ -0,0 +1 @@
+{ "in" = true; }
diff --git a/tvix/eval/src/tests/tvix_tests/identity-quoted-attrname-inherit.nix b/tvix/eval/src/tests/tvix_tests/identity-quoted-attrname-inherit.nix
new file mode 100644
index 0000000000..e62ed32b04
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/identity-quoted-attrname-inherit.nix
@@ -0,0 +1 @@
+{ "inherit" = true; }
diff --git a/tvix/eval/src/tests/tvix_tests/identity-quoted-attrname-let.nix b/tvix/eval/src/tests/tvix_tests/identity-quoted-attrname-let.nix
new file mode 100644
index 0000000000..196ec7cc88
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/identity-quoted-attrname-let.nix
@@ -0,0 +1 @@
+{ "let" = true; }
diff --git a/tvix/eval/src/tests/tvix_tests/identity-quoted-attrname-rec.nix b/tvix/eval/src/tests/tvix_tests/identity-quoted-attrname-rec.nix
new file mode 100644
index 0000000000..d2c4f93a2a
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/identity-quoted-attrname-rec.nix
@@ -0,0 +1 @@
+{ "rec" = true; }
diff --git a/tvix/eval/src/tests/tvix_tests/identity-quoted-attrname-then.nix b/tvix/eval/src/tests/tvix_tests/identity-quoted-attrname-then.nix
new file mode 100644
index 0000000000..f2af8d6970
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/identity-quoted-attrname-then.nix
@@ -0,0 +1 @@
+{ "then" = true; }
diff --git a/tvix/eval/src/tests/tvix_tests/identity-quoted-attrname-with.nix b/tvix/eval/src/tests/tvix_tests/identity-quoted-attrname-with.nix
new file mode 100644
index 0000000000..cbcfa970c2
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/identity-quoted-attrname-with.nix
@@ -0,0 +1 @@
+{ "with" = true; }
diff --git a/tvix/eval/src/tests/tvix_tests/identity-signed-float.nix b/tvix/eval/src/tests/tvix_tests/identity-signed-float.nix
new file mode 100644
index 0000000000..50c9d06aa5
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/identity-signed-float.nix
@@ -0,0 +1 @@
+-4.2
diff --git a/tvix/eval/src/tests/tvix_tests/identity-signed-int.nix b/tvix/eval/src/tests/tvix_tests/identity-signed-int.nix
new file mode 100644
index 0000000000..6a0e60d48b
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/identity-signed-int.nix
@@ -0,0 +1 @@
+-42
diff --git a/tvix/eval/src/tests/tvix_tests/identity-string.nix b/tvix/eval/src/tests/tvix_tests/identity-string.nix
new file mode 100644
index 0000000000..d71ddbcf82
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/identity-string.nix
@@ -0,0 +1 @@
+"test string"
diff --git a/tvix/eval/src/tests/tvix_tests/lib.nix b/tvix/eval/src/tests/tvix_tests/lib.nix
new file mode 100644
index 0000000000..ab509bc85f
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/lib.nix
@@ -0,0 +1,64 @@
+with builtins;
+
+rec {
+
+  fold = op: nul: list:
+    if list == [ ]
+    then nul
+    else op (head list) (fold op nul (tail list));
+
+  concat =
+    fold (x: y: x + y) "";
+
+  and = fold (x: y: x && y) true;
+
+  flatten = x:
+    if isList x
+    then fold (x: y: (flatten x) ++ y) [ ] x
+    else [ x ];
+
+  sum = foldl' (x: y: add x y) 0;
+
+  hasSuffix = ext: fileName:
+    let
+      lenFileName = stringLength fileName;
+      lenExt = stringLength ext;
+    in
+    !(lessThan lenFileName lenExt) &&
+    substring (sub lenFileName lenExt) lenFileName fileName == ext;
+
+  # Split a list at the given position.
+  splitAt = pos: list:
+    if pos == 0 then { first = [ ]; second = list; } else
+    if list == [ ] then { first = [ ]; second = [ ]; } else
+    let res = splitAt (sub pos 1) (tail list);
+    in { first = [ (head list) ] ++ res.first; second = res.second; };
+
+  # Stable merge sort.
+  sortBy = comp: list:
+    if lessThan 1 (length list)
+    then
+      let
+        split = splitAt (div (length list) 2) list;
+        first = sortBy comp split.first;
+        second = sortBy comp split.second;
+      in
+      mergeLists comp first second
+    else list;
+
+  mergeLists = comp: list1: list2:
+    if list1 == [ ] then list2 else
+    if list2 == [ ] then list1 else
+    if comp (head list2) (head list1) then [ (head list2) ] ++ mergeLists comp list1 (tail list2) else
+    [ (head list1) ] ++ mergeLists comp (tail list1) list2;
+
+  id = x: x;
+
+  const = x: y: x;
+
+  range = first: last:
+    if first > last
+    then [ ]
+    else genList (n: first + n) (last - first + 1);
+
+}
diff --git a/tvix/eval/src/tests/tvix_tests/notyetpassing/eval-fail-builtins-genericClosure-uncomparable-keys.nix b/tvix/eval/src/tests/tvix_tests/notyetpassing/eval-fail-builtins-genericClosure-uncomparable-keys.nix
new file mode 100644
index 0000000000..d4e93e1f28
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/notyetpassing/eval-fail-builtins-genericClosure-uncomparable-keys.nix
@@ -0,0 +1,9 @@
+# Attribute sets can't be compared, only checked for equality
+builtins.genericClosure {
+  startSet = [
+    { key = { foo = 21; }; }
+  ];
+  operator = _: [
+    { key = { bar = 21; }; }
+  ];
+}
diff --git a/tvix/eval/src/tests/tvix_tests/notyetpassing/eval-fail-builtins-genericClosure-uncomparable-keys2.nix b/tvix/eval/src/tests/tvix_tests/notyetpassing/eval-fail-builtins-genericClosure-uncomparable-keys2.nix
new file mode 100644
index 0000000000..0589a3ab59
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/notyetpassing/eval-fail-builtins-genericClosure-uncomparable-keys2.nix
@@ -0,0 +1,12 @@
+let
+  id = x: x;
+in
+
+builtins.genericClosure {
+  startSet = [{ key = id; first = true; }];
+  operator =
+    { first, ... }:
+    if first then [
+      { key = id; first = false; }
+    ] else [ ];
+}
diff --git a/tvix/eval/src/tests/tvix_tests/notyetpassing/eval-okay-builtins-set-pointer-equality.exp b/tvix/eval/src/tests/tvix_tests/notyetpassing/eval-okay-builtins-set-pointer-equality.exp
new file mode 100644
index 0000000000..097eb2033a
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/notyetpassing/eval-okay-builtins-set-pointer-equality.exp
@@ -0,0 +1 @@
+[ true true true true true true true true true true ]
diff --git a/tvix/eval/src/tests/tvix_tests/notyetpassing/eval-okay-builtins-set-pointer-equality.nix b/tvix/eval/src/tests/tvix_tests/notyetpassing/eval-okay-builtins-set-pointer-equality.nix
new file mode 100644
index 0000000000..aa2a0a1e19
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/notyetpassing/eval-okay-builtins-set-pointer-equality.nix
@@ -0,0 +1,25 @@
+let
+  alias = builtins;
+in
+
+[
+  (builtins == builtins)
+  (alias == builtins)
+  (builtins == builtins.builtins)
+  (builtins.builtins == builtins.builtins)
+  (builtins.builtins == builtins.builtins.builtins)
+  (alias == alias)
+  (alias == builtins.builtins)
+  ([ builtins ] == [ builtins ])
+
+  # Surprisingly the following expressions don't work. They are
+  # here for documentation purposes and covered only
+  # by eval-okay-select-pointer-inequality.nix. Reasoning is that
+  # we may not want / be able to replicate this behavior at all.
+  #   ([ builtins.add ] == [ builtins.add ])
+  #   ({ inherit (builtins) import; } == { inherit (builtins) import; })
+
+  # These expressions work as expected, however:
+  (let x = { inherit (builtins) add; }; in x == x)
+  (let inherit (builtins) add; in [ add ] == [ add ])
+]
diff --git a/tvix/eval/src/tests/tvix_tests/notyetpassing/eval-okay-cycle-display-cpp-nix-2.13.exp b/tvix/eval/src/tests/tvix_tests/notyetpassing/eval-okay-cycle-display-cpp-nix-2.13.exp
new file mode 100644
index 0000000000..9c44023f02
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/notyetpassing/eval-okay-cycle-display-cpp-nix-2.13.exp
@@ -0,0 +1 @@
+[ { car = 42; cdr = «repeated»; } [ «repeated» «repeated» «repeated» ] { val = 42; wal = «repeated»; xal = «repeated»; } { tail1 = «repeated»; tail2 = «repeated»; val = 42; } { tail1 = «repeated»; tail2 = «repeated»; val = 21; } ]
diff --git a/tvix/eval/src/tests/tvix_tests/notyetpassing/eval-okay-cycle-display-cpp-nix-2.13.nix b/tvix/eval/src/tests/tvix_tests/notyetpassing/eval-okay-cycle-display-cpp-nix-2.13.nix
new file mode 100644
index 0000000000..ac849a58fe
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/notyetpassing/eval-okay-cycle-display-cpp-nix-2.13.nix
@@ -0,0 +1,34 @@
+let
+  linkedList = {
+    car = 42;
+    cdr = linkedList;
+  };
+
+  list = [
+    linkedList
+    linkedList
+    linkedList
+  ];
+
+  set = {
+    val = 42;
+    wal = set;
+    xal = set;
+  };
+
+  multiTail = {
+    val = 42;
+    tail1 = multiTail;
+    tail2 = multiTail;
+  };
+in
+
+[
+  linkedList
+  list
+  set
+
+  # In C++ Nix 2.3 these would be displayed differently
+  multiTail
+  (let multiTail = { val = 21; tail1 = multiTail; tail2 = multiTail; }; in multiTail)
+]
diff --git a/tvix/eval/src/tests/tvix_tests/notyetpassing/eval-okay-minimal-2.3-builtins.exp b/tvix/eval/src/tests/tvix_tests/notyetpassing/eval-okay-minimal-2.3-builtins.exp
new file mode 100644
index 0000000000..967fc858bc
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/notyetpassing/eval-okay-minimal-2.3-builtins.exp
@@ -0,0 +1 @@
+[ "abort" "add" "addErrorContext" "all" "any" "appendContext" "attrNames" "attrValues" "baseNameOf" "bitAnd" "bitOr" "bitXor" "builtins" "catAttrs" "compareVersions" "concatLists" "concatMap" "concatStringsSep" "currentSystem" "currentTime" "deepSeq" "derivation" "derivationStrict" "dirOf" "div" "elem" "elemAt" "false" "fetchGit" "fetchMercurial" "fetchTarball" "fetchurl" "filter" "filterSource" "findFile" "foldl'" "fromJSON" "fromTOML" "functionArgs" "genList" "genericClosure" "getAttr" "getContext" "getEnv" "hasAttr" "hasContext" "hashFile" "hashString" "head" "import" "intersectAttrs" "isAttrs" "isBool" "isFloat" "isFunction" "isInt" "isList" "isNull" "isPath" "isString" "langVersion" "length" "lessThan" "listToAttrs" "map" "mapAttrs" "match" "mul" "nixPath" "nixVersion" "null" "parseDrvName" "partition" "path" "pathExists" "placeholder" "readDir" "readFile" "removeAttrs" "replaceStrings" "scopedImport" "seq" "sort" "split" "splitVersion" "storeDir" "storePath" "stringLength" "sub" "substring" "tail" "throw" "toFile" "toJSON" "toPath" "toString" "toXML" "trace" "true" "tryEval" "typeOf" "unsafeDiscardOutputDependency" "unsafeDiscardStringContext" "unsafeGetAttrPos" ]
diff --git a/tvix/eval/src/tests/tvix_tests/notyetpassing/eval-okay-minimal-2.3-builtins.nix b/tvix/eval/src/tests/tvix_tests/notyetpassing/eval-okay-minimal-2.3-builtins.nix
new file mode 100644
index 0000000000..4480daecd9
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/notyetpassing/eval-okay-minimal-2.3-builtins.nix
@@ -0,0 +1,122 @@
+# This tests verifies that the Nix implementation evaluating this has at least
+# all the builtins given in `minimalBuiltins`. We don't test a precise list of
+# builtins since we accept that there will always be difference between the
+# builtins sets of Tvix, C++ Nix 2.3 and newer C++ Nix versions, as new builtins
+# are added.
+#
+# Tvix also may choose never to implement some builtins if they are only useful
+# for flakes or perform well enough via the shims nixpkgs usually provides.
+
+let
+  # C++ Nix 2.3 builtins except valueSize which is removed in later versions
+  minimalBuiltins = [
+    "abort"
+    "add"
+    "addErrorContext"
+    "all"
+    "any"
+    "appendContext"
+    "attrNames"
+    "attrValues"
+    "baseNameOf"
+    "bitAnd"
+    "bitOr"
+    "bitXor"
+    "builtins"
+    "catAttrs"
+    "compareVersions"
+    "concatLists"
+    "concatMap"
+    "concatStringsSep"
+    "currentSystem"
+    "currentTime"
+    "deepSeq"
+    "derivation"
+    "derivationStrict"
+    "dirOf"
+    "div"
+    "elem"
+    "elemAt"
+    "false"
+    "fetchGit"
+    "fetchMercurial"
+    "fetchTarball"
+    "fetchurl"
+    "filter"
+    "filterSource"
+    "findFile"
+    "foldl'"
+    "fromJSON"
+    "fromTOML"
+    "functionArgs"
+    "genList"
+    "genericClosure"
+    "getAttr"
+    "getContext"
+    "getEnv"
+    "hasAttr"
+    "hasContext"
+    "hashFile"
+    "hashString"
+    "head"
+    "import"
+    "intersectAttrs"
+    "isAttrs"
+    "isBool"
+    "isFloat"
+    "isFunction"
+    "isInt"
+    "isList"
+    "isNull"
+    "isPath"
+    "isString"
+    "langVersion"
+    "length"
+    "lessThan"
+    "listToAttrs"
+    "map"
+    "mapAttrs"
+    "match"
+    "mul"
+    "nixPath"
+    "nixVersion"
+    "null"
+    "parseDrvName"
+    "partition"
+    "path"
+    "pathExists"
+    "placeholder"
+    "readDir"
+    "readFile"
+    "removeAttrs"
+    "replaceStrings"
+    "scopedImport"
+    "seq"
+    "sort"
+    "split"
+    "splitVersion"
+    "storeDir"
+    "storePath"
+    "stringLength"
+    "sub"
+    "substring"
+    "tail"
+    "throw"
+    "toFile"
+    "toJSON"
+    "toPath"
+    "toString"
+    "toXML"
+    "trace"
+    "true"
+    "tryEval"
+    "typeOf"
+    "unsafeDiscardOutputDependency"
+    "unsafeDiscardStringContext"
+    "unsafeGetAttrPos"
+  ];
+
+  intersectLists = as: bs: builtins.filter (a: builtins.elem a bs) as;
+in
+
+intersectLists minimalBuiltins (builtins.attrNames builtins)
diff --git a/tvix/eval/src/tests/tvix_tests/notyetpassing/eval-okay-non-identifier-pointer-inequality.exp b/tvix/eval/src/tests/tvix_tests/notyetpassing/eval-okay-non-identifier-pointer-inequality.exp
new file mode 100644
index 0000000000..69fd1d0847
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/notyetpassing/eval-okay-non-identifier-pointer-inequality.exp
@@ -0,0 +1 @@
+[ false false false false false true false false ]
diff --git a/tvix/eval/src/tests/tvix_tests/notyetpassing/eval-okay-non-identifier-pointer-inequality.nix b/tvix/eval/src/tests/tvix_tests/notyetpassing/eval-okay-non-identifier-pointer-inequality.nix
new file mode 100644
index 0000000000..821aa47a0d
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/notyetpassing/eval-okay-non-identifier-pointer-inequality.nix
@@ -0,0 +1,28 @@
+# C++ Nix frequently creates copies of Value structs when evaluating
+# a variety of expressions. As a result, pointer equality doesn't
+# work for many (all?) expressions that go beyond simple identifier
+# access from the scope: Even if the inner representation of the
+# value still has the same memory location, C++ Nix has created
+# a copy of the struct that holds the pointer to this memory.
+# Since pointer equality is established via the location of
+# the latter, not the former, the values are no longer equal
+# by pointer.
+let
+  foo = { bar = x: x; };
+
+  id = x: x;
+in
+
+[
+  ({ inherit (foo) bar; } == { inherit (foo) bar; })
+  ([ foo.bar ] == [ foo.bar ])
+
+  ([ builtins.add ] == [ builtins.add ])
+  ({ inherit (builtins) import; } == { inherit (builtins) import; })
+
+  ([ (id id) ] == [ (id id) ])
+  ([ id ] == [ id ])
+
+  (with foo; [ bar ] == [ bar ])
+  (with builtins; [ add ] == [ add ])
+]
diff --git a/tvix/eval/src/tests/tvix_tests/observable-eval-cache1.nix b/tvix/eval/src/tests/tvix_tests/observable-eval-cache1.nix
new file mode 100644
index 0000000000..b5f3f59a79
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/observable-eval-cache1.nix
@@ -0,0 +1 @@
+let id = x: x; in { inherit id; }
diff --git a/tvix/eval/src/tests/tvix_tests/observable-eval-cache2.nix b/tvix/eval/src/tests/tvix_tests/observable-eval-cache2.nix
new file mode 120000
index 0000000000..7f69c0eb47
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/observable-eval-cache2.nix
@@ -0,0 +1 @@
+observable-eval-cache1.nix
\ No newline at end of file
diff --git a/tvix/eval/src/tests/tvix_tests/observable-eval-cache3.nix b/tvix/eval/src/tests/tvix_tests/observable-eval-cache3.nix
new file mode 100644
index 0000000000..b5f3f59a79
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/observable-eval-cache3.nix
@@ -0,0 +1 @@
+let id = x: x; in { inherit id; }
diff --git a/tvix/eval/src/tests/tvix_tests/readDir/bar b/tvix/eval/src/tests/tvix_tests/readDir/bar
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/readDir/bar
diff --git a/tvix/eval/src/tests/tvix_tests/readDir/foo/.keep b/tvix/eval/src/tests/tvix_tests/readDir/foo/.keep
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/readDir/foo/.keep
diff --git a/tvix/eval/src/upvalues.rs b/tvix/eval/src/upvalues.rs
new file mode 100644
index 0000000000..687d6850cc
--- /dev/null
+++ b/tvix/eval/src/upvalues.rs
@@ -0,0 +1,86 @@
+//! This module encapsulates some logic for upvalue handling, which is
+//! relevant to both thunks (delayed computations for lazy-evaluation)
+//! as well as closures (lambdas that capture variables from the
+//! surrounding scope).
+//!
+//! The upvalues of a scope are whatever data are needed at runtime
+//! in order to resolve each free variable in the scope to a value.
+//! "Upvalue" is a term taken from Lua.
+
+use std::ops::Index;
+
+use crate::{opcode::UpvalueIdx, Value};
+
+/// Structure for carrying upvalues of an UpvalueCarrier.  The
+/// implementation of this struct encapsulates the logic for
+/// capturing and accessing upvalues.
+///
+/// Nix's `with` cannot be used to shadow an enclosing binding --
+/// like Rust's `use xyz::*` construct, but unlike Javascript's
+/// `with (xyz)`.  This means that Nix has two kinds of identifiers,
+/// which can be distinguished at compile time:
+///
+/// - Static identifiers, which are bound in some enclosing scope by
+///   `let`, `name:` or `{name}:`
+/// - Dynamic identifiers, which are not bound in any enclosing
+///   scope
+#[derive(Clone, Debug)]
+pub struct Upvalues {
+    /// The upvalues of static identifiers.  Each static identifier
+    /// is assigned an integer identifier at compile time, which is
+    /// an index into this Vec.
+    static_upvalues: Vec<Value>,
+
+    /// The upvalues of dynamic identifiers, if any exist.  This
+    /// consists of the value passed to each enclosing `with val;`,
+    /// from outermost to innermost.
+    with_stack: Option<Vec<Value>>,
+}
+
+impl Upvalues {
+    pub fn with_capacity(count: usize) -> Self {
+        Upvalues {
+            static_upvalues: Vec::with_capacity(count),
+            with_stack: None,
+        }
+    }
+
+    /// Push an upvalue at the end of the upvalue list.
+    pub fn push(&mut self, value: Value) {
+        self.static_upvalues.push(value);
+    }
+
+    /// Set the captured with stack.
+    pub fn set_with_stack(&mut self, with_stack: Vec<Value>) {
+        self.with_stack = Some(with_stack);
+    }
+
+    pub fn with_stack(&self) -> Option<&Vec<Value>> {
+        self.with_stack.as_ref()
+    }
+
+    pub fn with_stack_len(&self) -> usize {
+        match &self.with_stack {
+            None => 0,
+            Some(stack) => stack.len(),
+        }
+    }
+
+    /// Resolve deferred upvalues from the provided stack slice,
+    /// mutating them in the internal upvalue slots.
+    pub fn resolve_deferred_upvalues(&mut self, stack: &[Value]) {
+        for upvalue in self.static_upvalues.iter_mut() {
+            if let Value::DeferredUpvalue(update_from_idx) = upvalue {
+                *upvalue = stack[update_from_idx.0].clone();
+            }
+        }
+    }
+}
+
+impl Index<UpvalueIdx> for Upvalues {
+    type Output = Value;
+
+    fn index(&self, index: UpvalueIdx) -> &Self::Output {
+        &self.static_upvalues[index.0]
+    }
+}
diff --git a/tvix/eval/src/value/arbitrary.rs b/tvix/eval/src/value/arbitrary.rs
new file mode 100644
index 0000000000..bf53f4fcb2
--- /dev/null
+++ b/tvix/eval/src/value/arbitrary.rs
@@ -0,0 +1,106 @@
+//! Support for configurable generation of arbitrary nix values
+
+use imbl::proptest::{ord_map, vector};
+use proptest::{prelude::*, strategy::BoxedStrategy};
+use std::ffi::OsString;
+
+use super::{attrs::AttrsRep, NixAttrs, NixList, NixString, Value};
+
+#[derive(Clone)]
+pub enum Parameters {
+    Strategy(BoxedStrategy<Value>),
+    Parameters {
+        generate_internal_values: bool,
+        generate_functions: bool,
+        generate_nested: bool,
+    },
+}
+
+impl Default for Parameters {
+    fn default() -> Self {
+        Self::Parameters {
+            generate_internal_values: false,
+            generate_functions: false,
+            generate_nested: true,
+        }
+    }
+}
+
+impl Arbitrary for NixAttrs {
+    type Parameters = Parameters;
+    type Strategy = BoxedStrategy<Self>;
+
+    fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
+        prop_oneof![
+            // Empty attrs representation
+            Just(Self(AttrsRep::Empty)),
+            // KV representation (name/value pairs)
+            (
+                any_with::<Value>(args.clone()),
+                any_with::<Value>(args.clone())
+            )
+                .prop_map(|(name, value)| Self(AttrsRep::KV { name, value })),
+            // Map representation
+            ord_map(NixString::arbitrary(), Value::arbitrary_with(args), 0..100)
+                .prop_map(|map| Self(AttrsRep::Im(map)))
+        ]
+        .boxed()
+    }
+}
+
+impl Arbitrary for NixList {
+    type Parameters = <Value as Arbitrary>::Parameters;
+    type Strategy = BoxedStrategy<Self>;
+
+    fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
+        vector(<Value as Arbitrary>::arbitrary_with(args), 0..100)
+            .prop_map(NixList::from)
+            .boxed()
+    }
+}
+
+impl Arbitrary for Value {
+    type Parameters = Parameters;
+    type Strategy = BoxedStrategy<Self>;
+
+    fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
+        match args {
+            Parameters::Strategy(s) => s,
+            Parameters::Parameters {
+                generate_internal_values,
+                generate_functions,
+                generate_nested,
+            } => {
+                if generate_internal_values || generate_functions {
+                    todo!("Generating internal values and functions not implemented yet")
+                } else if generate_nested {
+                    non_internal_value().boxed()
+                } else {
+                    leaf_value().boxed()
+                }
+            }
+        }
+    }
+}
+
+fn leaf_value() -> impl Strategy<Value = Value> {
+    use Value::*;
+
+    prop_oneof![
+        Just(Null),
+        any::<bool>().prop_map(Bool),
+        any::<i64>().prop_map(Integer),
+        any::<f64>().prop_map(Float),
+        any::<NixString>().prop_map(String),
+        any::<OsString>().prop_map(|s| Path(Box::new(s.into()))),
+    ]
+}
+
+fn non_internal_value() -> impl Strategy<Value = Value> {
+    leaf_value().prop_recursive(3, 5, 5, |inner| {
+        prop_oneof![
+            NixAttrs::arbitrary_with(Parameters::Strategy(inner.clone())).prop_map(Value::attrs),
+            any_with::<NixList>(Parameters::Strategy(inner)).prop_map(Value::List)
+        ]
+    })
+}
diff --git a/tvix/eval/src/value/attrs.rs b/tvix/eval/src/value/attrs.rs
index e2ebc7cb34..33259c8058 100644
--- a/tvix/eval/src/value/attrs.rs
+++ b/tvix/eval/src/value/attrs.rs
@@ -1,43 +1,621 @@
-/// This module implements Nix attribute sets. They have flexible
-/// backing implementations, as they are used in very versatile
-/// use-cases that are all exposed the same way in the language
-/// surface.
-use std::collections::BTreeMap;
-use std::fmt::Display;
+//! This module implements Nix attribute sets. They have flexible
+//! backing implementations, as they are used in very versatile
+//! use-cases that are all exposed the same way in the language
+//! surface.
+//!
+//! Due to this, construction and management of attribute sets has
+//! some peculiarities that are encapsulated within this module.
+use std::borrow::Borrow;
+use std::iter::FromIterator;
+
+use bstr::BStr;
+use imbl::{ordmap, OrdMap};
+use lazy_static::lazy_static;
+use serde::de::{Deserializer, Error, Visitor};
+use serde::Deserialize;
 
 use super::string::NixString;
+use super::thunk::ThunkSet;
+use super::TotalDisplay;
 use super::Value;
+use crate::errors::ErrorKind;
+use crate::CatchableErrorKind;
 
-#[derive(Debug)]
-pub enum NixAttrs {
-    Map(BTreeMap<NixString, Value>),
-    KV { name: Value, value: Value },
+lazy_static! {
+    static ref NAME_S: NixString = "name".into();
+    static ref NAME_REF: &'static NixString = &NAME_S;
+    static ref VALUE_S: NixString = "value".into();
+    static ref VALUE_REF: &'static NixString = &VALUE_S;
 }
 
-impl Display for NixAttrs {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        f.write_str("{ ")?;
+#[cfg(test)]
+mod tests;
+
+#[derive(Clone, Debug, Deserialize, Default)]
+pub(super) enum AttrsRep {
+    #[default]
+    Empty,
+
+    Im(OrdMap<NixString, Value>),
+
+    /// Warning: this represents a **two**-attribute attrset, with
+    /// attribute names "name" and "value", like `{name="foo";
+    /// value="bar";}`, *not* `{foo="bar";}`!
+    KV {
+        name: Value,
+        value: Value,
+    },
+}
 
+impl AttrsRep {
+    /// Retrieve reference to a mutable map inside of an attrs,
+    /// optionally changing the representation if required.
+    fn map_mut(&mut self) -> &mut OrdMap<NixString, Value> {
         match self {
-            NixAttrs::KV { name, value } => {
-                f.write_fmt(format_args!("name = \"{}\"; ", name))?;
-                f.write_fmt(format_args!("value = {}; ", value))?;
-                f.write_str("/* optimised pair! */")?;
+            AttrsRep::Im(m) => m,
+
+            AttrsRep::Empty => {
+                *self = AttrsRep::Im(OrdMap::new());
+                self.map_mut()
+            }
+
+            AttrsRep::KV { name, value } => {
+                *self = AttrsRep::Im(ordmap! {
+                    NAME_S.clone() => name.clone(),
+                    VALUE_S.clone() => value.clone()
+                });
+
+                self.map_mut()
+            }
+        }
+    }
+
+    fn select(&self, key: &BStr) -> Option<&Value> {
+        match self {
+            AttrsRep::Empty => None,
+
+            AttrsRep::KV { name, value } => match &**key {
+                b"name" => Some(name),
+                b"value" => Some(value),
+                _ => None,
+            },
+
+            AttrsRep::Im(map) => map.get(key),
+        }
+    }
+
+    fn contains(&self, key: &BStr) -> bool {
+        match self {
+            AttrsRep::Empty => false,
+            AttrsRep::KV { .. } => key == "name" || key == "value",
+            AttrsRep::Im(map) => map.contains_key(key),
+        }
+    }
+}
+
+#[repr(transparent)]
+#[derive(Clone, Debug, Default)]
+pub struct NixAttrs(pub(super) AttrsRep);
+
+impl From<OrdMap<NixString, Value>> for NixAttrs {
+    fn from(map: OrdMap<NixString, Value>) -> Self {
+        NixAttrs(AttrsRep::Im(map))
+    }
+}
+
+impl<K, V> FromIterator<(K, V)> for NixAttrs
+where
+    NixString: From<K>,
+    Value: From<V>,
+{
+    fn from_iter<T>(iter: T) -> NixAttrs
+    where
+        T: IntoIterator<Item = (K, V)>,
+    {
+        NixAttrs(AttrsRep::Im(iter.into_iter().collect()))
+    }
+}
+
+impl TotalDisplay for NixAttrs {
+    fn total_fmt(&self, f: &mut std::fmt::Formatter<'_>, set: &mut ThunkSet) -> std::fmt::Result {
+        f.write_str("{ ")?;
+
+        match &self.0 {
+            AttrsRep::KV { name, value } => {
+                f.write_str("name = ")?;
+                name.total_fmt(f, set)?;
+                f.write_str("; ")?;
+
+                f.write_str("value = ")?;
+                value.total_fmt(f, set)?;
+                f.write_str("; ")?;
             }
 
-            NixAttrs::Map(map) => {
+            AttrsRep::Im(map) => {
                 for (name, value) in map {
-                    f.write_fmt(format_args!("{} = {}; ", name, value))?;
+                    write!(f, "{} = ", name.ident_str())?;
+                    value.total_fmt(f, set)?;
+                    f.write_str("; ")?;
                 }
             }
+
+            AttrsRep::Empty => { /* no values to print! */ }
         }
 
         f.write_str("}")
     }
 }
 
-impl PartialEq for NixAttrs {
-    fn eq(&self, _other: &Self) -> bool {
-        todo!("attrset equality")
+impl<'de> Deserialize<'de> for NixAttrs {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        struct MapVisitor;
+
+        impl<'de> Visitor<'de> for MapVisitor {
+            type Value = NixAttrs;
+
+            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
+                formatter.write_str("a valid Nix attribute set")
+            }
+
+            fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
+            where
+                A: serde::de::MapAccess<'de>,
+            {
+                let mut stack_array = Vec::with_capacity(map.size_hint().unwrap_or(0) * 2);
+
+                while let Some((key, value)) = map.next_entry()? {
+                    stack_array.push(key);
+                    stack_array.push(value);
+                }
+
+                Ok(NixAttrs::construct(stack_array.len() / 2, stack_array)
+                    .map_err(A::Error::custom)?
+                    .expect("Catchable values are unreachable here"))
+            }
+        }
+
+        deserializer.deserialize_map(MapVisitor)
+    }
+}
+
+impl NixAttrs {
+    pub fn empty() -> Self {
+        Self(AttrsRep::Empty)
+    }
+
+    /// Compare two attribute sets by pointer equality. Only makes
+    /// sense for some attribute set reprsentations, i.e. returning
+    /// `false` does not mean that the two attribute sets do not have
+    /// equal *content*.
+    pub fn ptr_eq(&self, other: &Self) -> bool {
+        match (&self.0, &other.0) {
+            (AttrsRep::Im(lhs), AttrsRep::Im(rhs)) => lhs.ptr_eq(rhs),
+            _ => false,
+        }
+    }
+
+    /// Return an attribute set containing the merge of the two
+    /// provided sets. Keys from the `other` set have precedence.
+    pub fn update(self, other: Self) -> Self {
+        // Short-circuit on some optimal cases:
+        match (&self.0, &other.0) {
+            (AttrsRep::Empty, AttrsRep::Empty) => return self,
+            (AttrsRep::Empty, _) => return other,
+            (_, AttrsRep::Empty) => return self,
+            (AttrsRep::KV { .. }, AttrsRep::KV { .. }) => return other,
+
+            // Explicitly handle all branches instead of falling
+            // through, to ensure that we get at least some compiler
+            // errors if variants are modified.
+            (AttrsRep::Im(_), AttrsRep::Im(_))
+            | (AttrsRep::Im(_), AttrsRep::KV { .. })
+            | (AttrsRep::KV { .. }, AttrsRep::Im(_)) => {}
+        };
+
+        // Slightly more advanced, but still optimised updates
+        match (self.0, other.0) {
+            (AttrsRep::Im(mut m), AttrsRep::KV { name, value }) => {
+                m.insert(NAME_S.clone(), name);
+                m.insert(VALUE_S.clone(), value);
+                NixAttrs(AttrsRep::Im(m))
+            }
+
+            (AttrsRep::KV { name, value }, AttrsRep::Im(mut m)) => {
+                match m.entry(NAME_S.clone()) {
+                    imbl::ordmap::Entry::Vacant(e) => {
+                        e.insert(name);
+                    }
+
+                    imbl::ordmap::Entry::Occupied(_) => { /* name from `m` has precedence */ }
+                };
+
+                match m.entry(VALUE_S.clone()) {
+                    imbl::ordmap::Entry::Vacant(e) => {
+                        e.insert(value);
+                    }
+
+                    imbl::ordmap::Entry::Occupied(_) => { /* value from `m` has precedence */ }
+                };
+
+                NixAttrs(AttrsRep::Im(m))
+            }
+
+            // Plain merge of maps.
+            (AttrsRep::Im(m1), AttrsRep::Im(m2)) => NixAttrs(AttrsRep::Im(m2.union(m1))),
+
+            // Cases handled above by the borrowing match:
+            _ => unreachable!(),
+        }
+    }
+
+    /// Return the number of key-value entries in an attrset.
+    pub fn len(&self) -> usize {
+        match &self.0 {
+            AttrsRep::Im(map) => map.len(),
+            AttrsRep::Empty => 0,
+            AttrsRep::KV { .. } => 2,
+        }
+    }
+
+    pub fn is_empty(&self) -> bool {
+        match &self.0 {
+            AttrsRep::Im(map) => map.is_empty(),
+            AttrsRep::Empty => true,
+            AttrsRep::KV { .. } => false,
+        }
+    }
+
+    /// Select a value from an attribute set by key.
+    pub fn select<K>(&self, key: &K) -> Option<&Value>
+    where
+        K: Borrow<BStr> + ?Sized,
+    {
+        self.0.select(key.borrow())
+    }
+
+    /// Select a required value from an attribute set by key, return
+    /// an `AttributeNotFound` error if it is missing.
+    pub fn select_required<K>(&self, key: &K) -> Result<&Value, ErrorKind>
+    where
+        K: Borrow<BStr> + ?Sized,
+    {
+        self.select(key)
+            .ok_or_else(|| ErrorKind::AttributeNotFound {
+                name: key.borrow().to_string(),
+            })
+    }
+
+    pub fn contains<'a, K: 'a>(&self, key: K) -> bool
+    where
+        &'a BStr: From<K>,
+    {
+        self.0.contains(key.into())
+    }
+
+    /// Construct an iterator over all the key-value pairs in the attribute set.
+    #[allow(clippy::needless_lifetimes)]
+    pub fn iter<'a>(&'a self) -> Iter<KeyValue<'a>> {
+        Iter(match &self.0 {
+            AttrsRep::Im(map) => KeyValue::Im(map.iter()),
+            AttrsRep::Empty => KeyValue::Empty,
+
+            AttrsRep::KV {
+                ref name,
+                ref value,
+            } => KeyValue::KV {
+                name,
+                value,
+                at: IterKV::default(),
+            },
+        })
+    }
+
+    /// Same as iter(), but marks call sites which rely on the
+    /// iteration being lexicographic.
+    pub fn iter_sorted(&self) -> Iter<KeyValue<'_>> {
+        self.iter()
+    }
+
+    /// Same as [IntoIterator::into_iter], but marks call sites which rely on the
+    /// iteration being lexicographic.
+    pub fn into_iter_sorted(self) -> OwnedAttrsIterator {
+        self.into_iter()
+    }
+
+    /// Construct an iterator over all the keys of the attribute set
+    pub fn keys(&self) -> Keys {
+        Keys(match &self.0 {
+            AttrsRep::Empty => KeysInner::Empty,
+            AttrsRep::Im(m) => KeysInner::Im(m.keys()),
+            AttrsRep::KV { .. } => KeysInner::KV(IterKV::default()),
+        })
+    }
+
+    /// Implement construction logic of an attribute set, to encapsulate
+    /// logic about attribute set optimisations inside of this module.
+    pub fn construct(
+        count: usize,
+        mut stack_slice: Vec<Value>,
+    ) -> Result<Result<Self, CatchableErrorKind>, ErrorKind> {
+        debug_assert!(
+            stack_slice.len() == count * 2,
+            "construct_attrs called with count == {}, but slice.len() == {}",
+            count,
+            stack_slice.len(),
+        );
+
+        // Optimisation: Empty attribute set
+        if count == 0 {
+            return Ok(Ok(NixAttrs(AttrsRep::Empty)));
+        }
+
+        // Optimisation: KV pattern
+        if count == 2 {
+            if let Some(kv) = attempt_optimise_kv(&mut stack_slice) {
+                return Ok(Ok(kv));
+            }
+        }
+
+        let mut attrs = NixAttrs(AttrsRep::Im(OrdMap::new()));
+
+        for _ in 0..count {
+            let value = stack_slice.pop().unwrap();
+            let key = stack_slice.pop().unwrap();
+
+            match key {
+                Value::String(ks) => set_attr(&mut attrs, ks, value)?,
+
+                Value::Null => {
+                    // This is in fact valid, but leads to the value
+                    // being ignored and nothing being set, i.e. `{
+                    // ${null} = 1; } => { }`.
+                    continue;
+                }
+
+                Value::Catchable(err) => return Ok(Err(*err)),
+
+                other => return Err(ErrorKind::InvalidAttributeName(other)),
+            }
+        }
+
+        Ok(Ok(attrs))
+    }
+
+    /// Construct an optimized "KV"-style attribute set given the value for the
+    /// `"name"` key, and the value for the `"value"` key
+    pub(crate) fn from_kv(name: Value, value: Value) -> Self {
+        NixAttrs(AttrsRep::KV { name, value })
+    }
+}
+
+impl IntoIterator for NixAttrs {
+    type Item = (NixString, Value);
+    type IntoIter = OwnedAttrsIterator;
+
+    fn into_iter(self) -> Self::IntoIter {
+        match self.0 {
+            AttrsRep::Empty => OwnedAttrsIterator(IntoIterRepr::Empty),
+            AttrsRep::KV { name, value } => OwnedAttrsIterator(IntoIterRepr::Finite(
+                vec![(NAME_REF.clone(), name), (VALUE_REF.clone(), value)].into_iter(),
+            )),
+            AttrsRep::Im(map) => OwnedAttrsIterator(IntoIterRepr::Im(map.into_iter())),
+        }
+    }
+}
+
+/// In Nix, name/value attribute pairs are frequently constructed from
+/// literals. This particular case should avoid allocation of a map,
+/// additional heap values etc. and use the optimised `KV` variant
+/// instead.
+///
+/// ```norust
+/// `slice` is the top of the stack from which the attrset is being
+/// constructed, e.g.
+///
+///   slice: [ "value" 5 "name" "foo" ]
+///   index:   0       1 2      3
+///   stack:   3       2 1      0
+/// ```
+fn attempt_optimise_kv(slice: &mut [Value]) -> Option<NixAttrs> {
+    let (name_idx, value_idx) = {
+        match (&slice[2], &slice[0]) {
+            (Value::String(s1), Value::String(s2)) if (*s1 == *NAME_S && *s2 == *VALUE_S) => (3, 1),
+            (Value::String(s1), Value::String(s2)) if (*s1 == *VALUE_S && *s2 == *NAME_S) => (1, 3),
+
+            // Technically this branch lets type errors pass,
+            // but they will be caught during normal attribute
+            // set construction instead.
+            _ => return None,
+        }
+    };
+
+    Some(NixAttrs::from_kv(
+        slice[name_idx].clone(),
+        slice[value_idx].clone(),
+    ))
+}
+
+/// Set an attribute on an in-construction attribute set, while
+/// checking against duplicate keys.
+fn set_attr(attrs: &mut NixAttrs, key: NixString, value: Value) -> Result<(), ErrorKind> {
+    match attrs.0.map_mut().entry(key) {
+        imbl::ordmap::Entry::Occupied(entry) => Err(ErrorKind::DuplicateAttrsKey {
+            key: entry.key().to_string(),
+        }),
+
+        imbl::ordmap::Entry::Vacant(entry) => {
+            entry.insert(value);
+            Ok(())
+        }
+    }
+}
+
+/// Internal helper type to track the iteration status of an iterator
+/// over the name/value representation.
+#[derive(Debug, Default)]
+pub enum IterKV {
+    #[default]
+    Name,
+    Value,
+    Done,
+}
+
+impl IterKV {
+    fn next(&mut self) {
+        match *self {
+            Self::Name => *self = Self::Value,
+            Self::Value => *self = Self::Done,
+            Self::Done => {}
+        }
+    }
+}
+
+/// Iterator representation over the keys *and* values of an attribute
+/// set.
+pub enum KeyValue<'a> {
+    Empty,
+
+    KV {
+        name: &'a Value,
+        value: &'a Value,
+        at: IterKV,
+    },
+
+    Im(imbl::ordmap::Iter<'a, NixString, Value>),
+}
+
+/// Iterator over a Nix attribute set.
+// This wrapper type exists to make the inner "raw" iterator
+// inaccessible.
+#[repr(transparent)]
+pub struct Iter<T>(T);
+
+impl<'a> Iterator for Iter<KeyValue<'a>> {
+    type Item = (&'a NixString, &'a Value);
+
+    fn next(&mut self) -> Option<Self::Item> {
+        match &mut self.0 {
+            KeyValue::Im(inner) => inner.next(),
+            KeyValue::Empty => None,
+
+            KeyValue::KV { name, value, at } => match at {
+                IterKV::Name => {
+                    at.next();
+                    Some((&NAME_REF, name))
+                }
+
+                IterKV::Value => {
+                    at.next();
+                    Some((&VALUE_REF, value))
+                }
+
+                IterKV::Done => None,
+            },
+        }
+    }
+}
+
+impl<'a> ExactSizeIterator for Iter<KeyValue<'a>> {
+    fn len(&self) -> usize {
+        match &self.0 {
+            KeyValue::Empty => 0,
+            KeyValue::KV { .. } => 2,
+            KeyValue::Im(inner) => inner.len(),
+        }
+    }
+}
+
+enum KeysInner<'a> {
+    Empty,
+    KV(IterKV),
+    Im(imbl::ordmap::Keys<'a, NixString, Value>),
+}
+
+pub struct Keys<'a>(KeysInner<'a>);
+
+impl<'a> Iterator for Keys<'a> {
+    type Item = &'a NixString;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        match &mut self.0 {
+            KeysInner::Empty => None,
+            KeysInner::KV(at @ IterKV::Name) => {
+                at.next();
+                Some(&NAME_REF)
+            }
+            KeysInner::KV(at @ IterKV::Value) => {
+                at.next();
+                Some(&VALUE_REF)
+            }
+            KeysInner::KV(IterKV::Done) => None,
+            KeysInner::Im(m) => m.next(),
+        }
+    }
+}
+
+impl<'a> IntoIterator for &'a NixAttrs {
+    type Item = (&'a NixString, &'a Value);
+
+    type IntoIter = Iter<KeyValue<'a>>;
+
+    fn into_iter(self) -> Self::IntoIter {
+        self.iter()
+    }
+}
+
+impl<'a> ExactSizeIterator for Keys<'a> {
+    fn len(&self) -> usize {
+        match &self.0 {
+            KeysInner::Empty => 0,
+            KeysInner::KV(_) => 2,
+            KeysInner::Im(m) => m.len(),
+        }
+    }
+}
+
+/// Internal representation of an owning attrset iterator
+pub enum IntoIterRepr {
+    Empty,
+    Finite(std::vec::IntoIter<(NixString, Value)>),
+    Im(imbl::ordmap::ConsumingIter<(NixString, Value)>),
+}
+
+/// Wrapper type which hides the internal implementation details from
+/// users.
+#[repr(transparent)]
+pub struct OwnedAttrsIterator(IntoIterRepr);
+
+impl Iterator for OwnedAttrsIterator {
+    type Item = (NixString, Value);
+
+    fn next(&mut self) -> Option<Self::Item> {
+        match &mut self.0 {
+            IntoIterRepr::Empty => None,
+            IntoIterRepr::Finite(inner) => inner.next(),
+            IntoIterRepr::Im(inner) => inner.next(),
+        }
+    }
+}
+
+impl ExactSizeIterator for OwnedAttrsIterator {
+    fn len(&self) -> usize {
+        match &self.0 {
+            IntoIterRepr::Empty => 0,
+            IntoIterRepr::Finite(inner) => inner.len(),
+            IntoIterRepr::Im(inner) => inner.len(),
+        }
+    }
+}
+
+impl DoubleEndedIterator for OwnedAttrsIterator {
+    fn next_back(&mut self) -> Option<Self::Item> {
+        match &mut self.0 {
+            IntoIterRepr::Empty => None,
+            IntoIterRepr::Finite(inner) => inner.next_back(),
+            IntoIterRepr::Im(inner) => inner.next_back(),
+        }
     }
 }
diff --git a/tvix/eval/src/value/attrs/tests.rs b/tvix/eval/src/value/attrs/tests.rs
new file mode 100644
index 0000000000..534b78a00d
--- /dev/null
+++ b/tvix/eval/src/value/attrs/tests.rs
@@ -0,0 +1,106 @@
+use bstr::B;
+
+use super::*;
+
+#[test]
+fn test_empty_attrs() {
+    let attrs = NixAttrs::construct(0, vec![])
+        .expect("empty attr construction should succeed")
+        .unwrap();
+
+    assert!(
+        matches!(attrs, NixAttrs(AttrsRep::Empty)),
+        "empty attribute set should use optimised representation"
+    );
+}
+
+#[test]
+fn test_simple_attrs() {
+    let attrs = NixAttrs::construct(1, vec![Value::from("key"), Value::from("value")])
+        .expect("simple attr construction should succeed")
+        .unwrap();
+
+    assert!(
+        matches!(attrs, NixAttrs(AttrsRep::Im(_))),
+        "simple attribute set should use map representation",
+    )
+}
+
+#[test]
+fn test_kv_attrs() {
+    let name_val = Value::from("name");
+    let value_val = Value::from("value");
+    let meaning_val = Value::from("meaning");
+    let forty_two_val = Value::Integer(42);
+
+    let kv_attrs = NixAttrs::construct(
+        2,
+        vec![
+            value_val,
+            forty_two_val.clone(),
+            name_val,
+            meaning_val.clone(),
+        ],
+    )
+    .expect("constructing K/V pair attrs should succeed")
+    .unwrap();
+
+    match kv_attrs {
+        NixAttrs(AttrsRep::KV { name, value })
+            if name.to_str().unwrap() == meaning_val.to_str().unwrap()
+                || value.to_str().unwrap() == forty_two_val.to_str().unwrap() => {}
+
+        _ => panic!(
+            "K/V attribute set should use optimised representation, but got {:?}",
+            kv_attrs
+        ),
+    }
+}
+
+#[test]
+fn test_empty_attrs_iter() {
+    let attrs = NixAttrs::construct(0, vec![]).unwrap().unwrap();
+    assert!(attrs.iter().next().is_none());
+}
+
+#[test]
+fn test_kv_attrs_iter() {
+    let name_val = Value::from("name");
+    let value_val = Value::from("value");
+    let meaning_val = Value::from("meaning");
+    let forty_two_val = Value::Integer(42);
+
+    let kv_attrs = NixAttrs::construct(
+        2,
+        vec![
+            value_val,
+            forty_two_val.clone(),
+            name_val,
+            meaning_val.clone(),
+        ],
+    )
+    .expect("constructing K/V pair attrs should succeed")
+    .unwrap();
+
+    let mut iter = kv_attrs.iter().collect::<Vec<_>>().into_iter();
+    let (k, v) = iter.next().unwrap();
+    assert!(k == *NAME_REF);
+    assert!(v.to_str().unwrap() == meaning_val.to_str().unwrap());
+    let (k, v) = iter.next().unwrap();
+    assert!(k == *VALUE_REF);
+    assert!(v.as_int().unwrap() == forty_two_val.as_int().unwrap());
+    assert!(iter.next().is_none());
+}
+
+#[test]
+fn test_map_attrs_iter() {
+    let attrs = NixAttrs::construct(1, vec![Value::from("key"), Value::from("value")])
+        .expect("simple attr construction should succeed")
+        .unwrap();
+
+    let mut iter = attrs.iter().collect::<Vec<_>>().into_iter();
+    let (k, v) = iter.next().unwrap();
+    assert!(k == &NixString::from("key"));
+    assert_eq!(v.to_str().unwrap(), B("value"));
+    assert!(iter.next().is_none());
+}
diff --git a/tvix/eval/src/value/builtin.rs b/tvix/eval/src/value/builtin.rs
new file mode 100644
index 0000000000..346f06cb77
--- /dev/null
+++ b/tvix/eval/src/value/builtin.rs
@@ -0,0 +1,137 @@
+//! This module implements the runtime representation of a Nix
+//! builtin.
+//!
+//! Builtins are directly backed by Rust code operating on Nix values.
+
+use crate::vm::generators::Generator;
+
+use super::Value;
+
+use std::{
+    fmt::{Debug, Display},
+    rc::Rc,
+};
+
+/// Trait for closure types of builtins.
+///
+/// Builtins are expected to yield a generator which can be run by the VM to
+/// produce the final value.
+///
+/// Implementors should use the builtins-macros to create these functions
+/// instead of handling the argument-passing logic manually.
+pub trait BuiltinGen: Fn(Vec<Value>) -> Generator {}
+impl<F: Fn(Vec<Value>) -> Generator> BuiltinGen for F {}
+
+#[derive(Clone)]
+pub struct BuiltinRepr {
+    name: &'static str,
+    /// Optional documentation for the builtin.
+    documentation: Option<&'static str>,
+    arg_count: usize,
+
+    func: Rc<dyn BuiltinGen>,
+
+    /// Partially applied function arguments.
+    partials: Vec<Value>,
+}
+
+pub enum BuiltinResult {
+    /// Builtin was not ready to be called (arguments missing) and remains
+    /// partially applied.
+    Partial(Builtin),
+
+    /// Builtin was called and constructed a generator that the VM must run.
+    Called(&'static str, Generator),
+}
+
+/// Represents a single built-in function which directly executes Rust
+/// code that operates on a Nix value.
+///
+/// Builtins are the only functions in Nix that have varying arities
+/// (for example, `hasAttr` has an arity of 2, but `isAttrs` an arity
+/// of 1). To facilitate this generically, builtins expect to be
+/// called with a vector of Nix values corresponding to their
+/// arguments in order.
+///
+/// Partially applied builtins act similar to closures in that they
+/// "capture" the partially applied arguments, and are treated
+/// specially when printing their representation etc.
+#[derive(Clone)]
+pub struct Builtin(Box<BuiltinRepr>);
+
+impl From<BuiltinRepr> for Builtin {
+    fn from(value: BuiltinRepr) -> Self {
+        Builtin(Box::new(value))
+    }
+}
+
+impl Builtin {
+    pub fn new<F: BuiltinGen + 'static>(
+        name: &'static str,
+        documentation: Option<&'static str>,
+        arg_count: usize,
+        func: F,
+    ) -> Self {
+        BuiltinRepr {
+            name,
+            documentation,
+            arg_count,
+            func: Rc::new(func),
+            partials: vec![],
+        }
+        .into()
+    }
+
+    pub fn name(&self) -> &'static str {
+        self.0.name
+    }
+
+    pub fn documentation(&self) -> Option<&'static str> {
+        self.0.documentation
+    }
+
+    /// Apply an additional argument to the builtin.
+    /// After this, [`Builtin::call`] *must* be called, otherwise it may leave
+    /// the builtin in an incorrect state.
+    pub fn apply_arg(&mut self, arg: Value) {
+        self.0.partials.push(arg);
+
+        debug_assert!(
+            self.0.partials.len() <= self.0.arg_count,
+            "Tvix bug: pushed too many arguments to builtin"
+        );
+    }
+
+    /// Attempt to call a builtin, which will produce a generator if it is fully
+    /// applied or return the builtin if it is partially applied.
+    pub fn call(self) -> BuiltinResult {
+        if self.0.partials.len() == self.0.arg_count {
+            BuiltinResult::Called(self.0.name, (self.0.func)(self.0.partials))
+        } else {
+            BuiltinResult::Partial(self)
+        }
+    }
+}
+
+impl Debug for Builtin {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "builtin[{}]", self.0.name)
+    }
+}
+
+impl Display for Builtin {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        if !self.0.partials.is_empty() {
+            f.write_str("<PRIMOP-APP>")
+        } else {
+            f.write_str("<PRIMOP>")
+        }
+    }
+}
+
+/// Builtins are uniquely identified by their name
+impl PartialEq for Builtin {
+    fn eq(&self, other: &Self) -> bool {
+        self.0.name == other.0.name
+    }
+}
diff --git a/tvix/eval/src/value/function.rs b/tvix/eval/src/value/function.rs
new file mode 100644
index 0000000000..7592e3d641
--- /dev/null
+++ b/tvix/eval/src/value/function.rs
@@ -0,0 +1,112 @@
+//! This module implements the runtime representation of functions.
+use std::{collections::BTreeMap, hash::Hash, rc::Rc};
+
+use codemap::Span;
+use smol_str::SmolStr;
+
+use crate::{chunk::Chunk, upvalues::Upvalues};
+
+use super::NixString;
+
+#[derive(Clone, Debug, PartialEq)]
+pub(crate) struct Formals {
+    /// Map from argument name, to whether that argument is required
+    pub(crate) arguments: BTreeMap<NixString, bool>,
+
+    /// Do the formals of this function accept extra arguments
+    pub(crate) ellipsis: bool,
+
+    /// The span of the formals themselves, to use to emit errors
+    pub(crate) span: Span,
+
+    /// Optionally tracks a name for all function arguments (args@ style).
+    /// Used by toXML.
+    pub(crate) name: Option<String>,
+}
+
+impl Formals {
+    /// Returns true if the given arg is a valid argument to these formals.
+    ///
+    /// This is true if it is either listed in the list of arguments, or the formals have an
+    /// ellipsis
+    pub(crate) fn contains<Q>(&self, arg: &Q) -> bool
+    where
+        Q: ?Sized + Hash + Ord + Eq,
+        NixString: std::borrow::Borrow<Q>,
+    {
+        self.ellipsis || self.arguments.contains_key(arg)
+    }
+}
+
+/// The opcodes for a thunk or closure, plus the number of
+/// non-executable opcodes which are allowed after an OpThunkClosure or
+/// OpThunkSuspended referencing it.  At runtime `Lambda` is usually wrapped
+/// in `Rc` to avoid copying the `Chunk` it holds (which can be
+/// quite large).
+///
+/// In order to correctly reproduce cppnix's "pointer equality"
+/// semantics it is important that we never clone a Lambda --
+/// use `Rc<Lambda>::clone()` instead.  This struct deliberately
+/// does not `derive(Clone)` in order to prevent this from being
+/// done accidentally.
+///
+#[derive(/* do not add Clone here */ Debug, Default)]
+pub struct Lambda {
+    pub(crate) chunk: Chunk,
+
+    /// Name of the function (equivalent to the name of the
+    /// identifier (e.g. a value in a let-expression or an attribute
+    /// set entry) it is located in).
+    pub(crate) name: Option<SmolStr>,
+
+    /// Number of upvalues which the code in this Lambda closes
+    /// over, and which need to be initialised at
+    /// runtime.  Information about the variables is emitted using
+    /// data-carrying opcodes (see [`crate::opcode::OpCode::DataStackIdx`]).
+    pub(crate) upvalue_count: usize,
+    pub(crate) formals: Option<Formals>,
+}
+
+impl Lambda {
+    pub fn chunk(&mut self) -> &mut Chunk {
+        &mut self.chunk
+    }
+}
+
+///
+/// In order to correctly reproduce cppnix's "pointer equality"
+/// semantics it is important that we never clone a Lambda --
+/// use `Rc<Lambda>::clone()` instead.  This struct deliberately
+/// does not `derive(Clone)` in order to prevent this from being
+/// done accidentally.
+///
+#[derive(/* do not add Clone here */ Debug)]
+pub struct Closure {
+    pub lambda: Rc<Lambda>,
+    pub upvalues: Rc<Upvalues>,
+}
+
+impl Closure {
+    pub fn new(lambda: Rc<Lambda>) -> Self {
+        Self::new_with_upvalues(
+            Rc::new(Upvalues::with_capacity(lambda.upvalue_count)),
+            lambda,
+        )
+    }
+
+    pub fn new_with_upvalues(upvalues: Rc<Upvalues>, lambda: Rc<Lambda>) -> Self {
+        Closure { upvalues, lambda }
+    }
+
+    pub fn chunk(&self) -> &Chunk {
+        &self.lambda.chunk
+    }
+
+    pub fn lambda(&self) -> Rc<Lambda> {
+        self.lambda.clone()
+    }
+
+    pub fn upvalues(&self) -> Rc<Upvalues> {
+        self.upvalues.clone()
+    }
+}
diff --git a/tvix/eval/src/value/json.rs b/tvix/eval/src/value/json.rs
new file mode 100644
index 0000000000..c48e9c1f4e
--- /dev/null
+++ b/tvix/eval/src/value/json.rs
@@ -0,0 +1,154 @@
+/// Implementation of Value serialisation *to* JSON.
+///
+/// This can not be implemented through standard serde-derive methods,
+/// as there is internal Nix logic that must happen within the
+/// serialisation methods.
+use super::{CoercionKind, Value};
+use crate::errors::{CatchableErrorKind, ErrorKind};
+use crate::generators::{self, GenCo};
+use crate::NixContext;
+
+use bstr::ByteSlice;
+use serde_json::value::to_value;
+use serde_json::Value as Json; // name clash with *our* `Value`
+use serde_json::{Map, Number};
+
+impl Value {
+    /// Transforms the structure into a JSON
+    /// and accumulate all encountered context in the second's element
+    /// of the return type.
+    pub async fn into_contextful_json(
+        self,
+        co: &GenCo,
+    ) -> Result<Result<(Json, NixContext), CatchableErrorKind>, ErrorKind> {
+        let self_forced = generators::request_force(co, self).await;
+        let mut context = NixContext::new();
+
+        let value = match self_forced {
+            Value::Null => Json::Null,
+            Value::Bool(b) => Json::Bool(b),
+            Value::Integer(i) => Json::Number(Number::from(i)),
+            Value::Float(f) => to_value(f)?,
+            Value::String(s) => {
+                context.mimic(&s);
+
+                Json::String(s.to_str()?.to_owned())
+            }
+
+            Value::Path(p) => {
+                let imported = generators::request_path_import(co, *p).await;
+                let path = imported.to_string_lossy().to_string();
+                context = context.append(crate::NixContextElement::Plain(path.clone()));
+                Json::String(path)
+            }
+
+            Value::List(l) => {
+                let mut out = vec![];
+
+                for val in l.into_iter() {
+                    match generators::request_to_json(co, val).await {
+                        Ok((v, mut ctx)) => {
+                            context = context.join(&mut ctx);
+                            out.push(v)
+                        }
+                        Err(cek) => return Ok(Err(cek)),
+                    }
+                }
+
+                Json::Array(out)
+            }
+
+            Value::Attrs(attrs) => {
+                // Attribute sets with a callable `__toString` attribute
+                // serialise to the string-coerced version of the result of
+                // calling that.
+                if attrs.select("__toString").is_some() {
+                    let span = generators::request_span(co).await;
+                    match Value::Attrs(attrs)
+                        .coerce_to_string_(
+                            co,
+                            CoercionKind {
+                                strong: false,
+                                import_paths: false,
+                            },
+                            span,
+                        )
+                        .await?
+                    {
+                        Value::Catchable(cek) => return Ok(Err(*cek)),
+                        Value::String(s) => {
+                            // We need a fresh context here because `__toString` will discard
+                            // everything.
+                            let mut fresh = NixContext::new();
+                            fresh.mimic(&s);
+
+                            return Ok(Ok((Json::String(s.to_str()?.to_owned()), fresh)));
+                        }
+                        _ => panic!("Value::coerce_to_string_() returned a non-string!"),
+                    }
+                }
+
+                // Attribute sets with an `outPath` attribute
+                // serialise to a JSON serialisation of that inner
+                // value (regardless of what it is!).
+                if let Some(out_path) = attrs.select("outPath") {
+                    return Ok(generators::request_to_json(co, out_path.clone()).await);
+                }
+
+                let mut out = Map::with_capacity(attrs.len());
+                for (name, value) in attrs.into_iter_sorted() {
+                    out.insert(
+                        name.to_str()?.to_owned(),
+                        match generators::request_to_json(co, value).await {
+                            Ok((v, mut ctx)) => {
+                                context = context.join(&mut ctx);
+                                v
+                            }
+                            Err(cek) => return Ok(Err(cek)),
+                        },
+                    );
+                }
+
+                Json::Object(out)
+            }
+
+            Value::Catchable(c) => return Ok(Err(*c)),
+
+            val @ Value::Closure(_)
+            | val @ Value::Thunk(_)
+            | val @ Value::Builtin(_)
+            | val @ Value::AttrNotFound
+            | val @ Value::Blueprint(_)
+            | val @ Value::DeferredUpvalue(_)
+            | val @ Value::UnresolvedPath(_)
+            | val @ Value::Json(..)
+            | val @ Value::FinaliseRequest(_) => {
+                return Err(ErrorKind::NotSerialisableToJson(val.type_of()))
+            }
+        };
+
+        Ok(Ok((value, context)))
+    }
+
+    /// Generator version of the above, which wraps responses in
+    /// [`Value::Json`].
+    pub(crate) async fn into_contextful_json_generator(
+        self,
+        co: GenCo,
+    ) -> Result<Value, ErrorKind> {
+        match self.into_contextful_json(&co).await? {
+            Err(cek) => Ok(Value::from(cek)),
+            Ok((json, ctx)) => Ok(Value::Json(Box::new((json, ctx)))),
+        }
+    }
+
+    /// Transforms the structure into a JSON
+    /// All the accumulated context is ignored, use [`into_contextful_json`]
+    /// to obtain the resulting context of the JSON object.
+    pub async fn into_json(
+        self,
+        co: &GenCo,
+    ) -> Result<Result<Json, CatchableErrorKind>, ErrorKind> {
+        Ok(self.into_contextful_json(co).await?.map(|(json, _)| json))
+    }
+}
diff --git a/tvix/eval/src/value/list.rs b/tvix/eval/src/value/list.rs
index d5f7c8b2ba..2b8b3de28d 100644
--- a/tvix/eval/src/value/list.rs
+++ b/tvix/eval/src/value/list.rs
@@ -1,20 +1,102 @@
-/// This module implements Nix lists.
-use std::fmt::Display;
+//! This module implements Nix lists.
+use std::ops::Index;
+use std::rc::Rc;
 
+use imbl::{vector, Vector};
+
+use serde::Deserialize;
+
+use super::thunk::ThunkSet;
+use super::TotalDisplay;
 use super::Value;
 
-#[derive(Clone, Debug, PartialEq)]
-pub struct NixList(pub Vec<Value>);
+#[repr(transparent)]
+#[derive(Clone, Debug, Deserialize)]
+pub struct NixList(Rc<Vector<Value>>);
 
-impl Display for NixList {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+impl TotalDisplay for NixList {
+    fn total_fmt(&self, f: &mut std::fmt::Formatter<'_>, set: &mut ThunkSet) -> std::fmt::Result {
         f.write_str("[ ")?;
 
-        for v in &self.0 {
-            v.fmt(f)?;
+        for v in self {
+            v.total_fmt(f, set)?;
             f.write_str(" ")?;
         }
 
         f.write_str("]")
     }
 }
+
+impl From<Vector<Value>> for NixList {
+    fn from(vs: Vector<Value>) -> Self {
+        Self(Rc::new(vs))
+    }
+}
+
+impl NixList {
+    pub fn len(&self) -> usize {
+        self.0.len()
+    }
+
+    pub fn get(&self, i: usize) -> Option<&Value> {
+        self.0.get(i)
+    }
+
+    pub fn is_empty(&self) -> bool {
+        self.0.is_empty()
+    }
+
+    pub fn construct(count: usize, stack_slice: Vec<Value>) -> Self {
+        debug_assert!(
+            count == stack_slice.len(),
+            "NixList::construct called with count == {}, but slice.len() == {}",
+            count,
+            stack_slice.len(),
+        );
+
+        NixList(Rc::new(Vector::from_iter(stack_slice)))
+    }
+
+    pub fn iter(&self) -> vector::Iter<Value> {
+        self.0.iter()
+    }
+
+    pub fn ptr_eq(&self, other: &Self) -> bool {
+        Rc::ptr_eq(&self.0, &other.0)
+    }
+
+    pub fn into_inner(self) -> Vector<Value> {
+        Rc::try_unwrap(self.0).unwrap_or_else(|rc| (*rc).clone())
+    }
+
+    #[deprecated(note = "callers should avoid constructing from Vec")]
+    pub fn from_vec(vs: Vec<Value>) -> Self {
+        Self(Rc::new(Vector::from_iter(vs)))
+    }
+}
+
+impl IntoIterator for NixList {
+    type Item = Value;
+    type IntoIter = imbl::vector::ConsumingIter<Value>;
+
+    fn into_iter(self) -> Self::IntoIter {
+        self.into_inner().into_iter()
+    }
+}
+
+impl<'a> IntoIterator for &'a NixList {
+    type Item = &'a Value;
+    type IntoIter = imbl::vector::Iter<'a, Value>;
+
+    fn into_iter(self) -> Self::IntoIter {
+        self.0.iter()
+    }
+}
+
+impl Index<usize> for NixList {
+    type Output = Value;
+
+    fn index(&self, index: usize) -> &Self::Output {
+        &self.0[index]
+    }
+}
diff --git a/tvix/eval/src/value/mod.rs b/tvix/eval/src/value/mod.rs
index 55d44048b6..c171c9a04e 100644
--- a/tvix/eval/src/value/mod.rs
+++ b/tvix/eval/src/value/mod.rs
@@ -1,38 +1,663 @@
 //! This module implements the backing representation of runtime
 //! values in the Nix language.
+use std::cmp::Ordering;
 use std::fmt::Display;
+use std::num::{NonZeroI32, NonZeroUsize};
+use std::path::PathBuf;
 use std::rc::Rc;
 
+use bstr::{BString, ByteVec};
+use lexical_core::format::CXX_LITERAL;
+use serde::Deserialize;
+
+#[cfg(feature = "arbitrary")]
+mod arbitrary;
 mod attrs;
+mod builtin;
+mod function;
+mod json;
 mod list;
+mod path;
 mod string;
+mod thunk;
 
-use crate::errors::{Error, EvalResult};
+use crate::errors::{CatchableErrorKind, ErrorKind};
+use crate::opcode::StackIdx;
+use crate::spans::LightSpan;
+use crate::vm::generators::{self, GenCo};
+use crate::AddContext;
 pub use attrs::NixAttrs;
+pub use builtin::{Builtin, BuiltinResult};
+pub(crate) use function::Formals;
+pub use function::{Closure, Lambda};
 pub use list::NixList;
-pub use string::NixString;
+pub use path::canon_path;
+pub use string::{NixContext, NixContextElement, NixString};
+pub use thunk::Thunk;
+
+pub use self::thunk::ThunkSet;
+
+use lazy_static::lazy_static;
 
-#[derive(Clone, Debug)]
+#[warn(variant_size_differences)]
+#[derive(Clone, Debug, Deserialize)]
+#[serde(untagged)]
 pub enum Value {
     Null,
     Bool(bool),
     Integer(i64),
     Float(f64),
     String(NixString),
-    Attrs(Rc<NixAttrs>),
+
+    #[serde(skip)]
+    Path(Box<PathBuf>),
+    Attrs(Box<NixAttrs>),
     List(NixList),
 
+    #[serde(skip)]
+    Closure(Rc<Closure>), // must use Rc<Closure> here in order to get proper pointer equality
+
+    #[serde(skip)]
+    Builtin(Builtin),
+
     // Internal values that, while they technically exist at runtime,
     // are never returned to or created directly by users.
-    AttrPath(Vec<NixString>),
+    #[serde(skip_deserializing)]
+    Thunk(Thunk),
+
+    // See [`compiler::compile_select_or()`] for explanation
+    #[serde(skip)]
+    AttrNotFound,
+
+    // this can only occur in Chunk::Constants and nowhere else
+    #[serde(skip)]
+    Blueprint(Rc<Lambda>),
+
+    #[serde(skip)]
+    DeferredUpvalue(StackIdx),
+    #[serde(skip)]
+    UnresolvedPath(Box<PathBuf>),
+    #[serde(skip)]
+    Json(Box<(serde_json::Value, NixContext)>),
+
+    #[serde(skip)]
+    FinaliseRequest(bool),
+
+    #[serde(skip)]
+    // TODO(tazjin): why is this in a Box?
+    Catchable(Box<CatchableErrorKind>),
+}
+
+impl From<CatchableErrorKind> for Value {
+    #[inline]
+    fn from(c: CatchableErrorKind) -> Value {
+        Value::Catchable(Box::new(c))
+    }
+}
+
+impl<V> From<Result<V, CatchableErrorKind>> for Value
+where
+    Value: From<V>,
+{
+    #[inline]
+    fn from(v: Result<V, CatchableErrorKind>) -> Value {
+        match v {
+            Ok(v) => v.into(),
+            Err(e) => Value::Catchable(Box::new(e)),
+        }
+    }
+}
+
+lazy_static! {
+    static ref WRITE_FLOAT_OPTIONS: lexical_core::WriteFloatOptions =
+        lexical_core::WriteFloatOptionsBuilder::new()
+            .trim_floats(true)
+            .round_mode(lexical_core::write_float_options::RoundMode::Round)
+            .positive_exponent_break(Some(NonZeroI32::new(5).unwrap()))
+            .max_significant_digits(Some(NonZeroUsize::new(6).unwrap()))
+            .build()
+            .unwrap();
+}
+
+// Helper macros to generate the to_*/as_* macros while accounting for
+// thunks.
+
+/// Generate an `as_*` method returning a reference to the expected
+/// type, or a type error. This only works for types that implement
+/// `Copy`, as returning a reference to an inner thunk value is not
+/// possible.
+
+/// Generate an `as_*/to_*` accessor method that returns either the
+/// expected type, or a type error.
+macro_rules! gen_cast {
+    ( $name:ident, $type:ty, $expected:expr, $variant:pat, $result:expr ) => {
+        pub fn $name(&self) -> Result<$type, ErrorKind> {
+            match self {
+                $variant => Ok($result),
+                Value::Thunk(thunk) => Self::$name(&thunk.value()),
+                other => Err(type_error($expected, &other)),
+            }
+        }
+    };
 }
 
+/// Generate an `as_*_mut/to_*_mut` accessor method that returns either the
+/// expected type, or a type error.
+macro_rules! gen_cast_mut {
+    ( $name:ident, $type:ty, $expected:expr, $variant:ident) => {
+        pub fn $name(&mut self) -> Result<&mut $type, ErrorKind> {
+            match self {
+                Value::$variant(x) => Ok(x),
+                other => Err(type_error($expected, &other)),
+            }
+        }
+    };
+}
+
+/// Generate an `is_*` type-checking method.
+macro_rules! gen_is {
+    ( $name:ident, $variant:pat ) => {
+        pub fn $name(&self) -> bool {
+            match self {
+                $variant => true,
+                Value::Thunk(thunk) => Self::$name(&thunk.value()),
+                _ => false,
+            }
+        }
+    };
+}
+
+/// Describes what input types are allowed when coercing a `Value` to a string
+#[derive(Clone, Copy, PartialEq, Eq, Debug)]
+pub struct CoercionKind {
+    /// If false only coerce already "stringly" types like strings and paths, but
+    /// also coerce sets that have a `__toString` attribute. In Tvix, this is
+    /// usually called a weak coercion. Equivalent to passing `false` as the
+    /// `coerceMore` argument of `EvalState::coerceToString` in C++ Nix.
+    ///
+    /// If true coerce all value types included by a weak coercion, but also
+    /// coerce `null`, booleans, integers, floats and lists of coercible types.
+    /// Consequently, we call this a strong coercion. Equivalent to passing
+    /// `true` as `coerceMore` in C++ Nix.
+    pub strong: bool,
+
+    /// If `import_paths` is `true`, paths are imported into the store and their
+    /// store path is the result of the coercion (equivalent to the
+    /// `copyToStore` argument of `EvalState::coerceToString` in C++ Nix).
+    pub import_paths: bool,
+}
+
+impl<T> From<T> for Value
+where
+    T: Into<NixString>,
+{
+    fn from(t: T) -> Self {
+        Self::String(t.into())
+    }
+}
+
+/// Constructors
 impl Value {
-    pub fn is_number(&self) -> bool {
-        match self {
-            Value::Integer(_) => true,
-            Value::Float(_) => true,
-            _ => false,
+    /// Construct a [`Value::Attrs`] from a [`NixAttrs`].
+    pub fn attrs(attrs: NixAttrs) -> Self {
+        Self::Attrs(Box::new(attrs))
+    }
+}
+
+/// Controls what kind of by-pointer equality comparison is allowed.
+///
+/// See `//tvix/docs/value-pointer-equality.md` for details.
+#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
+pub enum PointerEquality {
+    /// Pointer equality not allowed at all.
+    ForbidAll,
+
+    /// Pointer equality comparisons only allowed for nested values.
+    AllowNested,
+
+    /// Pointer equality comparisons are allowed in all contexts.
+    AllowAll,
+}
+
+impl Value {
+    /// Deeply forces a value, traversing e.g. lists and attribute sets and forcing
+    /// their contents, too.
+    ///
+    /// This is a generator function.
+    pub(super) async fn deep_force(self, co: GenCo, span: LightSpan) -> Result<Value, ErrorKind> {
+        if let Some(v) = Self::deep_force_(self.clone(), co, span).await? {
+            Ok(v)
+        } else {
+            Ok(self)
+        }
+    }
+
+    /// Returns Some(v) or None to indicate the returned value is myself
+    async fn deep_force_(
+        myself: Value,
+        co: GenCo,
+        span: LightSpan,
+    ) -> Result<Option<Value>, ErrorKind> {
+        // This is a stack of values which still remain to be forced.
+        let mut vals = vec![myself];
+
+        let mut thunk_set: ThunkSet = Default::default();
+
+        loop {
+            let v = if let Some(v) = vals.pop() {
+                v
+            } else {
+                return Ok(None);
+            };
+
+            // Get rid of any top-level thunks, and bail out of self-recursive
+            // thunks.
+            let value = if let Value::Thunk(t) = &v {
+                if !thunk_set.insert(t) {
+                    continue;
+                }
+                Thunk::force_(t.clone(), &co, span.clone()).await?
+            } else {
+                v
+            };
+
+            match value {
+                // Short-circuit on already evaluated values, or fail on internal values.
+                Value::Null
+                | Value::Bool(_)
+                | Value::Integer(_)
+                | Value::Float(_)
+                | Value::String(_)
+                | Value::Path(_)
+                | Value::Closure(_)
+                | Value::Builtin(_) => continue,
+
+                Value::List(list) => {
+                    for val in list.into_iter().rev() {
+                        vals.push(val);
+                    }
+                    continue;
+                }
+
+                Value::Attrs(attrs) => {
+                    for (_, val) in attrs.into_iter().rev() {
+                        vals.push(val);
+                    }
+                    continue;
+                }
+
+                Value::Thunk(_) => panic!("Tvix bug: force_value() returned a thunk"),
+
+                Value::Catchable(_) => return Ok(Some(value)),
+
+                Value::AttrNotFound
+                | Value::Blueprint(_)
+                | Value::DeferredUpvalue(_)
+                | Value::UnresolvedPath(_)
+                | Value::Json(..)
+                | Value::FinaliseRequest(_) => panic!(
+                    "Tvix bug: internal value left on stack: {}",
+                    value.type_of()
+                ),
+            }
+        }
+    }
+
+    pub async fn coerce_to_string(
+        self,
+        co: GenCo,
+        kind: CoercionKind,
+        span: LightSpan,
+    ) -> Result<Value, ErrorKind> {
+        self.coerce_to_string_(&co, kind, span).await
+    }
+
+    /// Coerce a `Value` to a string. See `CoercionKind` for a rundown of what
+    /// input types are accepted under what circumstances.
+    pub async fn coerce_to_string_(
+        self,
+        co: &GenCo,
+        kind: CoercionKind,
+        span: LightSpan,
+    ) -> Result<Value, ErrorKind> {
+        let mut result = BString::default();
+        let mut vals = vec![self];
+        // Track if we are coercing the first value of a list to correctly emit
+        // separating white spaces.
+        let mut is_list_head = None;
+        // FIXME(raitobezarius): as per https://b.tvl.fyi/issues/364
+        // we might be interested into more powerful context-related coercion kinds.
+        let mut context: NixContext = NixContext::new();
+
+        loop {
+            let value = if let Some(v) = vals.pop() {
+                v.force(co, span.clone()).await?
+            } else {
+                return Ok(Value::String(NixString::new_context_from(context, result)));
+            };
+            let coerced: Result<BString, _> = match (value, kind) {
+                // coercions that are always done
+                (Value::String(mut s), _) => {
+                    if let Some(ctx) = s.context_mut() {
+                        context = context.join(ctx);
+                    }
+                    Ok((*s).into())
+                }
+
+                // TODO(sterni): Think about proper encoding handling here. This needs
+                // general consideration anyways, since one current discrepancy between
+                // C++ Nix and Tvix is that the former's strings are arbitrary byte
+                // sequences without NUL bytes, whereas Tvix only allows valid
+                // Unicode. See also b/189.
+                (
+                    Value::Path(p),
+                    CoercionKind {
+                        import_paths: true, ..
+                    },
+                ) => {
+                    let imported = generators::request_path_import(co, *p).await;
+                    // When we import a path from the evaluator, we must attach
+                    // its original path as its context.
+                    context = context.append(NixContextElement::Plain(
+                        imported.to_string_lossy().to_string(),
+                    ));
+                    Ok(imported.into_os_string().into_encoded_bytes().into())
+                }
+                (
+                    Value::Path(p),
+                    CoercionKind {
+                        import_paths: false,
+                        ..
+                    },
+                ) => Ok(p.into_os_string().into_encoded_bytes().into()),
+
+                // Attribute sets can be converted to strings if they either have an
+                // `__toString` attribute which holds a function that receives the
+                // set itself or an `outPath` attribute which should be a string.
+                // `__toString` is preferred.
+                (Value::Attrs(attrs), kind) => {
+                    if let Some(to_string) = attrs.select("__toString") {
+                        let callable = to_string.clone().force(co, span.clone()).await?;
+
+                        // Leave the attribute set on the stack as an argument
+                        // to the function call.
+                        generators::request_stack_push(co, Value::Attrs(attrs.clone())).await;
+
+                        // Call the callable ...
+                        let result = generators::request_call(co, callable).await;
+
+                        // Recurse on the result, as attribute set coercion
+                        // actually works recursively, e.g. you can even return
+                        // /another/ set with a __toString attr.
+                        vals.push(result);
+                        continue;
+                    } else if let Some(out_path) = attrs.select("outPath") {
+                        vals.push(out_path.clone());
+                        continue;
+                    } else {
+                        return Err(ErrorKind::NotCoercibleToString { from: "set", kind });
+                    }
+                }
+
+                // strong coercions
+                (Value::Null, CoercionKind { strong: true, .. })
+                | (Value::Bool(false), CoercionKind { strong: true, .. }) => Ok("".into()),
+                (Value::Bool(true), CoercionKind { strong: true, .. }) => Ok("1".into()),
+
+                (Value::Integer(i), CoercionKind { strong: true, .. }) => Ok(format!("{i}").into()),
+                (Value::Float(f), CoercionKind { strong: true, .. }) => {
+                    // contrary to normal Display, coercing a float to a string will
+                    // result in unconditional 6 decimal places
+                    Ok(format!("{:.6}", f).into())
+                }
+
+                // Lists are coerced by coercing their elements and interspersing spaces
+                (Value::List(list), CoercionKind { strong: true, .. }) => {
+                    for elem in list.into_iter().rev() {
+                        vals.push(elem);
+                    }
+                    // In case we are coercing a list within a list we don't want
+                    // to touch this. Since the algorithm is nonrecursive, the
+                    // space would not have been created yet (due to continue).
+                    if is_list_head.is_none() {
+                        is_list_head = Some(true);
+                    }
+                    continue;
+                }
+
+                (Value::Thunk(_), _) => panic!("Tvix bug: force returned unforced thunk"),
+
+                val @ (Value::Closure(_), _)
+                | val @ (Value::Builtin(_), _)
+                | val @ (Value::Null, _)
+                | val @ (Value::Bool(_), _)
+                | val @ (Value::Integer(_), _)
+                | val @ (Value::Float(_), _)
+                | val @ (Value::List(_), _) => Err(ErrorKind::NotCoercibleToString {
+                    from: val.0.type_of(),
+                    kind,
+                }),
+
+                (c @ Value::Catchable(_), _) => return Ok(c),
+
+                (Value::AttrNotFound, _)
+                | (Value::Blueprint(_), _)
+                | (Value::DeferredUpvalue(_), _)
+                | (Value::UnresolvedPath(_), _)
+                | (Value::Json(..), _)
+                | (Value::FinaliseRequest(_), _) => {
+                    panic!("tvix bug: .coerce_to_string() called on internal value")
+                }
+            };
+
+            if let Some(head) = is_list_head {
+                if !head {
+                    result.push(b' ');
+                } else {
+                    is_list_head = Some(false);
+                }
+            }
+
+            result.push_str(&coerced?);
+        }
+    }
+
+    pub(crate) async fn nix_eq_owned_genco(
+        self,
+        other: Value,
+        co: GenCo,
+        ptr_eq: PointerEquality,
+        span: LightSpan,
+    ) -> Result<Value, ErrorKind> {
+        self.nix_eq(other, &co, ptr_eq, span).await
+    }
+
+    /// Compare two Nix values for equality, forcing nested parts of the structure
+    /// as needed.
+    ///
+    /// This comparison needs to be invoked for nested values (e.g. in lists and
+    /// attribute sets) as well, which is done by suspending and asking the VM to
+    /// perform the nested comparison.
+    ///
+    /// The `top_level` parameter controls whether this invocation is the top-level
+    /// comparison, or a nested value comparison. See
+    /// `//tvix/docs/value-pointer-equality.md`
+    pub(crate) async fn nix_eq(
+        self,
+        other: Value,
+        co: &GenCo,
+        ptr_eq: PointerEquality,
+        span: LightSpan,
+    ) -> Result<Value, ErrorKind> {
+        // this is a stack of ((v1,v2),peq) triples to be compared;
+        // after each triple is popped off of the stack, v1 is
+        // compared to v2 using peq-mode PointerEquality
+        let mut vals = vec![((self, other), ptr_eq)];
+
+        loop {
+            let ((a, b), ptr_eq) = if let Some(abp) = vals.pop() {
+                abp
+            } else {
+                // stack is empty, so comparison has succeeded
+                return Ok(Value::Bool(true));
+            };
+            let a = match a {
+                Value::Thunk(thunk) => {
+                    // If both values are thunks, and thunk comparisons are allowed by
+                    // pointer, do that and move on.
+                    if ptr_eq == PointerEquality::AllowAll {
+                        if let Value::Thunk(t1) = &b {
+                            if t1.ptr_eq(&thunk) {
+                                continue;
+                            }
+                        }
+                    };
+
+                    Thunk::force_(thunk, co, span.clone()).await?
+                }
+
+                _ => a,
+            };
+
+            let b = b.force(co, span.clone()).await?;
+
+            debug_assert!(!matches!(a, Value::Thunk(_)));
+            debug_assert!(!matches!(b, Value::Thunk(_)));
+
+            let result = match (a, b) {
+                // Trivial comparisons
+                (c @ Value::Catchable(_), _) => return Ok(c),
+                (_, c @ Value::Catchable(_)) => return Ok(c),
+                (Value::Null, Value::Null) => true,
+                (Value::Bool(b1), Value::Bool(b2)) => b1 == b2,
+                (Value::String(s1), Value::String(s2)) => s1 == s2,
+                (Value::Path(p1), Value::Path(p2)) => p1 == p2,
+
+                // Numerical comparisons (they work between float & int)
+                (Value::Integer(i1), Value::Integer(i2)) => i1 == i2,
+                (Value::Integer(i), Value::Float(f)) => i as f64 == f,
+                (Value::Float(f1), Value::Float(f2)) => f1 == f2,
+                (Value::Float(f), Value::Integer(i)) => i as f64 == f,
+
+                // List comparisons
+                (Value::List(l1), Value::List(l2)) => {
+                    if ptr_eq >= PointerEquality::AllowNested && l1.ptr_eq(&l2) {
+                        continue;
+                    }
+
+                    if l1.len() != l2.len() {
+                        return Ok(Value::Bool(false));
+                    }
+
+                    vals.extend(l1.into_iter().rev().zip(l2.into_iter().rev()).zip(
+                        std::iter::repeat(std::cmp::max(ptr_eq, PointerEquality::AllowNested)),
+                    ));
+                    continue;
+                }
+
+                (_, Value::List(_)) | (Value::List(_), _) => return Ok(Value::Bool(false)),
+
+                // Attribute set comparisons
+                (Value::Attrs(a1), Value::Attrs(a2)) => {
+                    if ptr_eq >= PointerEquality::AllowNested && a1.ptr_eq(&a2) {
+                        continue;
+                    }
+
+                    // Special-case for derivation comparisons: If both attribute sets
+                    // have `type = derivation`, compare them by `outPath`.
+                    #[allow(clippy::single_match)] // might need more match arms later
+                    match (a1.select("type"), a2.select("type")) {
+                        (Some(v1), Some(v2)) => {
+                            let s1 = v1.clone().force(co, span.clone()).await?;
+                            if s1.is_catchable() {
+                                return Ok(s1);
+                            }
+                            let s2 = v2.clone().force(co, span.clone()).await?;
+                            if s2.is_catchable() {
+                                return Ok(s2);
+                            }
+                            let s1 = s1.to_str();
+                            let s2 = s2.to_str();
+
+                            if let (Ok(s1), Ok(s2)) = (s1, s2) {
+                                if s1 == "derivation" && s2 == "derivation" {
+                                    // TODO(tazjin): are the outPaths really required,
+                                    // or should it fall through?
+                                    let out1 = a1
+                                        .select_required("outPath")
+                                        .context("comparing derivations")?
+                                        .clone();
+
+                                    let out2 = a2
+                                        .select_required("outPath")
+                                        .context("comparing derivations")?
+                                        .clone();
+
+                                    let out1 = out1.clone().force(co, span.clone()).await?;
+                                    let out2 = out2.clone().force(co, span.clone()).await?;
+
+                                    if out1.is_catchable() {
+                                        return Ok(out1);
+                                    }
+
+                                    if out2.is_catchable() {
+                                        return Ok(out2);
+                                    }
+
+                                    let result =
+                                        out1.to_contextful_str()? == out2.to_contextful_str()?;
+                                    if !result {
+                                        return Ok(Value::Bool(false));
+                                    } else {
+                                        continue;
+                                    }
+                                }
+                            }
+                        }
+                        _ => {}
+                    };
+
+                    if a1.len() != a2.len() {
+                        return Ok(Value::Bool(false));
+                    }
+
+                    // note that it is important to be careful here with the
+                    // order we push the keys and values in order to properly
+                    // compare attrsets containing `throw` elements.
+                    let iter1 = a1.into_iter_sorted().rev();
+                    let iter2 = a2.into_iter_sorted().rev();
+                    for ((k1, v1), (k2, v2)) in iter1.zip(iter2) {
+                        vals.push((
+                            (v1, v2),
+                            std::cmp::max(ptr_eq, PointerEquality::AllowNested),
+                        ));
+                        vals.push((
+                            (k1.into(), k2.into()),
+                            std::cmp::max(ptr_eq, PointerEquality::AllowNested),
+                        ));
+                    }
+                    continue;
+                }
+
+                (Value::Attrs(_), _) | (_, Value::Attrs(_)) => return Ok(Value::Bool(false)),
+
+                (Value::Closure(c1), Value::Closure(c2))
+                    if ptr_eq >= PointerEquality::AllowNested =>
+                {
+                    if Rc::ptr_eq(&c1, &c2) {
+                        continue;
+                    } else {
+                        return Ok(Value::Bool(false));
+                    }
+                }
+
+                // Everything else is either incomparable (e.g. internal types) or
+                // false.
+                _ => return Ok(Value::Bool(false)),
+            };
+            if !result {
+                return Ok(Value::Bool(false));
+            }
         }
     }
 
@@ -43,74 +668,406 @@ impl Value {
             Value::Integer(_) => "int",
             Value::Float(_) => "float",
             Value::String(_) => "string",
+            Value::Path(_) => "path",
             Value::Attrs(_) => "set",
             Value::List(_) => "list",
+            Value::Closure(_) | Value::Builtin(_) => "lambda",
 
-            // Internal types
-            Value::AttrPath(_) => "internal",
+            // Internal types. Note: These are only elaborated here
+            // because it makes debugging easier. If a user ever sees
+            // any of these strings, it's a bug.
+            Value::Thunk(_) => "internal[thunk]",
+            Value::AttrNotFound => "internal[attr_not_found]",
+            Value::Blueprint(_) => "internal[blueprint]",
+            Value::DeferredUpvalue(_) => "internal[deferred_upvalue]",
+            Value::UnresolvedPath(_) => "internal[unresolved_path]",
+            Value::Json(..) => "internal[json]",
+            Value::FinaliseRequest(_) => "internal[finaliser_sentinel]",
+            Value::Catchable(_) => "internal[catchable]",
         }
     }
 
-    pub fn as_bool(self) -> EvalResult<bool> {
+    gen_cast!(as_bool, bool, "bool", Value::Bool(b), *b);
+    gen_cast!(as_int, i64, "int", Value::Integer(x), *x);
+    gen_cast!(as_float, f64, "float", Value::Float(x), *x);
+
+    /// Cast the current value into a **context-less** string.
+    /// If you wanted to cast it into a potentially contextful string,
+    /// you have to explicitly use `to_contextful_str`.
+    /// Contextful strings are special, they should not be obtained
+    /// everytime you want a string.
+    pub fn to_str(&self) -> Result<NixString, ErrorKind> {
         match self {
-            Value::Bool(b) => Ok(b),
-            other => Err(Error::TypeError {
-                expected: "bool",
-                actual: other.type_of(),
-            }),
+            Value::String(s) if !s.has_context() => Ok((*s).clone()),
+            Value::Thunk(thunk) => Self::to_str(&thunk.value()),
+            other => Err(type_error("contextless strings", other)),
         }
     }
 
-    pub fn as_string(self) -> EvalResult<NixString> {
+    gen_cast!(
+        to_contextful_str,
+        NixString,
+        "contextful string",
+        Value::String(s),
+        (*s).clone()
+    );
+    gen_cast!(to_path, Box<PathBuf>, "path", Value::Path(p), p.clone());
+    gen_cast!(to_attrs, Box<NixAttrs>, "set", Value::Attrs(a), a.clone());
+    gen_cast!(to_list, NixList, "list", Value::List(l), l.clone());
+    gen_cast!(
+        as_closure,
+        Rc<Closure>,
+        "lambda",
+        Value::Closure(c),
+        c.clone()
+    );
+
+    gen_cast_mut!(as_list_mut, NixList, "list", List);
+
+    gen_is!(is_path, Value::Path(_));
+    gen_is!(is_number, Value::Integer(_) | Value::Float(_));
+    gen_is!(is_bool, Value::Bool(_));
+    gen_is!(is_attrs, Value::Attrs(_));
+    gen_is!(is_catchable, Value::Catchable(_));
+
+    /// Returns `true` if the value is a [`Thunk`].
+    ///
+    /// [`Thunk`]: Value::Thunk
+    pub fn is_thunk(&self) -> bool {
+        matches!(self, Self::Thunk(..))
+    }
+
+    /// Compare `self` against other using (fallible) Nix ordering semantics.
+    ///
+    /// The function is intended to be used from within other generator
+    /// functions or `gen!` blocks.
+    pub async fn nix_cmp_ordering(
+        self,
+        other: Self,
+        co: GenCo,
+        span: LightSpan,
+    ) -> Result<Result<Ordering, CatchableErrorKind>, ErrorKind> {
+        Self::nix_cmp_ordering_(self, other, co, span).await
+    }
+
+    async fn nix_cmp_ordering_(
+        myself: Self,
+        other: Self,
+        co: GenCo,
+        span: LightSpan,
+    ) -> Result<Result<Ordering, CatchableErrorKind>, ErrorKind> {
+        // this is a stack of ((v1,v2),peq) triples to be compared;
+        // after each triple is popped off of the stack, v1 is
+        // compared to v2 using peq-mode PointerEquality
+        let mut vals = vec![((myself, other), PointerEquality::ForbidAll)];
+
+        loop {
+            let ((mut a, mut b), ptr_eq) = if let Some(abp) = vals.pop() {
+                abp
+            } else {
+                // stack is empty, so they are equal
+                return Ok(Ok(Ordering::Equal));
+            };
+            if ptr_eq == PointerEquality::AllowAll {
+                if a.clone()
+                    .nix_eq(b.clone(), &co, PointerEquality::AllowAll, span.clone())
+                    .await?
+                    .as_bool()?
+                {
+                    continue;
+                }
+                a = a.force(&co, span.clone()).await?;
+                b = b.force(&co, span.clone()).await?;
+            }
+            let result = match (a, b) {
+                (Value::Catchable(c), _) => return Ok(Err(*c)),
+                (_, Value::Catchable(c)) => return Ok(Err(*c)),
+                // same types
+                (Value::Integer(i1), Value::Integer(i2)) => i1.cmp(&i2),
+                (Value::Float(f1), Value::Float(f2)) => f1.total_cmp(&f2),
+                (Value::String(s1), Value::String(s2)) => s1.cmp(&s2),
+                (Value::List(l1), Value::List(l2)) => {
+                    let max = l1.len().max(l2.len());
+                    for j in 0..max {
+                        let i = max - 1 - j;
+                        if i >= l2.len() {
+                            vals.push(((1.into(), 0.into()), PointerEquality::ForbidAll));
+                        } else if i >= l1.len() {
+                            vals.push(((0.into(), 1.into()), PointerEquality::ForbidAll));
+                        } else {
+                            vals.push(((l1[i].clone(), l2[i].clone()), PointerEquality::AllowAll));
+                        }
+                    }
+                    continue;
+                }
+
+                // different types
+                (Value::Integer(i1), Value::Float(f2)) => (i1 as f64).total_cmp(&f2),
+                (Value::Float(f1), Value::Integer(i2)) => f1.total_cmp(&(i2 as f64)),
+
+                // unsupported types
+                (lhs, rhs) => {
+                    return Err(ErrorKind::Incomparable {
+                        lhs: lhs.type_of(),
+                        rhs: rhs.type_of(),
+                    })
+                }
+            };
+            if result != Ordering::Equal {
+                return Ok(Ok(result));
+            }
+        }
+    }
+
+    // TODO(amjoseph): de-asyncify this (when called directly by the VM)
+    pub async fn force(self, co: &GenCo, span: LightSpan) -> Result<Value, ErrorKind> {
+        if let Value::Thunk(thunk) = self {
+            // TODO(amjoseph): use #[tailcall::mutual]
+            return Thunk::force_(thunk, co, span).await;
+        }
+        Ok(self)
+    }
+
+    // need two flavors, because async
+    pub async fn force_owned_genco(self, co: GenCo, span: LightSpan) -> Result<Value, ErrorKind> {
+        if let Value::Thunk(thunk) = self {
+            // TODO(amjoseph): use #[tailcall::mutual]
+            return Thunk::force_(thunk, &co, span).await;
+        }
+        Ok(self)
+    }
+
+    /// Explain a value in a human-readable way, e.g. by presenting
+    /// the docstrings of functions if present.
+    pub fn explain(&self) -> String {
         match self {
-            Value::String(s) => Ok(s),
-            other => Err(Error::TypeError {
-                expected: "string",
-                actual: other.type_of(),
-            }),
+            Value::Null => "the 'null' value".into(),
+            Value::Bool(b) => format!("the boolean value '{}'", b),
+            Value::Integer(i) => format!("the integer '{}'", i),
+            Value::Float(f) => format!("the float '{}'", f),
+            Value::String(s) if s.has_context() => format!("the contextful string '{}'", s),
+            Value::String(s) => format!("the contextless string '{}'", s),
+            Value::Path(p) => format!("the path '{}'", p.to_string_lossy()),
+            Value::Attrs(attrs) => format!("a {}-item attribute set", attrs.len()),
+            Value::List(list) => format!("a {}-item list", list.len()),
+
+            Value::Closure(f) => {
+                if let Some(name) = &f.lambda.name {
+                    format!("the user-defined Nix function '{}'", name)
+                } else {
+                    "a user-defined Nix function".to_string()
+                }
+            }
+
+            Value::Builtin(b) => {
+                let mut out = format!("the builtin function '{}'", b.name());
+                if let Some(docs) = b.documentation() {
+                    out.push_str("\n\n");
+                    out.push_str(docs);
+                }
+                out
+            }
+
+            // TODO: handle suspended thunks with a different explanation instead of panicking
+            Value::Thunk(t) => t.value().explain(),
+
+            Value::Catchable(_) => "a catchable failure".into(),
+
+            Value::AttrNotFound
+            | Value::Blueprint(_)
+            | Value::DeferredUpvalue(_)
+            | Value::UnresolvedPath(_)
+            | Value::Json(..)
+            | Value::FinaliseRequest(_) => "an internal Tvix evaluator value".into(),
         }
     }
 }
 
+trait TotalDisplay {
+    fn total_fmt(&self, f: &mut std::fmt::Formatter<'_>, set: &mut ThunkSet) -> std::fmt::Result;
+}
+
 impl Display for Value {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        self.total_fmt(f, &mut Default::default())
+    }
+}
+
+/// Emulates the C++-Nix style formatting of floats, which diverges
+/// significantly from Rust's native float formatting.
+fn total_fmt_float<F: std::fmt::Write>(num: f64, mut f: F) -> std::fmt::Result {
+    let mut buf = [b'0'; lexical_core::BUFFER_SIZE];
+    let mut s = lexical_core::write_with_options::<f64, { CXX_LITERAL }>(
+        num,
+        &mut buf,
+        &WRITE_FLOAT_OPTIONS,
+    );
+
+    // apply some postprocessing on the buffer. If scientific
+    // notation is used (we see an `e`), and the next character is
+    // a digit, add the missing `+` sign.)
+    let mut new_s = Vec::with_capacity(s.len());
+
+    if s.contains(&b'e') {
+        for (i, c) in s.iter().enumerate() {
+            // encountered `e`
+            if c == &b'e' {
+                // next character is a digit (so no negative exponent)
+                if s.len() > i && s[i + 1].is_ascii_digit() {
+                    // copy everything from the start up to (including) the e
+                    new_s.extend_from_slice(&s[0..=i]);
+                    // add the missing '+'
+                    new_s.push(b'+');
+                    // check for the remaining characters.
+                    // If it's only one, we need to prepend a trailing zero
+                    if s.len() == i + 2 {
+                        new_s.push(b'0');
+                    }
+                    new_s.extend_from_slice(&s[i + 1..]);
+                    break;
+                }
+            }
+        }
+
+        // if we modified the scientific notation, flip the reference
+        if !new_s.is_empty() {
+            s = &mut new_s
+        }
+    } else if s.contains(&b'.') {
+        // else, if this is not scientific notation, and there's a
+        // decimal point, make sure we really drop trailing zeroes.
+        // In some cases, lexical_core doesn't.
+        for (i, c) in s.iter().enumerate() {
+            // at `.``
+            if c == &b'.' {
+                // trim zeroes from the right side.
+                let frac = String::from_utf8_lossy(&s[i + 1..]);
+                let frac_no_trailing_zeroes = frac.trim_end_matches('0');
+
+                if frac.len() != frac_no_trailing_zeroes.len() {
+                    // we managed to strip something, construct new_s
+                    if frac_no_trailing_zeroes.is_empty() {
+                        // if frac_no_trailing_zeroes is empty, the fractional part was all zeroes, so we can drop the decimal point as well
+                        new_s.extend_from_slice(&s[0..=i - 1]);
+                    } else {
+                        // else, assemble the rest of the string
+                        new_s.extend_from_slice(&s[0..=i]);
+                        new_s.extend_from_slice(frac_no_trailing_zeroes.as_bytes());
+                    }
+
+                    // flip the reference
+                    s = &mut new_s;
+                    break;
+                }
+            }
+        }
+    }
+
+    write!(f, "{}", String::from_utf8_lossy(s))
+}
+
+impl TotalDisplay for Value {
+    fn total_fmt(&self, f: &mut std::fmt::Formatter<'_>, set: &mut ThunkSet) -> std::fmt::Result {
         match self {
             Value::Null => f.write_str("null"),
             Value::Bool(true) => f.write_str("true"),
             Value::Bool(false) => f.write_str("false"),
-            Value::Integer(num) => f.write_fmt(format_args!("{}", num)),
-            Value::Float(num) => f.write_fmt(format_args!("{}", num)),
+            Value::Integer(num) => write!(f, "{}", num),
             Value::String(s) => s.fmt(f),
-            Value::Attrs(attrs) => attrs.fmt(f),
-            Value::List(list) => list.fmt(f),
+            Value::Path(p) => p.display().fmt(f),
+            Value::Attrs(attrs) => attrs.total_fmt(f, set),
+            Value::List(list) => list.total_fmt(f, set),
+            // TODO: fancy REPL display with position
+            Value::Closure(_) => f.write_str("<LAMBDA>"),
+            Value::Builtin(builtin) => builtin.fmt(f),
+
+            // Nix prints floats with a maximum precision of 5 digits
+            // only. Except when it decides to use scientific notation
+            // (with a + after the `e`, and zero-padded to 0 digits)
+            Value::Float(num) => total_fmt_float(*num, f),
 
             // internal types
-            Value::AttrPath(_) => f.write_str("internal"),
+            Value::AttrNotFound => f.write_str("internal[not found]"),
+            Value::Blueprint(_) => f.write_str("internal[blueprint]"),
+            Value::DeferredUpvalue(_) => f.write_str("internal[deferred_upvalue]"),
+            Value::UnresolvedPath(_) => f.write_str("internal[unresolved_path]"),
+            Value::Json(..) => f.write_str("internal[json]"),
+            Value::FinaliseRequest(_) => f.write_str("internal[finaliser_sentinel]"),
+
+            // Delegate thunk display to the type, as it must handle
+            // the case of already evaluated or cyclic thunks.
+            Value::Thunk(t) => t.total_fmt(f, set),
+            Value::Catchable(_) => panic!("total_fmt() called on a CatchableErrorKind"),
         }
     }
 }
 
-impl PartialEq for Value {
-    fn eq(&self, other: &Self) -> bool {
-        match (self, other) {
-            // Trivial comparisons
-            (Value::Null, Value::Null) => true,
-            (Value::Bool(b1), Value::Bool(b2)) => b1 == b2,
-            (Value::List(l1), Value::List(l2)) => l1 == l2,
-            (Value::String(s1), Value::String(s2)) => s1 == s2,
+impl From<bool> for Value {
+    fn from(b: bool) -> Self {
+        Value::Bool(b)
+    }
+}
+
+impl From<i64> for Value {
+    fn from(i: i64) -> Self {
+        Self::Integer(i)
+    }
+}
+
+impl From<f64> for Value {
+    fn from(i: f64) -> Self {
+        Self::Float(i)
+    }
+}
+
+impl From<PathBuf> for Value {
+    fn from(path: PathBuf) -> Self {
+        Self::Path(Box::new(path))
+    }
+}
 
-            // Numerical comparisons (they work between float & int)
-            (Value::Integer(i1), Value::Integer(i2)) => i1 == i2,
-            (Value::Integer(i), Value::Float(f)) => *i as f64 == *f,
-            (Value::Float(f1), Value::Float(f2)) => f1 == f2,
-            (Value::Float(f), Value::Integer(i)) => *i as f64 == *f,
+fn type_error(expected: &'static str, actual: &Value) -> ErrorKind {
+    ErrorKind::TypeError {
+        expected,
+        actual: actual.type_of(),
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use std::mem::size_of;
+
+    #[test]
+    fn size() {
+        assert_eq!(size_of::<Value>(), 16);
+    }
 
-            // Optimised attribute set comparison
-            (Value::Attrs(a1), Value::Attrs(a2)) => Rc::ptr_eq(a1, a2) || { a1 == a2 },
+    mod floats {
+        use crate::value::total_fmt_float;
 
-            // Everything else is either incomparable (e.g. internal
-            // types) or false.
-            _ => false,
+        #[test]
+        fn format_float() {
+            let ff = [
+                (0f64, "0"),
+                (1.0f64, "1"),
+                (-0.01, "-0.01"),
+                (5e+22, "5e+22"),
+                (1e6, "1e+06"),
+                (-2E-2, "-0.02"),
+                (6.626e-34, "6.626e-34"),
+                (9_224_617.445_991_227, "9.22462e+06"),
+            ];
+            for (n, expected) in ff.iter() {
+                let mut buf = String::new();
+                let res = total_fmt_float(*n, &mut buf);
+                assert!(res.is_ok());
+                assert_eq!(
+                    expected, &buf,
+                    "{} should be formatted as {}, but got {}",
+                    n, expected, &buf
+                );
+            }
         }
     }
 }
diff --git a/tvix/eval/src/value/path.rs b/tvix/eval/src/value/path.rs
new file mode 100644
index 0000000000..ad526a8746
--- /dev/null
+++ b/tvix/eval/src/value/path.rs
@@ -0,0 +1,14 @@
+use path_clean::PathClean;
+use std::path::PathBuf;
+
+/// This function should match the behavior of canonPath() in
+/// src/libutil/util.cc of cppnix.  Currently it does not match that
+/// behavior; it uses the `path_clean` library which is based on the
+/// Go standard library
+///
+/// TODO: make this match the behavior of cppnix
+/// TODO: write tests for this
+
+pub fn canon_path(path: PathBuf) -> PathBuf {
+    path.clean()
+}
diff --git a/tvix/eval/src/value/string.rs b/tvix/eval/src/value/string.rs
index 531bcf547b..ceb43f1ea5 100644
--- a/tvix/eval/src/value/string.rs
+++ b/tvix/eval/src/value/string.rs
@@ -1,13 +1,873 @@
-use std::fmt::Display;
+//! This module implements Nix language strings.
+//!
+//! Nix language strings never need to be modified on the language
+//! level, allowing us to shave off some memory overhead and only
+//! paying the cost when creating new strings.
+use bstr::{BStr, BString, ByteSlice, Chars};
+use rnix::ast;
+use std::alloc::{alloc, dealloc, handle_alloc_error, Layout};
+use std::borrow::{Borrow, Cow};
+use std::collections::HashSet;
+use std::ffi::c_void;
+use std::fmt::{self, Debug, Display};
+use std::hash::Hash;
+use std::ops::Deref;
+use std::ptr::{self, NonNull};
+use std::slice;
 
-/// This module implements Nix language strings and their different
-/// backing implementations.
+use serde::de::{Deserializer, Visitor};
+use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
-pub struct NixString(pub String);
+#[derive(Clone, Debug, Serialize, Hash, PartialEq, Eq)]
+pub enum NixContextElement {
+    /// A plain store path (e.g. source files copied to the store)
+    Plain(String),
+
+    /// Single output of a derivation, represented by its name and its derivation path.
+    Single { name: String, derivation: String },
+
+    /// A reference to a complete derivation
+    /// including its source and its binary closure.
+    /// It is used for the `drvPath` attribute context.
+    /// The referred string is the store path to
+    /// the derivation path.
+    Derivation(String),
+}
+
+/// Nix context strings representation in Tvix. This tracks a set of different kinds of string
+/// dependencies that we can come across during manipulation of our language primitives, mostly
+/// strings. There's some simple algebra of context strings and how they propagate w.r.t. primitive
+/// operations, e.g. concatenation, interpolation and other string operations.
+#[repr(transparent)]
+#[derive(Clone, Debug, Serialize, Default)]
+pub struct NixContext(HashSet<NixContextElement>);
+
+impl From<NixContextElement> for NixContext {
+    fn from(value: NixContextElement) -> Self {
+        Self([value].into())
+    }
+}
+
+impl From<HashSet<NixContextElement>> for NixContext {
+    fn from(value: HashSet<NixContextElement>) -> Self {
+        Self(value)
+    }
+}
+
+impl NixContext {
+    /// Creates an empty context that can be populated
+    /// and passed to form a contextful [NixString], albeit
+    /// if the context is concretly empty, the resulting [NixString]
+    /// will be contextless.
+    pub fn new() -> Self {
+        Self::default()
+    }
+
+    /// For internal consumers, we let people observe
+    /// if the [NixContext] is actually empty or not
+    /// to decide whether they want to skip the allocation
+    /// of a full blown [HashSet].
+    pub(crate) fn is_empty(&self) -> bool {
+        self.0.is_empty()
+    }
+
+    /// Consumes a new [NixContextElement] and add it if not already
+    /// present in this context.
+    pub fn append(mut self, other: NixContextElement) -> Self {
+        self.0.insert(other);
+        self
+    }
+
+    /// Consumes both ends of the join into a new NixContent
+    /// containing the union of elements of both ends.
+    pub fn join(mut self, other: &mut NixContext) -> Self {
+        let other_set = std::mem::take(&mut other.0);
+        let mut set: HashSet<NixContextElement> = std::mem::take(&mut self.0);
+        set.extend(other_set);
+        Self(set)
+    }
+
+    /// Copies from another [NixString] its context strings
+    /// in this context.
+    pub fn mimic(&mut self, other: &NixString) {
+        if let Some(context) = other.context() {
+            self.0.extend(context.iter().cloned());
+        }
+    }
+
+    /// Iterates over "plain" context elements, e.g. sources imported
+    /// in the store without more information, i.e. `toFile` or coerced imported paths.
+    /// It yields paths to the store.
+    pub fn iter_plain(&self) -> impl Iterator<Item = &str> {
+        self.iter().filter_map(|elt| {
+            if let NixContextElement::Plain(s) = elt {
+                Some(s.as_str())
+            } else {
+                None
+            }
+        })
+    }
+
+    /// Iterates over "full derivations" context elements, e.g. something
+    /// referring to their `drvPath`, i.e. their full sources and binary closure.
+    /// It yields derivation paths.
+    pub fn iter_derivation(&self) -> impl Iterator<Item = &str> {
+        self.iter().filter_map(|elt| {
+            if let NixContextElement::Derivation(s) = elt {
+                Some(s.as_str())
+            } else {
+                None
+            }
+        })
+    }
+
+    /// Iterates over "single" context elements, e.g. single derived paths,
+    /// or also known as the single output of a given derivation.
+    /// The first element of the tuple is the output name
+    /// and the second element is the derivation path.
+    pub fn iter_single_outputs(&self) -> impl Iterator<Item = (&str, &str)> {
+        self.iter().filter_map(|elt| {
+            if let NixContextElement::Single { name, derivation } = elt {
+                Some((name.as_str(), derivation.as_str()))
+            } else {
+                None
+            }
+        })
+    }
+
+    /// Iterates over any element of the context.
+    pub fn iter(&self) -> impl Iterator<Item = &NixContextElement> {
+        self.0.iter()
+    }
+
+    /// Produces a list of owned references to this current context,
+    /// no matter its type.
+    pub fn to_owned_references(self) -> Vec<String> {
+        self.0
+            .into_iter()
+            .map(|ctx| match ctx {
+                NixContextElement::Derivation(drv_path) => drv_path,
+                NixContextElement::Plain(store_path) => store_path,
+                NixContextElement::Single { derivation, .. } => derivation,
+            })
+            .collect()
+    }
+}
+
+/// This type is never instantiated, but serves to document the memory layout of the actual heap
+/// allocation for Nix strings.
+#[allow(dead_code)]
+struct NixStringInner {
+    /// The string context, if any.  Note that this is boxed to take advantage of the null pointer
+    /// niche, otherwise this field ends up being very large:
+    ///
+    /// ```notrust
+    /// >> std::mem::size_of::<Option<HashSet<String>>>()
+    /// 48
+    ///
+    /// >> std::mem::size_of::<Option<Box<HashSet<String>>>>()
+    /// 8
+    /// ```
+    context: Option<Box<NixContext>>,
+    /// The length of the data, stored *inline in the allocation*
+    length: usize,
+    /// The actual data for the string itself. Will always be `length` bytes long
+    data: [u8],
+}
+
+#[allow(clippy::zst_offset)]
+impl NixStringInner {
+    /// Construct a [`Layout`] for a nix string allocation with the given length.
+    ///
+    /// Returns a tuple of:
+    /// 1. The layout itself.
+    /// 2. The offset of [`Self::length`] within the allocation, assuming the allocation starts at 0
+    /// 3. The offset of the data array within the allocation, assuming the allocation starts at 0
+    fn layout(len: usize) -> (Layout, usize, usize) {
+        let layout = Layout::new::<Option<Box<NixContext>>>();
+        let (layout, len_offset) = layout.extend(Layout::new::<usize>()).unwrap();
+        let (layout, data_offset) = layout.extend(Layout::array::<u8>(len).unwrap()).unwrap();
+        (layout, len_offset, data_offset)
+    }
+
+    /// Returns the [`Layout`] for an *already-allocated* nix string, loading the length from the
+    /// pointer.
+    ///
+    /// Returns a tuple of:
+    /// 1. The layout itself.
+    /// 2. The offset of [`Self::length`] within the allocation, assuming the allocation starts at 0
+    /// 3. The offset of the data array within the allocation, assuming the allocation starts at 0
+    ///
+    /// # Safety
+    ///
+    /// This function must only be called on a pointer that has been properly initialized with
+    /// [`Self::alloc`]. The data buffer may not necessarily be initialized
+    unsafe fn layout_of(this: NonNull<c_void>) -> (Layout, usize, usize) {
+        let layout = Layout::new::<Option<Box<NixContext>>>();
+        let (_, len_offset) = layout.extend(Layout::new::<usize>()).unwrap();
+        // SAFETY: Layouts are linear, so even though we haven't involved data at all yet, we know
+        // the len_offset is a valid offset into the second field of the allocation
+        let len = *(this.as_ptr().add(len_offset) as *const usize);
+        Self::layout(len)
+    }
+
+    /// Allocate an *uninitialized* nix string with the given length. Writes the length to the
+    /// length value in the pointer, but leaves both context and data uninitialized
+    ///
+    /// This function is safe to call (as constructing pointers of any sort of validity is always
+    /// safe in Rust) but it is unsafe to use the resulting pointer to do anything other than
+    ///
+    /// 1. Read the length
+    /// 2. Write the context
+    /// 3. Write the data
+    ///
+    /// until the string is fully initialized
+    fn alloc(len: usize) -> NonNull<c_void> {
+        let (layout, len_offset, _data_offset) = Self::layout(len);
+        debug_assert_ne!(layout.size(), 0);
+        unsafe {
+            // SAFETY: Layout has non-zero size, since the layout of the context and the
+            // layout of the len both have non-zero size
+            let ptr = alloc(layout);
+
+            if let Some(this) = NonNull::new(ptr as *mut _) {
+                // SAFETY: We've allocated with a layout that causes the len_offset to be in-bounds
+                // and writeable, and if the allocation succeeded it won't wrap
+                ((this.as_ptr() as *mut u8).add(len_offset) as *mut usize).write(len);
+                debug_assert_eq!(Self::len(this), len);
+                this
+            } else {
+                handle_alloc_error(layout);
+            }
+        }
+    }
+
+    /// Deallocate the Nix string at the given pointer
+    ///
+    /// # Safety
+    ///
+    /// This function must only be called with a pointer that has been properly initialized with
+    /// [`Self::alloc`]
+    unsafe fn dealloc(this: NonNull<c_void>) {
+        let (layout, _, _) = Self::layout_of(this);
+        // SAFETY: okay because of the safety guarantees of this method
+        dealloc(this.as_ptr() as *mut u8, layout)
+    }
+
+    /// Return the length of the Nix string at the given pointer
+    ///
+    /// # Safety
+    ///
+    /// This function must only be called with a pointer that has been properly initialized with
+    /// [`Self::alloc`]
+    unsafe fn len(this: NonNull<c_void>) -> usize {
+        let (_, len_offset, _) = Self::layout_of(this);
+        // SAFETY: As long as the safety guarantees of this method are upheld, we've allocated with
+        // a layout that causes the len_offset to be in-bounds and writeable, and if the allocation
+        // succeeded it won't wrap
+        *(this.as_ptr().add(len_offset) as *const usize)
+    }
+
+    /// Return a pointer to the context value within the given Nix string pointer
+    ///
+    /// # Safety
+    ///
+    /// This function must only be called with a pointer that has been properly initialized with
+    /// [`Self::alloc`]
+    unsafe fn context_ptr(this: NonNull<c_void>) -> *mut Option<Box<NixContext>> {
+        // SAFETY: The context is the first field in the layout of the allocation
+        this.as_ptr() as *mut Option<Box<NixContext>>
+    }
+
+    /// Construct a shared reference to the context value within the given Nix string pointer
+    ///
+    /// # Safety
+    ///
+    /// This function must only be called with a pointer that has been properly initialized with
+    /// [`Self::alloc`], and where the context has been properly initialized (by writing to the
+    /// pointer returned from [`Self::context_ptr`]).
+    ///
+    /// Also, all the normal Rust rules about pointer-to-reference conversion apply. See
+    /// [`NonNull::as_ref`] for more.
+    unsafe fn context_ref<'a>(this: NonNull<c_void>) -> &'a Option<Box<NixContext>> {
+        Self::context_ptr(this).as_ref().unwrap()
+    }
+
+    /// Construct a mutable reference to the context value within the given Nix string pointer
+    ///
+    /// # Safety
+    ///
+    /// This function must only be called with a pointer that has been properly initialized with
+    /// [`Self::alloc`], and where the context has been properly initialized (by writing to the
+    /// pointer returned from [`Self::context_ptr`]).
+    ///
+    /// Also, all the normal Rust rules about pointer-to-reference conversion apply. See
+    /// [`NonNull::as_mut`] for more.
+    unsafe fn context_mut<'a>(this: NonNull<c_void>) -> &'a mut Option<Box<NixContext>> {
+        Self::context_ptr(this).as_mut().unwrap()
+    }
+
+    /// Return a pointer to the data array within the given Nix string pointer
+    ///
+    /// # Safety
+    ///
+    /// This function must only be called with a pointer that has been properly initialized with
+    /// [`Self::alloc`]
+    unsafe fn data_ptr(this: NonNull<c_void>) -> *mut u8 {
+        let (_, _, data_offset) = Self::layout_of(this);
+        // SAFETY: data is the third field in the layout of the allocation
+        this.as_ptr().add(data_offset) as *mut u8
+    }
+
+    /// Construct a shared reference to the data slice within the given Nix string pointer
+    ///
+    /// # Safety
+    ///
+    /// This function must only be called with a pointer that has been properly initialized with
+    /// [`Self::alloc`], and where the data array has been properly initialized (by writing to the
+    /// pointer returned from [`Self::data_ptr`]).
+    ///
+    /// Also, all the normal Rust rules about pointer-to-reference conversion apply. See
+    /// [`slice::from_raw_parts`] for more.
+    unsafe fn data_slice<'a>(this: NonNull<c_void>) -> &'a [u8] {
+        let len = Self::len(this);
+        let data = Self::data_ptr(this);
+        slice::from_raw_parts(data, len)
+    }
+
+    /// Construct a mutable reference to the data slice within the given Nix string pointer
+    ///
+    /// # Safety
+    ///
+    /// This function must only be called with a pointer that has been properly initialized with
+    /// [`Self::alloc`], and where the data array has been properly initialized (by writing to the
+    /// pointer returned from [`Self::data_ptr`]).
+    ///
+    /// Also, all the normal Rust rules about pointer-to-reference conversion apply. See
+    /// [`slice::from_raw_parts_mut`] for more.
+    #[allow(dead_code)]
+    unsafe fn data_slice_mut<'a>(this: NonNull<c_void>) -> &'a mut [u8] {
+        let len = Self::len(this);
+        let data = Self::data_ptr(this);
+        slice::from_raw_parts_mut(data, len)
+    }
+
+    /// Clone the Nix string pointed to by this pointer, and return a pointer to a new Nix string
+    /// containing the same data and context.
+    ///
+    /// # Safety
+    ///
+    /// This function must only be called with a pointer that has been properly initialized with
+    /// [`Self::alloc`], and where the context has been properly initialized (by writing to the
+    /// pointer returned from [`Self::context_ptr`]), and the data array has been properly
+    /// initialized (by writing to the pointer returned from [`Self::data_ptr`]).
+    unsafe fn clone(this: NonNull<c_void>) -> NonNull<c_void> {
+        let (layout, _, _) = Self::layout_of(this);
+        let ptr = alloc(layout);
+        if let Some(new) = NonNull::new(ptr as *mut _) {
+            ptr::copy_nonoverlapping(this.as_ptr(), new.as_ptr(), layout.size());
+            Self::context_ptr(new).write(Self::context_ref(this).clone());
+            new
+        } else {
+            handle_alloc_error(layout);
+        }
+    }
+}
+
+/// Nix string values
+///
+/// # Internals
+///
+/// For performance reasons (to keep allocations small, and to avoid indirections), [`NixString`] is
+/// represented as a single *thin* pointer to a packed data structure containing the
+/// [context][NixContext] and the string data itself (which is a raw byte array, to match the Nix
+/// string semantics that allow any array of bytes to be represented by a string).
+
+/// This memory representation is documented in [`NixStringInner`], but since Rust prefers to deal
+/// with slices via *fat pointers* (pointers that include the length in the *pointer*, not in the
+/// heap allocation), we have to do mostly manual layout management and allocation for this
+/// representation. See the documentation for the methods of [`NixStringInner`] for more information
+pub struct NixString(NonNull<c_void>);
+
+unsafe impl Send for NixString {}
+unsafe impl Sync for NixString {}
+
+impl Drop for NixString {
+    fn drop(&mut self) {
+        // SAFETY: There's no way to construct a NixString that doesn't leave the allocation correct
+        // according to the rules of dealloc
+        unsafe {
+            NixStringInner::dealloc(self.0);
+        }
+    }
+}
+
+impl Clone for NixString {
+    fn clone(&self) -> Self {
+        // SAFETY: There's no way to construct a NixString that doesn't leave the allocation correct
+        // according to the rules of clone
+        unsafe { Self(NixStringInner::clone(self.0)) }
+    }
+}
+
+impl Debug for NixString {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        if let Some(ctx) = self.context() {
+            f.debug_struct("NixString")
+                .field("context", ctx)
+                .field("data", &self.as_bstr())
+                .finish()
+        } else {
+            write!(f, "{:?}", self.as_bstr())
+        }
+    }
+}
+
+impl PartialEq for NixString {
+    fn eq(&self, other: &Self) -> bool {
+        self.as_bstr() == other.as_bstr()
+    }
+}
+
+impl Eq for NixString {}
+
+impl PartialEq<&[u8]> for NixString {
+    fn eq(&self, other: &&[u8]) -> bool {
+        **self == **other
+    }
+}
+
+impl PartialEq<&str> for NixString {
+    fn eq(&self, other: &&str) -> bool {
+        **self == other.as_bytes()
+    }
+}
+
+impl PartialOrd for NixString {
+    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+        Some(self.cmp(other))
+    }
+}
+
+impl Ord for NixString {
+    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+        self.as_bstr().cmp(other.as_bstr())
+    }
+}
+
+impl From<Box<BStr>> for NixString {
+    fn from(value: Box<BStr>) -> Self {
+        Self::new(&value, None)
+    }
+}
+
+impl From<BString> for NixString {
+    fn from(value: BString) -> Self {
+        Self::new(&value, None)
+    }
+}
+
+impl From<&BStr> for NixString {
+    fn from(value: &BStr) -> Self {
+        value.to_owned().into()
+    }
+}
+
+impl From<&[u8]> for NixString {
+    fn from(value: &[u8]) -> Self {
+        Self::from(value.to_owned())
+    }
+}
+
+impl From<Vec<u8>> for NixString {
+    fn from(value: Vec<u8>) -> Self {
+        value.into_boxed_slice().into()
+    }
+}
+
+impl From<Box<[u8]>> for NixString {
+    fn from(value: Box<[u8]>) -> Self {
+        Self::new(&value, None)
+    }
+}
+
+impl From<&str> for NixString {
+    fn from(s: &str) -> Self {
+        s.as_bytes().into()
+    }
+}
+
+impl From<String> for NixString {
+    fn from(s: String) -> Self {
+        s.into_bytes().into()
+    }
+}
+
+impl<T> From<(T, Option<Box<NixContext>>)> for NixString
+where
+    NixString: From<T>,
+{
+    fn from((s, ctx): (T, Option<Box<NixContext>>)) -> Self {
+        Self::new(NixString::from(s).as_ref(), ctx)
+    }
+}
+
+impl From<Box<str>> for NixString {
+    fn from(s: Box<str>) -> Self {
+        s.into_boxed_bytes().into()
+    }
+}
+
+impl From<ast::Ident> for NixString {
+    fn from(ident: ast::Ident) -> Self {
+        ident.ident_token().unwrap().text().into()
+    }
+}
+
+impl<'a> From<&'a NixString> for &'a BStr {
+    fn from(s: &'a NixString) -> Self {
+        s.as_bstr()
+    }
+}
+
+// No impl From<NixString> for String, that one quotes.
+
+impl From<NixString> for BString {
+    fn from(s: NixString) -> Self {
+        s.as_bstr().to_owned()
+    }
+}
+
+impl AsRef<[u8]> for NixString {
+    fn as_ref(&self) -> &[u8] {
+        self.as_bytes()
+    }
+}
+
+impl Borrow<BStr> for NixString {
+    fn borrow(&self) -> &BStr {
+        self.as_bstr()
+    }
+}
+
+impl Hash for NixString {
+    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
+        self.as_bstr().hash(state)
+    }
+}
+
+impl<'de> Deserialize<'de> for NixString {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        struct StringVisitor;
+
+        impl<'de> Visitor<'de> for StringVisitor {
+            type Value = NixString;
+
+            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
+                formatter.write_str("a valid Nix string")
+            }
+
+            fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
+            where
+                E: serde::de::Error,
+            {
+                Ok(v.into())
+            }
+
+            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
+            where
+                E: serde::de::Error,
+            {
+                Ok(v.into())
+            }
+        }
+
+        deserializer.deserialize_string(StringVisitor)
+    }
+}
+
+impl Deref for NixString {
+    type Target = BStr;
+
+    fn deref(&self) -> &Self::Target {
+        self.as_bstr()
+    }
+}
+
+#[cfg(feature = "arbitrary")]
+mod arbitrary {
+    use super::*;
+    use proptest::prelude::{any_with, Arbitrary};
+    use proptest::strategy::{BoxedStrategy, Strategy};
+
+    impl Arbitrary for NixString {
+        type Parameters = <String as Arbitrary>::Parameters;
+
+        type Strategy = BoxedStrategy<Self>;
+
+        fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
+            any_with::<String>(args).prop_map(Self::from).boxed()
+        }
+    }
+}
+
+impl NixString {
+    fn new(contents: &[u8], context: Option<Box<NixContext>>) -> Self {
+        // SAFETY: We're always fully initializing a NixString here:
+        //
+        // 1. NixStringInner::alloc sets up the len for us
+        // 2. We set the context, using ptr::write to make sure that the uninitialized memory isn't
+        //    read or dropped
+        // 3. We set the data, using copy_from_nonoverlapping to make sure that the uninitialized
+        //    memory isn't read or dropped
+        //
+        // Only *then* can we construct a NixString
+        unsafe {
+            let inner = NixStringInner::alloc(contents.len());
+            NixStringInner::context_ptr(inner).write(context);
+            NixStringInner::data_ptr(inner)
+                .copy_from_nonoverlapping(contents.as_ptr(), contents.len());
+            Self(inner)
+        }
+    }
+
+    pub fn new_inherit_context_from<T>(other: &NixString, new_contents: T) -> Self
+    where
+        NixString: From<T>,
+    {
+        Self::new(
+            Self::from(new_contents).as_ref(),
+            other.context().map(|c| Box::new(c.clone())),
+        )
+    }
+
+    pub fn new_context_from<T>(context: NixContext, contents: T) -> Self
+    where
+        NixString: From<T>,
+    {
+        Self::new(
+            Self::from(contents).as_ref(),
+            if context.is_empty() {
+                None
+            } else {
+                Some(Box::new(context))
+            },
+        )
+    }
+
+    pub fn as_bstr(&self) -> &BStr {
+        BStr::new(self.as_bytes())
+    }
+
+    pub fn as_bytes(&self) -> &[u8] {
+        // SAFETY: There's no way to construct an uninitialized NixString (see the SAFETY comment in
+        // `new`)
+        unsafe { NixStringInner::data_slice(self.0) }
+    }
+
+    pub fn into_bstring(self) -> BString {
+        self.as_bstr().to_owned()
+    }
+
+    /// Return a displayable representation of the string as an
+    /// identifier.
+    ///
+    /// This is used when printing out strings used as e.g. attribute
+    /// set keys, as those are only escaped in the presence of special
+    /// characters.
+    pub fn ident_str(&self) -> Cow<str> {
+        let escaped = match self.to_str_lossy() {
+            Cow::Borrowed(s) => nix_escape_string(s),
+            Cow::Owned(s) => nix_escape_string(&s).into_owned().into(),
+        };
+        match escaped {
+            // A borrowed string is unchanged and can be returned as
+            // is.
+            Cow::Borrowed(_) => {
+                if is_valid_nix_identifier(&escaped) && !is_keyword(&escaped) {
+                    escaped
+                } else {
+                    Cow::Owned(format!("\"{}\"", escaped))
+                }
+            }
+
+            // An owned string has escapes, and needs the outer quotes
+            // for display.
+            Cow::Owned(s) => Cow::Owned(format!("\"{}\"", s)),
+        }
+    }
+
+    pub fn concat(&self, other: &Self) -> Self {
+        let mut s = self.to_vec();
+        s.extend(&(***other));
+
+        let context = [self.context(), other.context()]
+            .into_iter()
+            .flatten()
+            .fold(NixContext::new(), |acc_ctx, new_ctx| {
+                acc_ctx.join(&mut new_ctx.clone())
+            });
+        Self::new_context_from(context, s)
+    }
+
+    pub(crate) fn context(&self) -> Option<&NixContext> {
+        // SAFETY: There's no way to construct an uninitialized or invalid NixString (see the SAFETY
+        // comment in `new`).
+        //
+        // Also, we're using the same lifetime and mutability as self, to fit the
+        // pointer-to-reference conversion rules
+        unsafe { NixStringInner::context_ref(self.0).as_deref() }
+    }
+
+    pub(crate) fn context_mut(&mut self) -> Option<&mut NixContext> {
+        // SAFETY: There's no way to construct an uninitialized or invalid NixString (see the SAFETY
+        // comment in `new`).
+        //
+        // Also, we're using the same lifetime and mutability as self, to fit the
+        // pointer-to-reference conversion rules
+        unsafe { NixStringInner::context_mut(self.0).as_deref_mut() }
+    }
+
+    pub fn iter_context(&self) -> impl Iterator<Item = &NixContext> {
+        self.context().into_iter()
+    }
+
+    pub fn iter_plain(&self) -> impl Iterator<Item = &str> {
+        self.iter_context().flat_map(|context| context.iter_plain())
+    }
+
+    pub fn iter_derivation(&self) -> impl Iterator<Item = &str> {
+        return self
+            .iter_context()
+            .flat_map(|context| context.iter_derivation());
+    }
+
+    pub fn iter_single_outputs(&self) -> impl Iterator<Item = (&str, &str)> {
+        return self
+            .iter_context()
+            .flat_map(|context| context.iter_single_outputs());
+    }
+
+    /// Returns whether this Nix string possess a context or not.
+    pub fn has_context(&self) -> bool {
+        self.context().is_some()
+    }
+
+    /// This clears the context of that string, losing
+    /// all dependency tracking information.
+    pub fn clear_context(&mut self) {
+        // SAFETY: There's no way to construct an uninitialized or invalid NixString (see the SAFETY
+        // comment in `new`).
+        *unsafe { NixStringInner::context_mut(self.0) } = None;
+    }
+
+    pub fn chars(&self) -> Chars<'_> {
+        self.as_bstr().chars()
+    }
+}
+
+fn nix_escape_char(ch: char, next: Option<&char>) -> Option<&'static str> {
+    match (ch, next) {
+        ('\\', _) => Some("\\\\"),
+        ('"', _) => Some("\\\""),
+        ('\n', _) => Some("\\n"),
+        ('\t', _) => Some("\\t"),
+        ('\r', _) => Some("\\r"),
+        ('$', Some('{')) => Some("\\$"),
+        _ => None,
+    }
+}
+
+/// Return true if this string is a keyword -- character strings
+/// which lexically match the "identifier" production but are not
+/// parsed as identifiers.  See also cppnix commit
+/// b72bc4a972fe568744d98b89d63adcd504cb586c.
+fn is_keyword(s: &str) -> bool {
+    matches!(
+        s,
+        "if" | "then" | "else" | "assert" | "with" | "let" | "in" | "rec" | "inherit"
+    )
+}
+
+/// Return true if this string can be used as an identifier in Nix.
+fn is_valid_nix_identifier(s: &str) -> bool {
+    // adapted from rnix-parser's tokenizer.rs
+    let mut chars = s.chars();
+    match chars.next() {
+        Some('a'..='z' | 'A'..='Z' | '_') => (),
+        _ => return false,
+    }
+    for c in chars {
+        match c {
+            'a'..='z' | 'A'..='Z' | '0'..='9' | '_' | '-' | '\'' => (),
+            _ => return false,
+        }
+    }
+    true
+}
+
+/// Escape a Nix string for display, as most user-visible representation
+/// are escaped strings.
+///
+/// Note that this does not add the outer pair of surrounding quotes.
+fn nix_escape_string(input: &str) -> Cow<str> {
+    let mut iter = input.char_indices().peekable();
+
+    while let Some((i, c)) = iter.next() {
+        if let Some(esc) = nix_escape_char(c, iter.peek().map(|(_, c)| c)) {
+            let mut escaped = String::with_capacity(input.len());
+            escaped.push_str(&input[..i]);
+            escaped.push_str(esc);
+
+            // In theory we calculate how many bytes it takes to represent `esc`
+            // in UTF-8 and use that for the offset. It is, however, safe to
+            // assume that to be 1, as all characters that can be escaped in a
+            // Nix string are ASCII.
+            let mut inner_iter = input[i + 1..].chars().peekable();
+            while let Some(c) = inner_iter.next() {
+                match nix_escape_char(c, inner_iter.peek()) {
+                    Some(esc) => escaped.push_str(esc),
+                    None => escaped.push(c),
+                }
+            }
+
+            return Cow::Owned(escaped);
+        }
+    }
+
+    Cow::Borrowed(input)
+}
 
 impl Display for NixString {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        f.write_str(self.0.as_str())
+        f.write_str("\"")?;
+        f.write_str(&nix_escape_string(&self.to_str_lossy()))?;
+        f.write_str("\"")
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use test_strategy::proptest;
+
+    use super::*;
+
+    use crate::properties::{eq_laws, hash_laws, ord_laws};
+
+    #[test]
+    fn size() {
+        assert_eq!(std::mem::size_of::<NixString>(), 8);
+    }
+
+    #[proptest]
+    fn clone_strings(s: NixString) {
+        drop(s.clone())
+    }
+
+    eq_laws!(NixString);
+    hash_laws!(NixString);
+    ord_laws!(NixString);
+}
diff --git a/tvix/eval/src/value/thunk.rs b/tvix/eval/src/value/thunk.rs
new file mode 100644
index 0000000000..a67537f945
--- /dev/null
+++ b/tvix/eval/src/value/thunk.rs
@@ -0,0 +1,434 @@
+//! This module implements the runtime representation of Thunks.
+//!
+//! Thunks are a special kind of Nix value, similar to a 0-argument
+//! closure that yields some value. Thunks are used to implement the
+//! lazy evaluation behaviour of Nix:
+//!
+//! Whenever the compiler determines that an expression should be
+//! evaluated lazily, it creates a thunk instead of compiling the
+//! expression value directly. At any point in the runtime where the
+//! actual value of a thunk is required, it is "forced", meaning that
+//! the encompassing computation takes place and the thunk takes on
+//! its new value.
+//!
+//! Thunks have interior mutability to be able to memoise their
+//! computation. Once a thunk is evaluated, its internal
+//! representation becomes the result of the expression. It is legal
+//! for the runtime to replace a thunk object directly with its value
+//! object, but when forcing a thunk, the runtime *must* mutate the
+//! memoisable slot.
+
+use std::{
+    cell::{Ref, RefCell, RefMut},
+    collections::HashSet,
+    fmt::Debug,
+    rc::Rc,
+};
+
+use crate::{
+    errors::ErrorKind,
+    opcode::OpCode,
+    spans::LightSpan,
+    upvalues::Upvalues,
+    value::Closure,
+    vm::generators::{self, GenCo},
+    Value,
+};
+
+use super::{Lambda, TotalDisplay};
+use codemap::Span;
+
+/// Internal representation of a suspended native thunk.
+struct SuspendedNative(Box<dyn Fn() -> Result<Value, ErrorKind>>);
+
+impl Debug for SuspendedNative {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "SuspendedNative({:p})", self.0)
+    }
+}
+
+/// Internal representation of the different states of a thunk.
+///
+/// Upvalues must be finalised before leaving the initial state
+/// (Suspended or RecursiveClosure).  The [`value()`] function may
+/// not be called until the thunk is in the final state (Evaluated).
+#[derive(Debug)]
+enum ThunkRepr {
+    /// Thunk is closed over some values, suspended and awaiting
+    /// execution.
+    Suspended {
+        lambda: Rc<Lambda>,
+        upvalues: Rc<Upvalues>,
+        light_span: LightSpan,
+    },
+
+    /// Thunk is a suspended native computation.
+    Native(SuspendedNative),
+
+    /// Thunk currently under-evaluation; encountering a blackhole
+    /// value means that infinite recursion has occured.
+    Blackhole {
+        /// Span at which the thunk was first forced.
+        forced_at: LightSpan,
+
+        /// Span at which the thunk was originally suspended.
+        suspended_at: Option<LightSpan>,
+
+        /// Span of the first instruction of the actual code inside
+        /// the thunk.
+        content_span: Option<Span>,
+    },
+
+    // TODO(amjoseph): consider changing `Value` to `Rc<Value>` to avoid
+    // expensive clone()s in Thunk::force().
+    /// Fully evaluated thunk.
+    Evaluated(Value),
+}
+
+impl ThunkRepr {
+    fn debug_repr(&self) -> String {
+        match self {
+            ThunkRepr::Evaluated(v) => format!("thunk(val|{})", v),
+            ThunkRepr::Blackhole { .. } => "thunk(blackhole)".to_string(),
+            ThunkRepr::Native(_) => "thunk(native)".to_string(),
+            ThunkRepr::Suspended { lambda, .. } => format!("thunk({:p})", *lambda),
+        }
+    }
+
+    /// Return the Value within a fully-evaluated ThunkRepr; panics
+    /// if the thunk is not fully-evaluated.
+    fn expect(self) -> Value {
+        match self {
+            ThunkRepr::Evaluated(value) => value,
+            ThunkRepr::Blackhole { .. } => panic!("Thunk::expect() called on a black-holed thunk"),
+            ThunkRepr::Suspended { .. } | ThunkRepr::Native(_) => {
+                panic!("Thunk::expect() called on a suspended thunk")
+            }
+        }
+    }
+
+    fn expect_ref(&self) -> &Value {
+        match self {
+            ThunkRepr::Evaluated(value) => value,
+            ThunkRepr::Blackhole { .. } => panic!("Thunk::expect() called on a black-holed thunk"),
+            ThunkRepr::Suspended { .. } | ThunkRepr::Native(_) => {
+                panic!("Thunk::expect() called on a suspended thunk")
+            }
+        }
+    }
+
+    pub fn is_forced(&self) -> bool {
+        match self {
+            ThunkRepr::Evaluated(Value::Thunk(_)) => false,
+            ThunkRepr::Evaluated(_) => true,
+            _ => false,
+        }
+    }
+}
+
+/// A thunk is created for any value which requires non-strict
+/// evaluation due to self-reference or lazy semantics (or both).
+/// Every reference cycle involving `Value`s will contain at least
+/// one `Thunk`.
+#[derive(Clone, Debug)]
+pub struct Thunk(Rc<RefCell<ThunkRepr>>);
+
+impl Thunk {
+    pub fn new_closure(lambda: Rc<Lambda>) -> Self {
+        Thunk(Rc::new(RefCell::new(ThunkRepr::Evaluated(Value::Closure(
+            Rc::new(Closure {
+                upvalues: Rc::new(Upvalues::with_capacity(lambda.upvalue_count)),
+                lambda: lambda.clone(),
+            }),
+        )))))
+    }
+
+    pub fn new_suspended(lambda: Rc<Lambda>, light_span: LightSpan) -> Self {
+        Thunk(Rc::new(RefCell::new(ThunkRepr::Suspended {
+            upvalues: Rc::new(Upvalues::with_capacity(lambda.upvalue_count)),
+            lambda: lambda.clone(),
+            light_span,
+        })))
+    }
+
+    pub fn new_suspended_native(native: Box<dyn Fn() -> Result<Value, ErrorKind>>) -> Self {
+        Thunk(Rc::new(RefCell::new(ThunkRepr::Native(SuspendedNative(
+            native,
+        )))))
+    }
+
+    /// Helper function to create a [`Thunk`] that calls a function given as the
+    /// [`Value`] `callee` with the argument `arg` when it is forced. This is
+    /// particularly useful in builtin implementations if the result of calling
+    /// a function does not need to be forced immediately, because e.g. it is
+    /// stored in an attribute set.
+    pub fn new_suspended_call(callee: Value, arg: Value, light_span: LightSpan) -> Self {
+        let mut lambda = Lambda::default();
+        let span = light_span.span();
+
+        let arg_idx = lambda.chunk().push_constant(arg);
+        let f_idx = lambda.chunk().push_constant(callee);
+
+        // This is basically a recreation of compile_apply():
+        // We need to push the argument onto the stack and then the function.
+        // The function (not the argument) needs to be forced before calling.
+        lambda.chunk.push_op(OpCode::OpConstant(arg_idx), span);
+        lambda.chunk().push_op(OpCode::OpConstant(f_idx), span);
+        lambda.chunk.push_op(OpCode::OpForce, span);
+        lambda.chunk.push_op(OpCode::OpCall, span);
+
+        // Inform the VM that the chunk has ended
+        lambda.chunk.push_op(OpCode::OpReturn, span);
+
+        Thunk(Rc::new(RefCell::new(ThunkRepr::Suspended {
+            upvalues: Rc::new(Upvalues::with_capacity(0)),
+            lambda: Rc::new(lambda),
+            light_span,
+        })))
+    }
+
+    fn prepare_blackhole(&self, forced_at: LightSpan) -> ThunkRepr {
+        match &*self.0.borrow() {
+            ThunkRepr::Suspended {
+                light_span, lambda, ..
+            } => ThunkRepr::Blackhole {
+                forced_at,
+                suspended_at: Some(light_span.clone()),
+                content_span: Some(lambda.chunk.first_span()),
+            },
+
+            _ => ThunkRepr::Blackhole {
+                forced_at,
+                suspended_at: None,
+                content_span: None,
+            },
+        }
+    }
+
+    pub async fn force(myself: Thunk, co: GenCo, span: LightSpan) -> Result<Value, ErrorKind> {
+        Self::force_(myself, &co, span).await
+    }
+    pub async fn force_(
+        mut myself: Thunk,
+        co: &GenCo,
+        span: LightSpan,
+    ) -> Result<Value, ErrorKind> {
+        // This vector of "thunks which point to the thunk-being-forced", to
+        // be updated along with it, is necessary in order to write this
+        // function in iterative (and later, mutual-tail-call) form.
+        let mut also_update: Vec<Rc<RefCell<ThunkRepr>>> = vec![];
+
+        loop {
+            // If the current thunk is already fully evaluated, return its evaluated
+            // value. The VM will continue running the code that landed us here.
+            if myself.is_forced() {
+                let val = myself.unwrap_or_clone();
+                for other_thunk in also_update.into_iter() {
+                    other_thunk.replace(ThunkRepr::Evaluated(val.clone()));
+                }
+                return Ok(val);
+            }
+
+            // Begin evaluation of this thunk by marking it as a blackhole, meaning
+            // that any other forcing frame encountering this thunk before its
+            // evaluation is completed detected an evaluation cycle.
+            let inner = myself.0.replace(myself.prepare_blackhole(span.clone()));
+
+            match inner {
+                // If there was already a blackhole in the thunk, this is an
+                // evaluation cycle.
+                ThunkRepr::Blackhole {
+                    forced_at,
+                    suspended_at,
+                    content_span,
+                } => {
+                    return Err(ErrorKind::InfiniteRecursion {
+                        first_force: forced_at.span(),
+                        suspended_at: suspended_at.map(|s| s.span()),
+                        content_span,
+                    })
+                }
+
+                // If there is a native function stored in the thunk, evaluate it
+                // and replace this thunk's representation with the result.
+                ThunkRepr::Native(native) => {
+                    let value = native.0()?;
+                    myself.0.replace(ThunkRepr::Evaluated(value));
+                    continue;
+                }
+
+                // When encountering a suspended thunk, request that the VM enters
+                // it and produces the result.
+                ThunkRepr::Suspended {
+                    lambda,
+                    upvalues,
+                    light_span,
+                } => {
+                    // TODO(amjoseph): use #[tailcall::mutual] here.  This can
+                    // be turned into a tailcall to vm::execute_bytecode() by
+                    // passing `also_update` to it.
+                    let value =
+                        generators::request_enter_lambda(co, lambda, upvalues, light_span).await;
+                    myself.0.replace(ThunkRepr::Evaluated(value));
+                    continue;
+                }
+
+                // nested thunks -- try to flatten before forcing
+                ThunkRepr::Evaluated(Value::Thunk(inner_thunk)) => {
+                    match Rc::try_unwrap(inner_thunk.0) {
+                        Ok(refcell) => {
+                            // we are the only reference to the inner thunk,
+                            // so steal it
+                            myself.0.replace(refcell.into_inner());
+                            continue;
+                        }
+                        Err(rc) => {
+                            let inner_thunk = Thunk(rc);
+                            if inner_thunk.is_forced() {
+                                // tail call to force the inner thunk; note that
+                                // this means the outer thunk remains unforced
+                                // even after calling force() on it; however the
+                                // next time it is forced we will be one
+                                // thunk-forcing closer to it being
+                                // fully-evaluated.
+                                myself
+                                    .0
+                                    .replace(ThunkRepr::Evaluated(inner_thunk.value().clone()));
+                                continue;
+                            }
+                            also_update.push(myself.0.clone());
+                            myself = inner_thunk;
+                            continue;
+                        }
+                    }
+                }
+
+                ThunkRepr::Evaluated(val) => {
+                    return Ok(val);
+                }
+            }
+        }
+    }
+
+    pub fn finalise(&self, stack: &[Value]) {
+        self.upvalues_mut().resolve_deferred_upvalues(stack);
+    }
+
+    pub fn is_evaluated(&self) -> bool {
+        matches!(*self.0.borrow(), ThunkRepr::Evaluated(_))
+    }
+
+    pub fn is_suspended(&self) -> bool {
+        matches!(
+            *self.0.borrow(),
+            ThunkRepr::Suspended { .. } | ThunkRepr::Native(_)
+        )
+    }
+
+    /// Returns true if forcing this thunk will not change it.
+    pub fn is_forced(&self) -> bool {
+        self.0.borrow().is_forced()
+    }
+
+    /// Returns a reference to the inner evaluated value of a thunk.
+    /// It is an error to call this on a thunk that has not been
+    /// forced, or is not otherwise known to be fully evaluated.
+    // Note: Due to the interior mutability of thunks this is
+    // difficult to represent in the type system without impacting the
+    // API too much.
+    pub fn value(&self) -> Ref<Value> {
+        Ref::map(self.0.borrow(), |thunk| match thunk {
+            ThunkRepr::Evaluated(value) => value,
+            ThunkRepr::Blackhole { .. } => panic!("Thunk::value called on a black-holed thunk"),
+            ThunkRepr::Suspended { .. } | ThunkRepr::Native(_) => {
+                panic!("Thunk::value called on a suspended thunk")
+            }
+        })
+    }
+
+    /// Returns the inner evaluated value of a thunk, cloning it if
+    /// the Rc has more than one strong reference.  It is an error
+    /// to call this on a thunk that has not been forced, or is not
+    /// otherwise known to be fully evaluated.
+    fn unwrap_or_clone(self) -> Value {
+        match Rc::try_unwrap(self.0) {
+            Ok(refcell) => refcell.into_inner().expect(),
+            Err(rc) => Ref::map(rc.borrow(), |thunkrepr| thunkrepr.expect_ref()).clone(),
+        }
+    }
+
+    pub fn upvalues(&self) -> Ref<'_, Upvalues> {
+        Ref::map(self.0.borrow(), |thunk| match thunk {
+            ThunkRepr::Suspended { upvalues, .. } => upvalues.as_ref(),
+            ThunkRepr::Evaluated(Value::Closure(c)) => &c.upvalues,
+            _ => panic!("upvalues() on non-suspended thunk"),
+        })
+    }
+
+    pub fn upvalues_mut(&self) -> RefMut<'_, Upvalues> {
+        RefMut::map(self.0.borrow_mut(), |thunk| match thunk {
+            ThunkRepr::Suspended { upvalues, .. } => Rc::get_mut(upvalues).unwrap(),
+            ThunkRepr::Evaluated(Value::Closure(c)) => Rc::get_mut(
+                &mut Rc::get_mut(c).unwrap().upvalues,
+            )
+            .expect(
+                "upvalues_mut() was called on a thunk which already had multiple references to it",
+            ),
+            thunk => panic!("upvalues() on non-suspended thunk: {thunk:?}"),
+        })
+    }
+
+    /// Do not use this without first reading and understanding
+    /// `tvix/docs/value-pointer-equality.md`.
+    pub(crate) fn ptr_eq(&self, other: &Self) -> bool {
+        if Rc::ptr_eq(&self.0, &other.0) {
+            return true;
+        }
+        match &*self.0.borrow() {
+            ThunkRepr::Evaluated(Value::Closure(c1)) => match &*other.0.borrow() {
+                ThunkRepr::Evaluated(Value::Closure(c2)) => Rc::ptr_eq(c1, c2),
+                _ => false,
+            },
+            _ => false,
+        }
+    }
+
+    /// Helper function to format thunks in observer output.
+    pub(crate) fn debug_repr(&self) -> String {
+        self.0.borrow().debug_repr()
+    }
+}
+
+impl TotalDisplay for Thunk {
+    fn total_fmt(&self, f: &mut std::fmt::Formatter<'_>, set: &mut ThunkSet) -> std::fmt::Result {
+        if !set.insert(self) {
+            return f.write_str("<CYCLE>");
+        }
+
+        match &*self.0.borrow() {
+            ThunkRepr::Evaluated(v) => v.total_fmt(f, set),
+            ThunkRepr::Suspended { .. } | ThunkRepr::Native(_) => f.write_str("<CODE>"),
+            other => write!(f, "internal[{}]", other.debug_repr()),
+        }
+    }
+}
+
+/// A wrapper type for tracking which thunks have already been seen
+/// in a context. This is necessary for printing and deeply forcing
+/// cyclic non-diverging data structures like `rec { f = [ f ]; }`.
+/// This is separate from the ThunkRepr::Blackhole mechanism, which
+/// detects diverging data structures like `(rec { f = f; }).f`.
+///
+/// The inner `HashSet` is not available on the outside, as it would be
+/// potentially unsafe to interact with the pointers in the set.
+#[derive(Default)]
+pub struct ThunkSet(HashSet<*const ThunkRepr>);
+
+impl ThunkSet {
+    /// Check whether the given thunk has already been seen. Will mark the thunk
+    /// as seen otherwise.
+    pub fn insert(&mut self, thunk: &Thunk) -> bool {
+        let ptr: *const ThunkRepr = thunk.0.as_ptr();
+        self.0.insert(ptr)
+    }
+}
diff --git a/tvix/eval/src/vm.rs b/tvix/eval/src/vm.rs
deleted file mode 100644
index 90f5f45f61..0000000000
--- a/tvix/eval/src/vm.rs
+++ /dev/null
@@ -1,385 +0,0 @@
-//! This module implements the virtual (or abstract) machine that runs
-//! Tvix bytecode.
-
-use std::{collections::BTreeMap, rc::Rc};
-
-use crate::{
-    chunk::Chunk,
-    errors::{Error, EvalResult},
-    opcode::OpCode,
-    value::{NixAttrs, NixList, NixString, Value},
-};
-
-pub struct VM {
-    ip: usize,
-    chunk: Chunk,
-    stack: Vec<Value>,
-}
-
-impl VM {
-    fn inc_ip(&mut self) -> OpCode {
-        let op = self.chunk.code[self.ip];
-        self.ip += 1;
-        op
-    }
-
-    fn peek(&self, at: usize) -> &Value {
-        &self.stack[self.stack.len() - 1 - at]
-    }
-
-    fn pop(&mut self) -> Value {
-        self.stack.pop().expect("TODO")
-    }
-
-    fn pop_number_pair(&mut self) -> EvalResult<NumberPair> {
-        let v2 = self.pop();
-        let v1 = self.pop();
-
-        match (v1, v2) {
-            (Value::Integer(i1), Value::Integer(i2)) => Ok(NumberPair::Integer(i1, i2)),
-
-            (Value::Float(f1), Value::Float(f2)) => Ok(NumberPair::Floats(f1, f2)),
-
-            (Value::Integer(i1), Value::Float(f2)) => Ok(NumberPair::Floats(i1 as f64, f2)),
-
-            (Value::Float(f1), Value::Integer(i2)) => Ok(NumberPair::Floats(f1, i2 as f64)),
-
-            (v1, v2) => Err(Error::TypeError {
-                expected: "number (either int or float)",
-                actual: if v1.is_number() {
-                    v2.type_of()
-                } else {
-                    v1.type_of()
-                },
-            }),
-        }
-    }
-
-    fn push(&mut self, value: Value) {
-        self.stack.push(value)
-    }
-
-    fn run(&mut self) -> EvalResult<Value> {
-        loop {
-            match self.inc_ip() {
-                OpCode::OpConstant(idx) => {
-                    let c = self.chunk.constant(idx).clone();
-                    self.push(c);
-                }
-
-                OpCode::OpAdd => match self.pop_number_pair()? {
-                    NumberPair::Floats(f1, f2) => self.push(Value::Float(f1 + f2)),
-                    NumberPair::Integer(i1, i2) => self.push(Value::Integer(i1 + i2)),
-                },
-
-                OpCode::OpSub => match self.pop_number_pair()? {
-                    NumberPair::Floats(f1, f2) => self.push(Value::Float(f1 - f2)),
-                    NumberPair::Integer(i1, i2) => self.push(Value::Integer(i1 - i2)),
-                },
-
-                OpCode::OpMul => match self.pop_number_pair()? {
-                    NumberPair::Floats(f1, f2) => self.push(Value::Float(f1 * f2)),
-                    NumberPair::Integer(i1, i2) => self.push(Value::Integer(i1 * i2)),
-                },
-
-                OpCode::OpDiv => match self.pop_number_pair()? {
-                    NumberPair::Floats(f1, f2) => self.push(Value::Float(f1 / f2)),
-                    NumberPair::Integer(i1, i2) => self.push(Value::Integer(i1 / i2)),
-                },
-
-                OpCode::OpInvert => {
-                    let v = self.pop().as_bool()?;
-                    self.push(Value::Bool(!v));
-                }
-
-                OpCode::OpNegate => match self.pop() {
-                    Value::Integer(i) => self.push(Value::Integer(-i)),
-                    Value::Float(f) => self.push(Value::Float(-f)),
-                    v => {
-                        return Err(Error::TypeError {
-                            expected: "number (either int or float)",
-                            actual: v.type_of(),
-                        })
-                    }
-                },
-
-                OpCode::OpEqual => {
-                    let v2 = self.pop();
-                    let v1 = self.pop();
-
-                    let eq = match (v1, v2) {
-                        (Value::Float(f), Value::Integer(i))
-                        | (Value::Integer(i), Value::Float(f)) => f == (i as f64),
-
-                        (v1, v2) => v1 == v2,
-                    };
-
-                    self.push(Value::Bool(eq))
-                }
-
-                OpCode::OpNull => self.push(Value::Null),
-                OpCode::OpTrue => self.push(Value::Bool(true)),
-                OpCode::OpFalse => self.push(Value::Bool(false)),
-                OpCode::OpAttrs(count) => self.run_attrset(count)?,
-                OpCode::OpAttrPath(count) => self.run_attr_path(count)?,
-                OpCode::OpList(count) => self.run_list(count)?,
-                OpCode::OpInterpolate(count) => self.run_interpolate(count)?,
-            }
-
-            if self.ip == self.chunk.code.len() {
-                return Ok(self.pop());
-            }
-        }
-    }
-
-    // Construct runtime representation of an attr path (essentially
-    // just a list of strings).
-    //
-    // The difference to the list construction operation is that this
-    // forces all elements into strings, as attribute set keys are
-    // required to be strict in Nix.
-    fn run_attr_path(&mut self, count: usize) -> EvalResult<()> {
-        debug_assert!(count > 1, "AttrPath needs at least two fragments");
-        let mut path = Vec::with_capacity(count);
-
-        for _ in 0..count {
-            path.push(self.pop().as_string()?);
-        }
-
-        self.push(Value::AttrPath(path));
-        Ok(())
-    }
-
-    fn run_attrset(&mut self, count: usize) -> EvalResult<()> {
-        // If the attribute count happens to be 2, we might be able to
-        // create the optimised name/value struct instead.
-        if count == 2 {
-            // When determining whether we are dealing with a
-            // name/value pair, we return the stack locations of name
-            // and value, using `0` as a sentinel value (i.e. if
-            // either is 0, we are dealing with some other attrset).
-            let is_pair = {
-                // The keys are located 1 & 3 values back in the
-                // stack.
-                let k1 = self.peek(1);
-                let k2 = self.peek(3);
-
-                match (k1, k2) {
-                    (Value::String(NixString(s1)), Value::String(NixString(s2)))
-                        if (s1 == "name" && s2 == "value") =>
-                    {
-                        (1, 2)
-                    }
-
-                    (Value::String(NixString(s1)), Value::String(NixString(s2)))
-                        if (s1 == "value" && s2 == "name") =>
-                    {
-                        (2, 1)
-                    }
-
-                    // Technically this branch lets type errors pass,
-                    // but they will be caught during normal attribute
-                    // set construction instead.
-                    _ => (0, 0),
-                }
-            };
-
-            match is_pair {
-                (1, 2) => {
-                    // The value of 'name' is at stack slot 0, the
-                    // value of 'value' is at stack slot 2.
-                    let pair = Value::Attrs(Rc::new(NixAttrs::KV {
-                        name: self.pop(),
-                        value: {
-                            self.pop(); // ignore the key
-                            self.pop()
-                        },
-                    }));
-
-                    // Clean up the last key fragment.
-                    self.pop();
-
-                    self.push(pair);
-                    return Ok(());
-                }
-
-                (2, 1) => {
-                    // The value of 'name' is at stack slot 2, the
-                    // value of 'value' is at stack slot 0.
-                    let pair = Value::Attrs(Rc::new(NixAttrs::KV {
-                        value: self.pop(),
-                        name: {
-                            self.pop(); // ignore the key
-                            self.pop()
-                        },
-                    }));
-
-                    // Clean up the last key fragment.
-                    self.pop();
-
-                    self.push(pair);
-                    return Ok(());
-                }
-                _ => {}
-            }
-        }
-
-        let mut attrs: BTreeMap<NixString, Value> = BTreeMap::new();
-
-        for _ in 0..count {
-            let value = self.pop();
-
-            // It is at this point that nested attribute sets need to
-            // be constructed (if they exist).
-            //
-            let key = self.pop();
-            match key {
-                Value::String(ks) => set_attr(&mut attrs, ks, value)?,
-
-                Value::AttrPath(mut path) => {
-                    set_nested_attr(
-                        &mut attrs,
-                        path.pop().expect("AttrPath is never empty"),
-                        path,
-                        value,
-                    )?;
-                }
-
-                other => {
-                    return Err(Error::InvalidKeyType {
-                        given: other.type_of(),
-                    })
-                }
-            }
-        }
-
-        // TODO(tazjin): extend_reserve(count) (rust#72631)
-        self.push(Value::Attrs(Rc::new(NixAttrs::Map(attrs))));
-        Ok(())
-    }
-
-    // Interpolate string fragments by popping the specified number of
-    // fragments of the stack, evaluating them to strings, and pushing
-    // the concatenated result string back on the stack.
-    fn run_interpolate(&mut self, count: usize) -> EvalResult<()> {
-        let mut out = String::new();
-
-        for _ in 0..count {
-            out.push_str(&self.pop().as_string()?.0);
-        }
-
-        self.push(Value::String(NixString(out)));
-        Ok(())
-    }
-
-    // Construct runtime representation of a list. Because the list
-    // items are on the stack in reverse order, the vector is created
-    // initialised and elements are directly assigned to their
-    // respective indices.
-    fn run_list(&mut self, count: usize) -> EvalResult<()> {
-        let mut list = vec![Value::Null; count];
-
-        for idx in 0..count {
-            list[count - idx - 1] = self.pop();
-        }
-
-        self.push(Value::List(NixList(list)));
-        Ok(())
-    }
-}
-
-#[derive(Clone, Copy, Debug, PartialEq)]
-pub enum NumberPair {
-    Floats(f64, f64),
-    Integer(i64, i64),
-}
-
-// Set an attribute on an in-construction attribute set, while
-// checking against duplicate key.s
-fn set_attr(
-    attrs: &mut BTreeMap<NixString, Value>,
-    key: NixString,
-    value: Value,
-) -> EvalResult<()> {
-    let entry = attrs.entry(key);
-
-    match entry {
-        std::collections::btree_map::Entry::Occupied(entry) => {
-            return Err(Error::DuplicateAttrsKey {
-                key: entry.key().0.clone(),
-            })
-        }
-
-        std::collections::btree_map::Entry::Vacant(entry) => {
-            entry.insert(value);
-            return Ok(());
-        }
-    };
-}
-
-// Set a nested attribute inside of an attribute set, throwing a
-// duplicate key error if a non-hashmap entry already exists on the
-// path.
-//
-// There is some optimisation potential for this simple implementation
-// if it becomes a problem.
-fn set_nested_attr(
-    attrs: &mut BTreeMap<NixString, Value>,
-    key: NixString,
-    mut path: Vec<NixString>,
-    value: Value,
-) -> EvalResult<()> {
-    // If there is no next key we are at the point where we
-    // should insert the value itself.
-    if path.is_empty() {
-        return set_attr(attrs, key, value);
-    }
-
-    let entry = attrs.entry(key);
-
-    // If there is not we go one step further down, in which case we
-    // need to ensure that there either is no entry, or the existing
-    // entry is a hashmap into which to insert the next value.
-    //
-    // If a value of a different type exists, the user specified a
-    // duplicate key.
-    match entry {
-        // Vacant entry -> new attribute set is needed.
-        std::collections::btree_map::Entry::Vacant(entry) => {
-            let mut map = BTreeMap::new();
-
-            // TODO(tazjin): technically recursing further is not
-            // required, we can create the whole hierarchy here, but
-            // it's noisy.
-            set_nested_attr(&mut map, path.pop().expect("next key exists"), path, value)?;
-
-            entry.insert(Value::Attrs(Rc::new(NixAttrs::Map(map))));
-        }
-
-        // Occupied entry: Either error out if there is something
-        // other than attrs, or insert the next value.
-        std::collections::btree_map::Entry::Occupied(mut entry) => match entry.get_mut() {
-            Value::Attrs(_attrs) => {
-                todo!("implement mutable attrsets")
-            }
-
-            _ => {
-                return Err(Error::DuplicateAttrsKey {
-                    key: entry.key().0.clone(),
-                })
-            }
-        },
-    }
-
-    Ok(())
-}
-
-pub fn run_chunk(chunk: Chunk) -> EvalResult<Value> {
-    let mut vm = VM {
-        chunk,
-        ip: 0,
-        stack: vec![],
-    };
-
-    vm.run()
-}
diff --git a/tvix/eval/src/vm/generators.rs b/tvix/eval/src/vm/generators.rs
new file mode 100644
index 0000000000..79de688692
--- /dev/null
+++ b/tvix/eval/src/vm/generators.rs
@@ -0,0 +1,809 @@
+//! This module implements generator logic for the VM. Generators are functions
+//! used during evaluation which can suspend their execution during their
+//! control flow, and request that the VM do something.
+//!
+//! This is used to keep the VM's stack size constant even when evaluating
+//! deeply nested recursive data structures.
+//!
+//! We implement generators using the [`genawaiter`] crate.
+
+use core::pin::Pin;
+use genawaiter::rc::Co;
+pub use genawaiter::rc::Gen;
+use std::fmt::Display;
+use std::future::Future;
+
+use crate::value::PointerEquality;
+use crate::warnings::{EvalWarning, WarningKind};
+use crate::FileType;
+use crate::NixString;
+
+use super::*;
+
+// -- Implementation of generic generator logic.
+
+/// States that a generator can be in while being driven by the VM.
+pub(crate) enum GeneratorState {
+    /// Normal execution of the generator.
+    Running,
+
+    /// Generator is awaiting the result of a forced value.
+    AwaitingValue,
+}
+
+/// Messages that can be sent from generators *to* the VM. In most
+/// cases, the VM will suspend the generator when receiving a message
+/// and enter some other frame to process the request.
+///
+/// Responses are returned to generators via the [`GeneratorResponse`] type.
+pub enum VMRequest {
+    /// Request that the VM forces this value. This message is first sent to the
+    /// VM with the unforced value, then returned to the generator with the
+    /// forced result.
+    ForceValue(Value),
+
+    /// Request that the VM deep-forces the value.
+    DeepForceValue(Value),
+
+    /// Request the value at the given index from the VM's with-stack, in forced
+    /// state.
+    ///
+    /// The value is returned in the `ForceValue` message.
+    WithValue(usize),
+
+    /// Request the value at the given index from the *captured* with-stack, in
+    /// forced state.
+    CapturedWithValue(usize),
+
+    /// Request that the two values be compared for Nix equality. The result is
+    /// returned in the `ForceValue` message.
+    NixEquality(Box<(Value, Value)>, PointerEquality),
+
+    /// Push the given value to the VM's stack. This is used to prepare the
+    /// stack for requesting a function call from the VM.
+    ///
+    /// The VM does not respond to this request, so the next message received is
+    /// `Empty`.
+    StackPush(Value),
+
+    /// Pop a value from the stack and return it to the generator.
+    StackPop,
+
+    /// Request that the VM coerces this value to a string.
+    StringCoerce(Value, CoercionKind),
+
+    /// Request that the VM calls the given value, with arguments already
+    /// prepared on the stack. Value must already be forced.
+    Call(Value),
+
+    /// Request a call frame entering the given lambda immediately. This can be
+    /// used to force thunks.
+    EnterLambda {
+        lambda: Rc<Lambda>,
+        upvalues: Rc<Upvalues>,
+        light_span: LightSpan,
+    },
+
+    /// Emit a runtime warning (already containing a span) through the VM.
+    EmitWarning(EvalWarning),
+
+    /// Emit a runtime warning through the VM. The span of the current generator
+    /// is used for the final warning.
+    EmitWarningKind(WarningKind),
+
+    /// Request a lookup in the VM's import cache, which tracks the
+    /// thunks yielded by previously imported files.
+    ImportCacheLookup(PathBuf),
+
+    /// Provide the VM with an imported value for a given path, which
+    /// it can populate its input cache with.
+    ImportCachePut(PathBuf, Value),
+
+    /// Request that the VM imports the given path through its I/O interface.
+    PathImport(PathBuf),
+
+    /// Request that the VM opens the specified file and provides a reader.
+    OpenFile(PathBuf),
+
+    /// Request that the VM checks whether the given path exists.
+    PathExists(PathBuf),
+
+    /// Request that the VM reads the given path.
+    ReadDir(PathBuf),
+
+    /// Request a reasonable span from the VM.
+    Span,
+
+    /// Request evaluation of `builtins.tryEval` from the VM. See
+    /// [`VM::catch_result`] for an explanation of how this works.
+    TryForce(Value),
+
+    /// Request serialisation of a value to JSON, according to the
+    /// slightly odd Nix evaluation rules.
+    ToJson(Value),
+}
+
+/// Human-readable representation of a generator message, used by observers.
+impl Display for VMRequest {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            VMRequest::ForceValue(v) => write!(f, "force_value({})", v.type_of()),
+            VMRequest::DeepForceValue(v) => {
+                write!(f, "deep_force_value({})", v.type_of())
+            }
+            VMRequest::WithValue(_) => write!(f, "with_value"),
+            VMRequest::CapturedWithValue(_) => write!(f, "captured_with_value"),
+            VMRequest::NixEquality(values, ptr_eq) => {
+                write!(
+                    f,
+                    "nix_eq({}, {}, PointerEquality::{:?})",
+                    values.0.type_of(),
+                    values.1.type_of(),
+                    ptr_eq
+                )
+            }
+            VMRequest::StackPush(v) => write!(f, "stack_push({})", v.type_of()),
+            VMRequest::StackPop => write!(f, "stack_pop"),
+            VMRequest::StringCoerce(
+                v,
+                CoercionKind {
+                    strong,
+                    import_paths,
+                },
+            ) => write!(
+                f,
+                "{}_{}importing_string_coerce({})",
+                if *strong { "strong" } else { "weak" },
+                if *import_paths { "" } else { "non_" },
+                v.type_of()
+            ),
+            VMRequest::Call(v) => write!(f, "call({})", v),
+            VMRequest::EnterLambda { lambda, .. } => {
+                write!(f, "enter_lambda({:p})", *lambda)
+            }
+            VMRequest::EmitWarning(_) => write!(f, "emit_warning"),
+            VMRequest::EmitWarningKind(_) => write!(f, "emit_warning_kind"),
+            VMRequest::ImportCacheLookup(p) => {
+                write!(f, "import_cache_lookup({})", p.to_string_lossy())
+            }
+            VMRequest::ImportCachePut(p, _) => {
+                write!(f, "import_cache_put({})", p.to_string_lossy())
+            }
+            VMRequest::PathImport(p) => write!(f, "path_import({})", p.to_string_lossy()),
+            VMRequest::OpenFile(p) => {
+                write!(f, "open_file({})", p.to_string_lossy())
+            }
+            VMRequest::PathExists(p) => write!(f, "path_exists({})", p.to_string_lossy()),
+            VMRequest::ReadDir(p) => write!(f, "read_dir({})", p.to_string_lossy()),
+            VMRequest::Span => write!(f, "span"),
+            VMRequest::TryForce(v) => write!(f, "try_force({})", v.type_of()),
+            VMRequest::ToJson(v) => write!(f, "to_json({})", v.type_of()),
+        }
+    }
+}
+
+/// Responses returned to generators *from* the VM.
+pub enum VMResponse {
+    /// Empty message. Passed to the generator as the first message,
+    /// or when return values were optional.
+    Empty,
+
+    /// Value produced by the VM and returned to the generator.
+    Value(Value),
+
+    /// Path produced by the VM in response to some IO operation.
+    Path(PathBuf),
+
+    /// VM response with the contents of a directory.
+    Directory(Vec<(bytes::Bytes, FileType)>),
+
+    /// VM response with a span to use at the current point.
+    Span(LightSpan),
+
+    /// [std::io::Reader] produced by the VM in response to some IO operation.
+    Reader(Box<dyn std::io::Read>),
+}
+
+impl Display for VMResponse {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            VMResponse::Empty => write!(f, "empty"),
+            VMResponse::Value(v) => write!(f, "value({})", v),
+            VMResponse::Path(p) => write!(f, "path({})", p.to_string_lossy()),
+            VMResponse::Directory(d) => write!(f, "dir(len = {})", d.len()),
+            VMResponse::Span(_) => write!(f, "span"),
+            VMResponse::Reader(_) => write!(f, "reader"),
+        }
+    }
+}
+
+pub(crate) type Generator =
+    Gen<VMRequest, VMResponse, Pin<Box<dyn Future<Output = Result<Value, ErrorKind>>>>>;
+
+/// Helper function to provide type annotations which are otherwise difficult to
+/// infer.
+pub fn pin_generator(
+    f: impl Future<Output = Result<Value, ErrorKind>> + 'static,
+) -> Pin<Box<dyn Future<Output = Result<Value, ErrorKind>>>> {
+    Box::pin(f)
+}
+
+impl<'o, IO> VM<'o, IO>
+where
+    IO: AsRef<dyn EvalIO> + 'static,
+{
+    /// Helper function to re-enqueue the current generator while it
+    /// is awaiting a value.
+    fn reenqueue_generator(&mut self, name: &'static str, span: LightSpan, generator: Generator) {
+        self.frames.push(Frame::Generator {
+            name,
+            generator,
+            span,
+            state: GeneratorState::AwaitingValue,
+        });
+    }
+
+    /// Helper function to enqueue a new generator.
+    pub(super) fn enqueue_generator<F, G>(&mut self, name: &'static str, span: LightSpan, gen: G)
+    where
+        F: Future<Output = Result<Value, ErrorKind>> + 'static,
+        G: FnOnce(GenCo) -> F,
+    {
+        self.frames.push(Frame::Generator {
+            name,
+            span,
+            state: GeneratorState::Running,
+            generator: Gen::new(|co| pin_generator(gen(co))),
+        });
+    }
+
+    /// Run a generator frame until it yields to the outer control loop, or runs
+    /// to completion.
+    ///
+    /// The return value indicates whether the generator has completed (true),
+    /// or was suspended (false).
+    pub(crate) fn run_generator(
+        &mut self,
+        name: &'static str,
+        span: LightSpan,
+        frame_id: usize,
+        state: GeneratorState,
+        mut generator: Generator,
+        initial_message: Option<VMResponse>,
+    ) -> EvalResult<bool> {
+        // Determine what to send to the generator based on its state.
+        let mut message = match (initial_message, state) {
+            (Some(msg), _) => msg,
+            (_, GeneratorState::Running) => VMResponse::Empty,
+
+            // If control returned here, and the generator is
+            // awaiting a value, send it the top of the stack.
+            (_, GeneratorState::AwaitingValue) => VMResponse::Value(self.stack_pop()),
+        };
+
+        loop {
+            match generator.resume_with(message) {
+                // If the generator yields, it contains an instruction
+                // for what the VM should do.
+                genawaiter::GeneratorState::Yielded(request) => {
+                    self.observer.observe_generator_request(name, &request);
+
+                    match request {
+                        VMRequest::StackPush(value) => {
+                            self.stack.push(value);
+                            message = VMResponse::Empty;
+                        }
+
+                        VMRequest::StackPop => {
+                            message = VMResponse::Value(self.stack_pop());
+                        }
+
+                        // Generator has requested a force, which means that
+                        // this function prepares the frame stack and yields
+                        // back to the outer VM loop.
+                        VMRequest::ForceValue(value) => {
+                            self.reenqueue_generator(name, span.clone(), generator);
+                            self.enqueue_generator("force", span.clone(), |co| {
+                                value.force_owned_genco(co, span)
+                            });
+                            return Ok(false);
+                        }
+
+                        // Generator has requested a deep-force.
+                        VMRequest::DeepForceValue(value) => {
+                            self.reenqueue_generator(name, span.clone(), generator);
+                            self.enqueue_generator("deep_force", span.clone(), |co| {
+                                value.deep_force(co, span)
+                            });
+                            return Ok(false);
+                        }
+
+                        // Generator has requested a value from the with-stack.
+                        // Logic is similar to `ForceValue`, except with the
+                        // value being taken from that stack.
+                        VMRequest::WithValue(idx) => {
+                            self.reenqueue_generator(name, span.clone(), generator);
+
+                            let value = self.stack[self.with_stack[idx]].clone();
+                            self.enqueue_generator("force", span.clone(), |co| {
+                                value.force_owned_genco(co, span)
+                            });
+
+                            return Ok(false);
+                        }
+
+                        // Generator has requested a value from the *captured*
+                        // with-stack. Logic is same as above, except for the
+                        // value being from that stack.
+                        VMRequest::CapturedWithValue(idx) => {
+                            self.reenqueue_generator(name, span.clone(), generator);
+
+                            let call_frame = self.last_call_frame()
+                                .expect("Tvix bug: generator requested captured with-value, but there is no call frame");
+
+                            let value = call_frame.upvalues.with_stack().unwrap()[idx].clone();
+                            self.enqueue_generator("force", span.clone(), |co| {
+                                value.force_owned_genco(co, span)
+                            });
+
+                            return Ok(false);
+                        }
+
+                        VMRequest::NixEquality(values, ptr_eq) => {
+                            let values = *values;
+                            self.reenqueue_generator(name, span.clone(), generator);
+                            self.enqueue_generator("nix_eq", span.clone(), |co| {
+                                values.0.nix_eq_owned_genco(values.1, co, ptr_eq, span)
+                            });
+                            return Ok(false);
+                        }
+
+                        VMRequest::StringCoerce(val, kind) => {
+                            self.reenqueue_generator(name, span.clone(), generator);
+                            self.enqueue_generator("coerce_to_string", span.clone(), |co| {
+                                val.coerce_to_string(co, kind, span)
+                            });
+                            return Ok(false);
+                        }
+
+                        VMRequest::Call(callable) => {
+                            self.reenqueue_generator(name, span.clone(), generator);
+                            self.call_value(span, None, callable)?;
+                            return Ok(false);
+                        }
+
+                        VMRequest::EnterLambda {
+                            lambda,
+                            upvalues,
+                            light_span,
+                        } => {
+                            self.reenqueue_generator(name, span, generator);
+
+                            self.frames.push(Frame::CallFrame {
+                                span: light_span,
+                                call_frame: CallFrame {
+                                    lambda,
+                                    upvalues,
+                                    ip: CodeIdx(0),
+                                    stack_offset: self.stack.len(),
+                                },
+                            });
+
+                            return Ok(false);
+                        }
+
+                        VMRequest::EmitWarning(warning) => {
+                            self.push_warning(warning);
+                            message = VMResponse::Empty;
+                        }
+
+                        VMRequest::EmitWarningKind(kind) => {
+                            self.emit_warning(kind);
+                            message = VMResponse::Empty;
+                        }
+
+                        VMRequest::ImportCacheLookup(path) => {
+                            if let Some(cached) = self.import_cache.get(path) {
+                                message = VMResponse::Value(cached.clone());
+                            } else {
+                                message = VMResponse::Empty;
+                            }
+                        }
+
+                        VMRequest::ImportCachePut(path, value) => {
+                            self.import_cache.insert(path, value);
+                            message = VMResponse::Empty;
+                        }
+
+                        VMRequest::PathImport(path) => {
+                            let imported = self
+                                .io_handle
+                                .as_ref()
+                                .import_path(&path)
+                                .map_err(|e| ErrorKind::IO {
+                                    path: Some(path),
+                                    error: e.into(),
+                                })
+                                .with_span(&span, self)?;
+
+                            message = VMResponse::Path(imported);
+                        }
+
+                        VMRequest::OpenFile(path) => {
+                            let reader = self
+                                .io_handle
+                                .as_ref()
+                                .open(&path)
+                                .map_err(|e| ErrorKind::IO {
+                                    path: Some(path),
+                                    error: e.into(),
+                                })
+                                .with_span(&span, self)?;
+
+                            message = VMResponse::Reader(reader)
+                        }
+
+                        VMRequest::PathExists(path) => {
+                            let exists = self
+                                .io_handle
+                                .as_ref()
+                                .path_exists(&path)
+                                .map_err(|e| ErrorKind::IO {
+                                    path: Some(path),
+                                    error: e.into(),
+                                })
+                                .map(Value::Bool)
+                                .with_span(&span, self)?;
+
+                            message = VMResponse::Value(exists);
+                        }
+
+                        VMRequest::ReadDir(path) => {
+                            let dir = self
+                                .io_handle
+                                .as_ref()
+                                .read_dir(&path)
+                                .map_err(|e| ErrorKind::IO {
+                                    path: Some(path),
+                                    error: e.into(),
+                                })
+                                .with_span(&span, self)?;
+                            message = VMResponse::Directory(dir);
+                        }
+
+                        VMRequest::Span => {
+                            message = VMResponse::Span(self.reasonable_light_span());
+                        }
+
+                        VMRequest::TryForce(value) => {
+                            self.try_eval_frames.push(frame_id);
+                            self.reenqueue_generator(name, span.clone(), generator);
+
+                            debug_assert!(
+                                self.frames.len() == frame_id + 1,
+                                "generator should be reenqueued with the same frame ID"
+                            );
+
+                            self.enqueue_generator("force", span.clone(), |co| {
+                                value.force_owned_genco(co, span)
+                            });
+                            return Ok(false);
+                        }
+
+                        VMRequest::ToJson(value) => {
+                            self.reenqueue_generator(name, span.clone(), generator);
+                            self.enqueue_generator("to_json", span, |co| {
+                                value.into_contextful_json_generator(co)
+                            });
+                            return Ok(false);
+                        }
+                    }
+                }
+
+                // Generator has completed, and its result value should
+                // be left on the stack.
+                genawaiter::GeneratorState::Complete(result) => {
+                    let value = result.with_span(&span, self)?;
+                    self.stack.push(value);
+                    return Ok(true);
+                }
+            }
+        }
+    }
+}
+
+pub type GenCo = Co<VMRequest, VMResponse>;
+
+// -- Implementation of concrete generator use-cases.
+
+/// Request that the VM place the given value on its stack.
+pub async fn request_stack_push(co: &GenCo, val: Value) {
+    match co.yield_(VMRequest::StackPush(val)).await {
+        VMResponse::Empty => {}
+        msg => panic!(
+            "Tvix bug: VM responded with incorrect generator message: {}",
+            msg
+        ),
+    }
+}
+
+/// Request that the VM pop a value from the stack and return it to the
+/// generator.
+pub async fn request_stack_pop(co: &GenCo) -> Value {
+    match co.yield_(VMRequest::StackPop).await {
+        VMResponse::Value(value) => value,
+        msg => panic!(
+            "Tvix bug: VM responded with incorrect generator message: {}",
+            msg
+        ),
+    }
+}
+
+/// Force any value and return the evaluated result from the VM.
+pub async fn request_force(co: &GenCo, val: Value) -> Value {
+    if let Value::Thunk(_) = val {
+        match co.yield_(VMRequest::ForceValue(val)).await {
+            VMResponse::Value(value) => value,
+            msg => panic!(
+                "Tvix bug: VM responded with incorrect generator message: {}",
+                msg
+            ),
+        }
+    } else {
+        val
+    }
+}
+
+/// Force a value
+pub(crate) async fn request_try_force(co: &GenCo, val: Value) -> Value {
+    if let Value::Thunk(_) = val {
+        match co.yield_(VMRequest::TryForce(val)).await {
+            VMResponse::Value(value) => value,
+            msg => panic!(
+                "Tvix bug: VM responded with incorrect generator message: {}",
+                msg
+            ),
+        }
+    } else {
+        val
+    }
+}
+
+/// Call the given value as a callable. The argument(s) must already be prepared
+/// on the stack.
+pub async fn request_call(co: &GenCo, val: Value) -> Value {
+    let val = request_force(co, val).await;
+    match co.yield_(VMRequest::Call(val)).await {
+        VMResponse::Value(value) => value,
+        msg => panic!(
+            "Tvix bug: VM responded with incorrect generator message: {}",
+            msg
+        ),
+    }
+}
+
+/// Helper function to call the given value with the provided list of arguments.
+/// This uses the StackPush and Call messages under the hood.
+pub async fn request_call_with<I>(co: &GenCo, mut callable: Value, args: I) -> Value
+where
+    I: IntoIterator<Item = Value>,
+    I::IntoIter: DoubleEndedIterator,
+{
+    let mut num_args = 0_usize;
+    for arg in args.into_iter().rev() {
+        num_args += 1;
+        request_stack_push(co, arg).await;
+    }
+
+    debug_assert!(num_args > 0, "call_with called with an empty list of args");
+
+    while num_args > 0 {
+        callable = request_call(co, callable).await;
+        num_args -= 1;
+    }
+
+    callable
+}
+
+pub async fn request_string_coerce(
+    co: &GenCo,
+    val: Value,
+    kind: CoercionKind,
+) -> Result<NixString, CatchableErrorKind> {
+    match val {
+        Value::String(s) => Ok(s),
+        _ => match co.yield_(VMRequest::StringCoerce(val, kind)).await {
+            VMResponse::Value(Value::Catchable(c)) => Err(*c),
+            VMResponse::Value(value) => Ok(value
+                .to_contextful_str()
+                .expect("coerce_to_string always returns a string")),
+            msg => panic!(
+                "Tvix bug: VM responded with incorrect generator message: {}",
+                msg
+            ),
+        },
+    }
+}
+
+/// Deep-force any value and return the evaluated result from the VM.
+pub async fn request_deep_force(co: &GenCo, val: Value) -> Value {
+    match co.yield_(VMRequest::DeepForceValue(val)).await {
+        VMResponse::Value(value) => value,
+        msg => panic!(
+            "Tvix bug: VM responded with incorrect generator message: {}",
+            msg
+        ),
+    }
+}
+
+/// Ask the VM to compare two values for equality.
+pub(crate) async fn check_equality(
+    co: &GenCo,
+    a: Value,
+    b: Value,
+    ptr_eq: PointerEquality,
+) -> Result<Result<bool, CatchableErrorKind>, ErrorKind> {
+    match co
+        .yield_(VMRequest::NixEquality(Box::new((a, b)), ptr_eq))
+        .await
+    {
+        VMResponse::Value(Value::Bool(b)) => Ok(Ok(b)),
+        VMResponse::Value(Value::Catchable(cek)) => Ok(Err(*cek)),
+        msg => panic!(
+            "Tvix bug: VM responded with incorrect generator message: {}",
+            msg
+        ),
+    }
+}
+
+/// Emit a fully constructed runtime warning.
+pub(crate) async fn emit_warning(co: &GenCo, warning: EvalWarning) {
+    match co.yield_(VMRequest::EmitWarning(warning)).await {
+        VMResponse::Empty => {}
+        msg => panic!(
+            "Tvix bug: VM responded with incorrect generator message: {}",
+            msg
+        ),
+    }
+}
+
+/// Emit a runtime warning with the span of the current generator.
+pub async fn emit_warning_kind(co: &GenCo, kind: WarningKind) {
+    match co.yield_(VMRequest::EmitWarningKind(kind)).await {
+        VMResponse::Empty => {}
+        msg => panic!(
+            "Tvix bug: VM responded with incorrect generator message: {}",
+            msg
+        ),
+    }
+}
+
+/// Request that the VM enter the given lambda.
+pub(crate) async fn request_enter_lambda(
+    co: &GenCo,
+    lambda: Rc<Lambda>,
+    upvalues: Rc<Upvalues>,
+    light_span: LightSpan,
+) -> Value {
+    let msg = VMRequest::EnterLambda {
+        lambda,
+        upvalues,
+        light_span,
+    };
+
+    match co.yield_(msg).await {
+        VMResponse::Value(value) => value,
+        msg => panic!(
+            "Tvix bug: VM responded with incorrect generator message: {}",
+            msg
+        ),
+    }
+}
+
+/// Request a lookup in the VM's import cache.
+pub(crate) async fn request_import_cache_lookup(co: &GenCo, path: PathBuf) -> Option<Value> {
+    match co.yield_(VMRequest::ImportCacheLookup(path)).await {
+        VMResponse::Value(value) => Some(value),
+        VMResponse::Empty => None,
+        msg => panic!(
+            "Tvix bug: VM responded with incorrect generator message: {}",
+            msg
+        ),
+    }
+}
+
+/// Request that the VM populate its input cache for the given path.
+pub(crate) async fn request_import_cache_put(co: &GenCo, path: PathBuf, value: Value) {
+    match co.yield_(VMRequest::ImportCachePut(path, value)).await {
+        VMResponse::Empty => {}
+        msg => panic!(
+            "Tvix bug: VM responded with incorrect generator message: {}",
+            msg
+        ),
+    }
+}
+
+/// Request that the VM import the given path.
+pub(crate) async fn request_path_import(co: &GenCo, path: PathBuf) -> PathBuf {
+    match co.yield_(VMRequest::PathImport(path)).await {
+        VMResponse::Path(path) => path,
+        msg => panic!(
+            "Tvix bug: VM responded with incorrect generator message: {}",
+            msg
+        ),
+    }
+}
+
+/// Request that the VM open a [std::io::Read] for the specified file.
+pub async fn request_open_file(co: &GenCo, path: PathBuf) -> Box<dyn std::io::Read> {
+    match co.yield_(VMRequest::OpenFile(path)).await {
+        VMResponse::Reader(value) => value,
+        msg => panic!(
+            "Tvix bug: VM responded with incorrect generator message: {}",
+            msg
+        ),
+    }
+}
+
+pub(crate) async fn request_path_exists(co: &GenCo, path: PathBuf) -> Value {
+    match co.yield_(VMRequest::PathExists(path)).await {
+        VMResponse::Value(value) => value,
+        msg => panic!(
+            "Tvix bug: VM responded with incorrect generator message: {}",
+            msg
+        ),
+    }
+}
+
+pub(crate) async fn request_read_dir(co: &GenCo, path: PathBuf) -> Vec<(bytes::Bytes, FileType)> {
+    match co.yield_(VMRequest::ReadDir(path)).await {
+        VMResponse::Directory(dir) => dir,
+        msg => panic!(
+            "Tvix bug: VM responded with incorrect generator message: {}",
+            msg
+        ),
+    }
+}
+
+pub(crate) async fn request_span(co: &GenCo) -> LightSpan {
+    match co.yield_(VMRequest::Span).await {
+        VMResponse::Span(span) => span,
+        msg => panic!(
+            "Tvix bug: VM responded with incorrect generator message: {}",
+            msg
+        ),
+    }
+}
+
+pub(crate) async fn request_to_json(
+    co: &GenCo,
+    value: Value,
+) -> Result<(serde_json::Value, NixContext), CatchableErrorKind> {
+    match co.yield_(VMRequest::ToJson(value)).await {
+        VMResponse::Value(Value::Json(json_with_ctx)) => Ok(*json_with_ctx),
+        VMResponse::Value(Value::Catchable(cek)) => Err(*cek),
+        msg => panic!(
+            "Tvix bug: VM responded with incorrect generator message: {}",
+            msg
+        ),
+    }
+}
+
+/// Call the given value as if it was an attribute set containing a functor. The
+/// arguments must already be prepared on the stack when a generator frame from
+/// this function is invoked.
+///
+pub(crate) async fn call_functor(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
+    let attrs = value.to_attrs()?;
+
+    match attrs.select("__functor") {
+        None => Err(ErrorKind::NotCallable("set without `__functor_` attribute")),
+        Some(functor) => {
+            // The functor receives the set itself as its first argument and
+            // needs to be called with it.
+            let functor = request_force(&co, functor.clone()).await;
+            let primed = request_call_with(&co, functor, [value]).await;
+            Ok(request_call(&co, primed).await)
+        }
+    }
+}
diff --git a/tvix/eval/src/vm/macros.rs b/tvix/eval/src/vm/macros.rs
new file mode 100644
index 0000000000..d8a09706ab
--- /dev/null
+++ b/tvix/eval/src/vm/macros.rs
@@ -0,0 +1,93 @@
+/// This module provides macros which are used in the implementation
+/// of the VM for the implementation of repetitive operations.
+
+/// This macro simplifies the implementation of arithmetic operations,
+/// correctly handling the behaviour on different pairings of number
+/// types.
+#[macro_export]
+macro_rules! arithmetic_op {
+    ( $self:ident, $op:tt ) => {{ // TODO: remove
+        let b = $self.pop();
+        let a = $self.pop();
+        let result = fallible!($self, arithmetic_op!(&a, &b, $op));
+        $self.push(result);
+    }};
+
+    ( $a:expr, $b:expr, $op:tt ) => {{
+        match ($a, $b) {
+            (Value::Integer(i1), Value::Integer(i2)) => Ok(Value::Integer(i1 $op i2)),
+            (Value::Float(f1), Value::Float(f2)) => Ok(Value::Float(f1 $op f2)),
+            (Value::Integer(i1), Value::Float(f2)) => Ok(Value::Float(*i1 as f64 $op f2)),
+            (Value::Float(f1), Value::Integer(i2)) => Ok(Value::Float(f1 $op *i2 as f64)),
+
+            (v1, v2) => Err(ErrorKind::TypeError {
+                expected: "number (either int or float)",
+                actual: if v1.is_number() {
+                    v2.type_of()
+                } else {
+                    v1.type_of()
+                },
+            }),
+        }
+    }};
+}
+
+/// This macro simplifies the implementation of comparison operations.
+#[macro_export]
+macro_rules! cmp_op {
+    ( $vm:ident, $frame:ident, $span:ident, $op:tt ) => {{
+        lifted_pop! {
+            $vm(b, a) => {
+                async fn compare(a: Value, b: Value, co: GenCo) -> Result<Value, ErrorKind> {
+                    let a = generators::request_force(&co, a).await;
+                    let b = generators::request_force(&co, b).await;
+                    let span = generators::request_span(&co).await;
+                    let ordering = a.nix_cmp_ordering(b, co, span).await?;
+                    match ordering {
+                        Err(cek) => Ok(Value::from(cek)),
+                        Ok(ordering) => Ok(Value::Bool(cmp_op!(@order $op ordering))),
+                    }
+                }
+
+                let gen_span = $frame.current_light_span();
+                $vm.push_call_frame($span, $frame);
+                $vm.enqueue_generator("compare", gen_span, |co| compare(a, b, co));
+                return Ok(false);
+            }
+        }
+    }};
+
+    (@order < $ordering:expr) => {
+        $ordering == Ordering::Less
+    };
+
+    (@order > $ordering:expr) => {
+        $ordering == Ordering::Greater
+    };
+
+    (@order <= $ordering:expr) => {
+        matches!($ordering, Ordering::Equal | Ordering::Less)
+    };
+
+    (@order >= $ordering:expr) => {
+        matches!($ordering, Ordering::Equal | Ordering::Greater)
+    };
+}
+
+#[macro_export]
+macro_rules! lifted_pop {
+    ($vm:ident ($($bind:ident),+) => $body:expr) => {
+        {
+            $(
+                let $bind = $vm.stack_pop();
+            )+
+            $(
+                if $bind.is_catchable() {
+                    $vm.stack.push($bind);
+                    continue;
+                }
+            )+
+            $body
+        }
+    }
+}
diff --git a/tvix/eval/src/vm/mod.rs b/tvix/eval/src/vm/mod.rs
new file mode 100644
index 0000000000..5c244cc3ca
--- /dev/null
+++ b/tvix/eval/src/vm/mod.rs
@@ -0,0 +1,1368 @@
+//! This module implements the abstract/virtual machine that runs Tvix
+//! bytecode.
+//!
+//! The operation of the VM is facilitated by the [`Frame`] type,
+//! which controls the current execution state of the VM and is
+//! processed within the VM's operating loop.
+//!
+//! A [`VM`] is used by instantiating it with an initial [`Frame`],
+//! then triggering its execution and waiting for the VM to return or
+//! yield an error.
+
+pub mod generators;
+mod macros;
+
+use bstr::{BString, ByteSlice, ByteVec};
+use codemap::Span;
+use serde_json::json;
+use std::{cmp::Ordering, collections::HashMap, ops::DerefMut, path::PathBuf, rc::Rc};
+
+use crate::{
+    arithmetic_op,
+    chunk::Chunk,
+    cmp_op,
+    compiler::GlobalsMap,
+    errors::{CatchableErrorKind, Error, ErrorKind, EvalResult},
+    io::EvalIO,
+    lifted_pop,
+    nix_search_path::NixSearchPath,
+    observer::RuntimeObserver,
+    opcode::{CodeIdx, Count, JumpOffset, OpCode, StackIdx, UpvalueIdx},
+    spans::LightSpan,
+    upvalues::Upvalues,
+    value::{
+        Builtin, BuiltinResult, Closure, CoercionKind, Lambda, NixAttrs, NixContext, NixList,
+        PointerEquality, Thunk, Value,
+    },
+    vm::generators::GenCo,
+    warnings::{EvalWarning, WarningKind},
+    NixString, SourceCode,
+};
+
+use generators::{call_functor, Generator, GeneratorState};
+
+use self::generators::{VMRequest, VMResponse};
+
+/// Internal helper trait for taking a span from a variety of types, to make use
+/// of `WithSpan` (defined below) more ergonomic at call sites.
+trait GetSpan {
+    fn get_span(self) -> Span;
+}
+
+impl<'o, IO> GetSpan for &VM<'o, IO> {
+    fn get_span(self) -> Span {
+        self.reasonable_span.span()
+    }
+}
+
+impl GetSpan for &CallFrame {
+    fn get_span(self) -> Span {
+        self.current_span()
+    }
+}
+
+impl GetSpan for &LightSpan {
+    fn get_span(self) -> Span {
+        self.span()
+    }
+}
+
+impl GetSpan for Span {
+    fn get_span(self) -> Span {
+        self
+    }
+}
+
+/// Internal helper trait for ergonomically converting from a `Result<T,
+/// ErrorKind>` to a `Result<T, Error>` using the current span of a call frame,
+/// and chaining the VM's frame stack around it for printing a cause chain.
+trait WithSpan<T, S: GetSpan, IO> {
+    fn with_span(self, top_span: S, vm: &VM<IO>) -> Result<T, Error>;
+}
+
+impl<T, S: GetSpan, IO> WithSpan<T, S, IO> for Result<T, ErrorKind> {
+    fn with_span(self, top_span: S, vm: &VM<IO>) -> Result<T, Error> {
+        match self {
+            Ok(something) => Ok(something),
+            Err(kind) => {
+                let mut error = Error::new(kind, top_span.get_span(), vm.source.clone());
+
+                // Wrap the top-level error in chaining errors for each element
+                // of the frame stack.
+                for frame in vm.frames.iter().rev() {
+                    match frame {
+                        Frame::CallFrame { span, .. } => {
+                            error = Error::new(
+                                ErrorKind::BytecodeError(Box::new(error)),
+                                span.span(),
+                                vm.source.clone(),
+                            );
+                        }
+                        Frame::Generator { name, span, .. } => {
+                            error = Error::new(
+                                ErrorKind::NativeError {
+                                    err: Box::new(error),
+                                    gen_type: name,
+                                },
+                                span.span(),
+                                vm.source.clone(),
+                            );
+                        }
+                    }
+                }
+
+                Err(error)
+            }
+        }
+    }
+}
+
+struct CallFrame {
+    /// The lambda currently being executed.
+    lambda: Rc<Lambda>,
+
+    /// Optional captured upvalues of this frame (if a thunk or
+    /// closure if being evaluated).
+    upvalues: Rc<Upvalues>,
+
+    /// Instruction pointer to the instruction currently being
+    /// executed.
+    ip: CodeIdx,
+
+    /// Stack offset, i.e. the frames "view" into the VM's full stack.
+    stack_offset: usize,
+}
+
+impl CallFrame {
+    /// Retrieve an upvalue from this frame at the given index.
+    fn upvalue(&self, idx: UpvalueIdx) -> &Value {
+        &self.upvalues[idx]
+    }
+
+    /// Borrow the chunk of this frame's lambda.
+    fn chunk(&self) -> &Chunk {
+        &self.lambda.chunk
+    }
+
+    /// Increment this frame's instruction pointer and return the operation that
+    /// the pointer moved past.
+    fn inc_ip(&mut self) -> OpCode {
+        let op = self.chunk()[self.ip];
+        self.ip += 1;
+        op
+    }
+
+    /// Construct an error result from the given ErrorKind and the source span
+    /// of the current instruction.
+    pub fn error<T, IO>(&self, vm: &VM<IO>, kind: ErrorKind) -> Result<T, Error> {
+        Err(kind).with_span(self, vm)
+    }
+
+    /// Returns the current span. This is potentially expensive and should only
+    /// be used when actually constructing an error or warning.
+    pub fn current_span(&self) -> Span {
+        self.chunk().get_span(self.ip - 1)
+    }
+
+    /// Returns the information needed to calculate the current span,
+    /// but without performing that calculation.
+    // TODO: why pub?
+    pub(crate) fn current_light_span(&self) -> LightSpan {
+        LightSpan::new_actual(self.current_span())
+    }
+}
+
+/// A frame represents an execution state of the VM. The VM has a stack of
+/// frames representing the nesting of execution inside of the VM, and operates
+/// on the frame at the top.
+///
+/// When a frame has been fully executed, it is removed from the VM's frame
+/// stack and expected to leave a result [`Value`] on the top of the stack.
+enum Frame {
+    /// CallFrame represents the execution of Tvix bytecode within a thunk,
+    /// function or closure.
+    CallFrame {
+        /// The call frame itself, separated out into another type to pass it
+        /// around easily.
+        call_frame: CallFrame,
+
+        /// Span from which the call frame was launched.
+        span: LightSpan,
+    },
+
+    /// Generator represents a frame that can yield further
+    /// instructions to the VM while its execution is being driven.
+    ///
+    /// A generator is essentially an asynchronous function that can
+    /// be suspended while waiting for the VM to do something (e.g.
+    /// thunk forcing), and resume at the same point.
+    Generator {
+        /// human-readable description of the generator,
+        name: &'static str,
+
+        /// Span from which the generator was launched.
+        span: LightSpan,
+
+        state: GeneratorState,
+
+        /// Generator itself, which can be resumed with `.resume()`.
+        generator: Generator,
+    },
+}
+
+impl Frame {
+    pub fn span(&self) -> LightSpan {
+        match self {
+            Frame::CallFrame { span, .. } | Frame::Generator { span, .. } => span.clone(),
+        }
+    }
+}
+
+#[derive(Default)]
+struct ImportCache(HashMap<PathBuf, Value>);
+
+/// The `ImportCache` holds the `Value` resulting from `import`ing a certain
+/// file, so that the same file doesn't need to be re-evaluated multiple times.
+/// Currently the real path of the imported file (determined using
+/// [`std::fs::canonicalize()`], not to be confused with our
+/// [`crate::value::canon_path()`]) is used to identify the file,
+/// just like C++ Nix does.
+///
+/// Errors while determining the real path are currently just ignored, since we
+/// pass around some fake paths like `/__corepkgs__/fetchurl.nix`.
+///
+/// In the future, we could use something more sophisticated, like file hashes.
+/// However, a consideration is that the eval cache is observable via impurities
+/// like pointer equality and `builtins.trace`.
+impl ImportCache {
+    fn get(&self, path: PathBuf) -> Option<&Value> {
+        let path = match std::fs::canonicalize(path.as_path()).map_err(ErrorKind::from) {
+            Ok(path) => path,
+            Err(_) => path,
+        };
+        self.0.get(&path)
+    }
+
+    fn insert(&mut self, path: PathBuf, value: Value) -> Option<Value> {
+        self.0.insert(
+            match std::fs::canonicalize(path.as_path()).map_err(ErrorKind::from) {
+                Ok(path) => path,
+                Err(_) => path,
+            },
+            value,
+        )
+    }
+}
+
+struct VM<'o, IO> {
+    /// VM's frame stack, representing the execution contexts the VM is working
+    /// through. Elements are usually pushed when functions are called, or
+    /// thunks are being forced.
+    frames: Vec<Frame>,
+
+    /// The VM's top-level value stack. Within this stack, each code-executing
+    /// frame holds a "view" of the stack representing the slice of the
+    /// top-level stack that is relevant to its operation. This is done to avoid
+    /// allocating a new `Vec` for each frame's stack.
+    pub(crate) stack: Vec<Value>,
+
+    /// Stack indices (absolute indexes into `stack`) of attribute
+    /// sets from which variables should be dynamically resolved
+    /// (`with`).
+    with_stack: Vec<usize>,
+
+    /// Runtime warnings collected during evaluation.
+    warnings: Vec<EvalWarning>,
+
+    /// Import cache, mapping absolute file paths to the value that
+    /// they compile to. Note that this reuses thunks, too!
+    // TODO: should probably be based on a file hash
+    pub import_cache: ImportCache,
+
+    /// Data structure holding all source code evaluated in this VM,
+    /// used for pretty error reporting.
+    source: SourceCode,
+
+    /// Parsed Nix search path, which is used to resolve `<...>`
+    /// references.
+    nix_search_path: NixSearchPath,
+
+    /// Implementation of I/O operations used for impure builtins and
+    /// features like `import`.
+    io_handle: IO,
+
+    /// Runtime observer which can print traces of runtime operations.
+    observer: &'o mut dyn RuntimeObserver,
+
+    /// Strong reference to the globals, guaranteeing that they are
+    /// kept alive for the duration of evaluation.
+    ///
+    /// This is important because recursive builtins (specifically
+    /// `import`) hold a weak reference to the builtins, while the
+    /// original strong reference is held by the compiler which does
+    /// not exist anymore at runtime.
+    #[allow(dead_code)]
+    globals: Rc<GlobalsMap>,
+
+    /// A reasonably applicable span that can be used for errors in each
+    /// execution situation.
+    ///
+    /// The VM should update this whenever control flow changes take place (i.e.
+    /// entering or exiting a frame to yield control somewhere).
+    reasonable_span: LightSpan,
+
+    /// This field is responsible for handling `builtins.tryEval`. When that
+    /// builtin is encountered, it sends a special message to the VM which
+    /// pushes the frame index that requested to be informed of catchable
+    /// errors in this field.
+    ///
+    /// The frame stack is then laid out like this:
+    ///
+    /// ```notrust
+    /// ┌──┬──────────────────────────┐
+    /// │ 0│ `Result`-producing frame │
+    /// ├──┼──────────────────────────┤
+    /// │-1│ `builtins.tryEval` frame │
+    /// ├──┼──────────────────────────┤
+    /// │..│ ... other frames ...     │
+    /// └──┴──────────────────────────┘
+    /// ```
+    ///
+    /// Control is yielded to the outer VM loop, which evaluates the next frame
+    /// and returns the result itself to the `builtins.tryEval` frame.
+    try_eval_frames: Vec<usize>,
+}
+
+impl<'o, IO> VM<'o, IO>
+where
+    IO: AsRef<dyn EvalIO> + 'static,
+{
+    pub fn new(
+        nix_search_path: NixSearchPath,
+        io_handle: IO,
+        observer: &'o mut dyn RuntimeObserver,
+        source: SourceCode,
+        globals: Rc<GlobalsMap>,
+        reasonable_span: LightSpan,
+    ) -> Self {
+        Self {
+            nix_search_path,
+            io_handle,
+            observer,
+            globals,
+            reasonable_span,
+            source,
+            frames: vec![],
+            stack: vec![],
+            with_stack: vec![],
+            warnings: vec![],
+            import_cache: Default::default(),
+            try_eval_frames: vec![],
+        }
+    }
+
+    /// Push a call frame onto the frame stack.
+    fn push_call_frame(&mut self, span: LightSpan, call_frame: CallFrame) {
+        self.frames.push(Frame::CallFrame { span, call_frame })
+    }
+
+    /// Run the VM's primary (outer) execution loop, continuing execution based
+    /// on the current frame at the top of the frame stack.
+    fn execute(mut self) -> EvalResult<RuntimeResult> {
+        while let Some(frame) = self.frames.pop() {
+            self.reasonable_span = frame.span();
+            let frame_id = self.frames.len();
+
+            match frame {
+                Frame::CallFrame { call_frame, span } => {
+                    self.observer
+                        .observe_enter_call_frame(0, &call_frame.lambda, frame_id);
+
+                    match self.execute_bytecode(span, call_frame) {
+                        Ok(true) => self.observer.observe_exit_call_frame(frame_id, &self.stack),
+                        Ok(false) => self
+                            .observer
+                            .observe_suspend_call_frame(frame_id, &self.stack),
+
+                        Err(err) => return Err(err),
+                    };
+                }
+
+                // Handle generator frames, which can request thunk forcing
+                // during their execution.
+                Frame::Generator {
+                    name,
+                    span,
+                    state,
+                    generator,
+                } => {
+                    self.observer
+                        .observe_enter_generator(frame_id, name, &self.stack);
+
+                    match self.run_generator(name, span, frame_id, state, generator, None) {
+                        Ok(true) => {
+                            self.observer
+                                .observe_exit_generator(frame_id, name, &self.stack)
+                        }
+                        Ok(false) => {
+                            self.observer
+                                .observe_suspend_generator(frame_id, name, &self.stack)
+                        }
+
+                        Err(err) => return Err(err),
+                    };
+                }
+            }
+        }
+
+        // Once no more frames are present, return the stack's top value as the
+        // result.
+        let value = self
+            .stack
+            .pop()
+            .expect("tvix bug: runtime stack empty after execution");
+        Ok(RuntimeResult {
+            value,
+            warnings: self.warnings,
+        })
+    }
+
+    /// Run the VM's inner execution loop, processing Tvix bytecode from a
+    /// chunk. This function returns if:
+    ///
+    /// 1. The code has run to the end, and has left a value on the top of the
+    ///    stack. In this case, the frame is not returned to the frame stack.
+    ///
+    /// 2. The code encounters a generator, in which case the frame in its
+    /// current state is pushed back on the stack, and the generator is left on
+    /// top of it for the outer loop to execute.
+    ///
+    /// 3. An error is encountered.
+    ///
+    /// This function *must* ensure that it leaves the frame stack in the
+    /// correct order, especially when re-enqueuing a frame to execute.
+    ///
+    /// The return value indicates whether the bytecode has been executed to
+    /// completion, or whether it has been suspended in favour of a generator.
+    fn execute_bytecode(&mut self, span: LightSpan, mut frame: CallFrame) -> EvalResult<bool> {
+        loop {
+            let op = frame.inc_ip();
+            self.observer.observe_execute_op(frame.ip, &op, &self.stack);
+
+            match op {
+                OpCode::OpThunkSuspended(idx) | OpCode::OpThunkClosure(idx) => {
+                    let blueprint = match &frame.chunk()[idx] {
+                        Value::Blueprint(lambda) => lambda.clone(),
+                        _ => panic!("compiler bug: non-blueprint in blueprint slot"),
+                    };
+
+                    let upvalue_count = blueprint.upvalue_count;
+                    let thunk = if matches!(op, OpCode::OpThunkClosure(_)) {
+                        debug_assert!(
+                            upvalue_count > 0,
+                            "OpThunkClosure should not be called for plain lambdas"
+                        );
+                        Thunk::new_closure(blueprint)
+                    } else {
+                        Thunk::new_suspended(blueprint, frame.current_light_span())
+                    };
+                    let upvalues = thunk.upvalues_mut();
+                    self.stack.push(Value::Thunk(thunk.clone()));
+
+                    // From this point on we internally mutate the
+                    // upvalues. The closure (if `is_closure`) is
+                    // already in its stack slot, which means that it
+                    // can capture itself as an upvalue for
+                    // self-recursion.
+                    self.populate_upvalues(&mut frame, upvalue_count, upvalues)?;
+                }
+
+                OpCode::OpForce => {
+                    if let Some(Value::Thunk(_)) = self.stack.last() {
+                        let thunk = match self.stack_pop() {
+                            Value::Thunk(t) => t,
+                            _ => unreachable!(),
+                        };
+
+                        let gen_span = frame.current_light_span();
+
+                        self.push_call_frame(span, frame);
+                        self.enqueue_generator("force", gen_span.clone(), |co| {
+                            Thunk::force(thunk, co, gen_span)
+                        });
+
+                        return Ok(false);
+                    }
+                }
+
+                OpCode::OpGetUpvalue(upv_idx) => {
+                    let value = frame.upvalue(upv_idx).clone();
+                    self.stack.push(value);
+                }
+
+                // Discard the current frame.
+                OpCode::OpReturn => {
+                    // TODO(amjoseph): I think this should assert `==` rather
+                    // than `<=` but it fails with the stricter condition.
+                    debug_assert!(self.stack.len() - 1 <= frame.stack_offset);
+                    return Ok(true);
+                }
+
+                OpCode::OpConstant(idx) => {
+                    let c = frame.chunk()[idx].clone();
+                    self.stack.push(c);
+                }
+
+                OpCode::OpCall => {
+                    let callable = self.stack_pop();
+                    self.call_value(frame.current_light_span(), Some((span, frame)), callable)?;
+
+                    // exit this loop and let the outer loop enter the new call
+                    return Ok(true);
+                }
+
+                // Remove the given number of elements from the stack,
+                // but retain the top value.
+                OpCode::OpCloseScope(Count(count)) => {
+                    // Immediately move the top value into the right
+                    // position.
+                    let target_idx = self.stack.len() - 1 - count;
+                    self.stack[target_idx] = self.stack_pop();
+
+                    // Then drop the remaining values.
+                    for _ in 0..(count - 1) {
+                        self.stack.pop();
+                    }
+                }
+
+                OpCode::OpClosure(idx) => {
+                    let blueprint = match &frame.chunk()[idx] {
+                        Value::Blueprint(lambda) => lambda.clone(),
+                        _ => panic!("compiler bug: non-blueprint in blueprint slot"),
+                    };
+
+                    let upvalue_count = blueprint.upvalue_count;
+                    debug_assert!(
+                        upvalue_count > 0,
+                        "OpClosure should not be called for plain lambdas"
+                    );
+
+                    let mut upvalues = Upvalues::with_capacity(blueprint.upvalue_count);
+                    self.populate_upvalues(&mut frame, upvalue_count, &mut upvalues)?;
+                    self.stack
+                        .push(Value::Closure(Rc::new(Closure::new_with_upvalues(
+                            Rc::new(upvalues),
+                            blueprint,
+                        ))));
+                }
+
+                OpCode::OpAttrsSelect => lifted_pop! {
+                    self(key, attrs) => {
+                        let key = key.to_str().with_span(&frame, self)?;
+                        let attrs = attrs.to_attrs().with_span(&frame, self)?;
+
+                        match attrs.select(&key) {
+                            Some(value) => self.stack.push(value.clone()),
+
+                            None => {
+                                return frame.error(
+                                    self,
+                                    ErrorKind::AttributeNotFound {
+                                        name: key.to_str_lossy().into_owned()
+                                    },
+                                );
+                            }
+                        }
+                    }
+                },
+
+                OpCode::OpJumpIfFalse(JumpOffset(offset)) => {
+                    debug_assert!(offset != 0);
+                    if !self.stack_peek(0).as_bool().with_span(&frame, self)? {
+                        frame.ip += offset;
+                    }
+                }
+
+                OpCode::OpJumpIfCatchable(JumpOffset(offset)) => {
+                    debug_assert!(offset != 0);
+                    if self.stack_peek(0).is_catchable() {
+                        frame.ip += offset;
+                    }
+                }
+
+                OpCode::OpJumpIfNoFinaliseRequest(JumpOffset(offset)) => {
+                    debug_assert!(offset != 0);
+                    match self.stack_peek(0) {
+                        Value::FinaliseRequest(finalise) => {
+                            if !finalise {
+                                frame.ip += offset;
+                            }
+                        },
+                        val => panic!("Tvix bug: OpJumIfNoFinaliseRequest: expected FinaliseRequest, but got {}", val.type_of()),
+                    }
+                }
+
+                OpCode::OpPop => {
+                    self.stack.pop();
+                }
+
+                OpCode::OpAttrsTrySelect => {
+                    let key = self.stack_pop().to_str().with_span(&frame, self)?;
+                    let value = match self.stack_pop() {
+                        Value::Attrs(attrs) => match attrs.select(&key) {
+                            Some(value) => value.clone(),
+                            None => Value::AttrNotFound,
+                        },
+
+                        _ => Value::AttrNotFound,
+                    };
+
+                    self.stack.push(value);
+                }
+
+                OpCode::OpGetLocal(StackIdx(local_idx)) => {
+                    let idx = frame.stack_offset + local_idx;
+                    self.stack.push(self.stack[idx].clone());
+                }
+
+                OpCode::OpJumpIfNotFound(JumpOffset(offset)) => {
+                    debug_assert!(offset != 0);
+                    if matches!(self.stack_peek(0), Value::AttrNotFound) {
+                        self.stack_pop();
+                        frame.ip += offset;
+                    }
+                }
+
+                OpCode::OpJump(JumpOffset(offset)) => {
+                    debug_assert!(offset != 0);
+                    frame.ip += offset;
+                }
+
+                OpCode::OpEqual => lifted_pop! {
+                    self(b, a) => {
+                        let gen_span = frame.current_light_span();
+                        self.push_call_frame(span, frame);
+                        self.enqueue_generator("nix_eq", gen_span.clone(), |co| {
+                            a.nix_eq_owned_genco(b, co, PointerEquality::ForbidAll, gen_span)
+                        });
+                        return Ok(false);
+                    }
+                },
+
+                // These assertion operations error out if the stack
+                // top is not of the expected type. This is necessary
+                // to implement some specific behaviours of Nix
+                // exactly.
+                OpCode::OpAssertBool => {
+                    let val = self.stack_peek(0);
+                    // TODO(edef): propagate this into is_bool, since bottom values *are* values of any type
+                    if !val.is_catchable() && !val.is_bool() {
+                        return frame.error(
+                            self,
+                            ErrorKind::TypeError {
+                                expected: "bool",
+                                actual: val.type_of(),
+                            },
+                        );
+                    }
+                }
+
+                OpCode::OpAssertAttrs => {
+                    let val = self.stack_peek(0);
+                    // TODO(edef): propagate this into is_attrs, since bottom values *are* values of any type
+                    if !val.is_catchable() && !val.is_attrs() {
+                        return frame.error(
+                            self,
+                            ErrorKind::TypeError {
+                                expected: "set",
+                                actual: val.type_of(),
+                            },
+                        );
+                    }
+                }
+
+                OpCode::OpAttrs(Count(count)) => self.run_attrset(&frame, count)?,
+
+                OpCode::OpAttrsUpdate => lifted_pop! {
+                    self(rhs, lhs) => {
+                        let rhs = rhs.to_attrs().with_span(&frame, self)?;
+                        let lhs = lhs.to_attrs().with_span(&frame, self)?;
+                        self.stack.push(Value::attrs(lhs.update(*rhs)))
+                    }
+                },
+
+                OpCode::OpInvert => lifted_pop! {
+                    self(v) => {
+                        let v = v.as_bool().with_span(&frame, self)?;
+                        self.stack.push(Value::Bool(!v));
+                    }
+                },
+
+                OpCode::OpList(Count(count)) => {
+                    let list =
+                        NixList::construct(count, self.stack.split_off(self.stack.len() - count));
+
+                    self.stack.push(Value::List(list));
+                }
+
+                OpCode::OpJumpIfTrue(JumpOffset(offset)) => {
+                    debug_assert!(offset != 0);
+                    if self.stack_peek(0).as_bool().with_span(&frame, self)? {
+                        frame.ip += offset;
+                    }
+                }
+
+                OpCode::OpHasAttr => lifted_pop! {
+                    self(key, attrs) => {
+                        let key = key.to_str().with_span(&frame, self)?;
+                        let result = match attrs {
+                            Value::Attrs(attrs) => attrs.contains(&key),
+
+                            // Nix allows use of `?` on non-set types, but
+                            // always returns false in those cases.
+                            _ => false,
+                        };
+
+                        self.stack.push(Value::Bool(result));
+                    }
+                },
+
+                OpCode::OpConcat => lifted_pop! {
+                    self(rhs, lhs) => {
+                        let rhs = rhs.to_list().with_span(&frame, self)?.into_inner();
+                        let lhs = lhs.to_list().with_span(&frame, self)?.into_inner();
+                        self.stack.push(Value::List(NixList::from(lhs + rhs)))
+                    }
+                },
+
+                OpCode::OpResolveWith => {
+                    let ident = self.stack_pop().to_str().with_span(&frame, self)?;
+
+                    // Re-enqueue this frame.
+                    let op_span = frame.current_light_span();
+                    self.push_call_frame(span, frame);
+
+                    // Construct a generator frame doing the lookup in constant
+                    // stack space.
+                    let with_stack_len = self.with_stack.len();
+                    let closed_with_stack_len = self
+                        .last_call_frame()
+                        .map(|frame| frame.upvalues.with_stack_len())
+                        .unwrap_or(0);
+
+                    self.enqueue_generator("resolve_with", op_span, |co| {
+                        resolve_with(
+                            co,
+                            ident.as_bstr().to_owned(),
+                            with_stack_len,
+                            closed_with_stack_len,
+                        )
+                    });
+
+                    return Ok(false);
+                }
+
+                OpCode::OpFinalise(StackIdx(idx)) => match &self.stack[frame.stack_offset + idx] {
+                    Value::Closure(_) => panic!("attempted to finalise a closure"),
+                    Value::Thunk(thunk) => thunk.finalise(&self.stack[frame.stack_offset..]),
+                    _ => panic!("attempted to finalise a non-thunk"),
+                },
+
+                OpCode::OpCoerceToString(kind) => {
+                    let value = self.stack_pop();
+                    let gen_span = frame.current_light_span();
+                    self.push_call_frame(span, frame);
+
+                    self.enqueue_generator("coerce_to_string", gen_span.clone(), |co| {
+                        value.coerce_to_string(co, kind, gen_span)
+                    });
+
+                    return Ok(false);
+                }
+
+                OpCode::OpInterpolate(Count(count)) => self.run_interpolate(&frame, count)?,
+
+                OpCode::OpValidateClosedFormals => {
+                    let formals = frame.lambda.formals.as_ref().expect(
+                        "OpValidateClosedFormals called within the frame of a lambda without formals",
+                    );
+
+                    let peeked = self.stack_peek(0);
+                    if peeked.is_catchable() {
+                        continue;
+                    }
+
+                    let args = peeked.to_attrs().with_span(&frame, self)?;
+                    for arg in args.keys() {
+                        if !formals.contains(arg) {
+                            return frame.error(
+                                self,
+                                ErrorKind::UnexpectedArgument {
+                                    arg: arg.clone(),
+                                    formals_span: formals.span,
+                                },
+                            );
+                        }
+                    }
+                }
+
+                OpCode::OpAdd => lifted_pop! {
+                    self(b, a) => {
+                        let gen_span = frame.current_light_span();
+                        self.push_call_frame(span, frame);
+
+                        // OpAdd can add not just numbers, but also string-like
+                        // things, which requires more VM logic. This operation is
+                        // evaluated in a generator frame.
+                        self.enqueue_generator("add_values", gen_span, |co| add_values(co, a, b));
+                        return Ok(false);
+                    }
+                },
+
+                OpCode::OpSub => lifted_pop! {
+                    self(b, a) => {
+                        let result = arithmetic_op!(&a, &b, -).with_span(&frame, self)?;
+                        self.stack.push(result);
+                    }
+                },
+
+                OpCode::OpMul => lifted_pop! {
+                    self(b, a) => {
+                        let result = arithmetic_op!(&a, &b, *).with_span(&frame, self)?;
+                        self.stack.push(result);
+                    }
+                },
+
+                OpCode::OpDiv => lifted_pop! {
+                    self(b, a) => {
+                        match b {
+                            Value::Integer(0) => return frame.error(self, ErrorKind::DivisionByZero),
+                            Value::Float(b) if b == 0.0_f64 => {
+                                return frame.error(self, ErrorKind::DivisionByZero)
+                            }
+                            _ => {}
+                        };
+
+                        let result = arithmetic_op!(&a, &b, /).with_span(&frame, self)?;
+                        self.stack.push(result);
+                    }
+                },
+
+                OpCode::OpNegate => match self.stack_pop() {
+                    Value::Integer(i) => self.stack.push(Value::Integer(-i)),
+                    Value::Float(f) => self.stack.push(Value::Float(-f)),
+                    Value::Catchable(cex) => self.stack.push(Value::Catchable(cex)),
+                    v => {
+                        return frame.error(
+                            self,
+                            ErrorKind::TypeError {
+                                expected: "number (either int or float)",
+                                actual: v.type_of(),
+                            },
+                        );
+                    }
+                },
+
+                OpCode::OpLess => cmp_op!(self, frame, span, <),
+                OpCode::OpLessOrEq => cmp_op!(self, frame, span, <=),
+                OpCode::OpMore => cmp_op!(self, frame, span, >),
+                OpCode::OpMoreOrEq => cmp_op!(self, frame, span, >=),
+
+                OpCode::OpFindFile => match self.stack_pop() {
+                    Value::UnresolvedPath(path) => {
+                        let resolved = self
+                            .nix_search_path
+                            .resolve(&self.io_handle, *path)
+                            .with_span(&frame, self)?;
+                        self.stack.push(resolved.into());
+                    }
+
+                    _ => panic!("tvix compiler bug: OpFindFile called on non-UnresolvedPath"),
+                },
+
+                OpCode::OpResolveHomePath => match self.stack_pop() {
+                    Value::UnresolvedPath(path) => {
+                        match dirs::home_dir() {
+                            None => {
+                                return frame.error(
+                                    self,
+                                    ErrorKind::RelativePathResolution(
+                                        "failed to determine home directory".into(),
+                                    ),
+                                );
+                            }
+                            Some(mut buf) => {
+                                buf.push(*path);
+                                self.stack.push(buf.into());
+                            }
+                        };
+                    }
+
+                    _ => {
+                        panic!("tvix compiler bug: OpResolveHomePath called on non-UnresolvedPath")
+                    }
+                },
+
+                OpCode::OpPushWith(StackIdx(idx)) => self.with_stack.push(frame.stack_offset + idx),
+
+                OpCode::OpPopWith => {
+                    self.with_stack.pop();
+                }
+
+                OpCode::OpAssertFail => {
+                    self.stack
+                        .push(Value::from(CatchableErrorKind::AssertionFailed));
+                }
+
+                // Data-carrying operands should never be executed,
+                // that is a critical error in the VM/compiler.
+                OpCode::DataStackIdx(_)
+                | OpCode::DataDeferredLocal(_)
+                | OpCode::DataUpvalueIdx(_)
+                | OpCode::DataCaptureWith => {
+                    panic!("Tvix bug: attempted to execute data-carrying operand")
+                }
+            }
+        }
+    }
+}
+
+/// Implementation of helper functions for the runtime logic above.
+impl<'o, IO> VM<'o, IO>
+where
+    IO: AsRef<dyn EvalIO> + 'static,
+{
+    pub(crate) fn stack_pop(&mut self) -> Value {
+        self.stack.pop().expect("runtime stack empty")
+    }
+
+    fn stack_peek(&self, offset: usize) -> &Value {
+        &self.stack[self.stack.len() - 1 - offset]
+    }
+
+    fn run_attrset(&mut self, frame: &CallFrame, count: usize) -> EvalResult<()> {
+        let attrs = NixAttrs::construct(count, self.stack.split_off(self.stack.len() - count * 2))
+            .with_span(frame, self)?
+            .map(Value::attrs)
+            .into();
+
+        self.stack.push(attrs);
+        Ok(())
+    }
+
+    /// Access the last call frame present in the frame stack.
+    fn last_call_frame(&self) -> Option<&CallFrame> {
+        for frame in self.frames.iter().rev() {
+            if let Frame::CallFrame { call_frame, .. } = frame {
+                return Some(call_frame);
+            }
+        }
+
+        None
+    }
+
+    /// Push an already constructed warning.
+    pub fn push_warning(&mut self, warning: EvalWarning) {
+        self.warnings.push(warning);
+    }
+
+    /// Emit a warning with the given WarningKind and the source span
+    /// of the current instruction.
+    pub fn emit_warning(&mut self, kind: WarningKind) {
+        self.push_warning(EvalWarning {
+            kind,
+            span: self.get_span(),
+        });
+    }
+
+    /// Interpolate string fragments by popping the specified number of
+    /// fragments of the stack, evaluating them to strings, and pushing
+    /// the concatenated result string back on the stack.
+    fn run_interpolate(&mut self, frame: &CallFrame, count: usize) -> EvalResult<()> {
+        let mut out = BString::default();
+        // Interpolation propagates the context and union them.
+        let mut context: NixContext = NixContext::new();
+
+        for i in 0..count {
+            let val = self.stack_pop();
+            if val.is_catchable() {
+                for _ in (i + 1)..count {
+                    self.stack.pop();
+                }
+                self.stack.push(val);
+                return Ok(());
+            }
+            let mut nix_string = val.to_contextful_str().with_span(frame, self)?;
+            out.push_str(nix_string.as_bstr());
+            if let Some(nix_string_ctx) = nix_string.context_mut() {
+                context = context.join(nix_string_ctx);
+            }
+        }
+
+        self.stack
+            .push(Value::String(NixString::new_context_from(context, out)));
+        Ok(())
+    }
+
+    /// Returns a reasonable light span for the current situation that the VM is
+    /// in.
+    pub fn reasonable_light_span(&self) -> LightSpan {
+        self.reasonable_span.clone()
+    }
+
+    /// Apply an argument from the stack to a builtin, and attempt to call it.
+    ///
+    /// All calls are tail-calls in Tvix, as every function application is a
+    /// separate thunk and OpCall is thus the last result in the thunk.
+    ///
+    /// Due to this, once control flow exits this function, the generator will
+    /// automatically be run by the VM.
+    fn call_builtin(&mut self, span: LightSpan, mut builtin: Builtin) -> EvalResult<()> {
+        let builtin_name = builtin.name();
+        self.observer.observe_enter_builtin(builtin_name);
+
+        builtin.apply_arg(self.stack_pop());
+
+        match builtin.call() {
+            // Partially applied builtin is just pushed back on the stack.
+            BuiltinResult::Partial(partial) => self.stack.push(Value::Builtin(partial)),
+
+            // Builtin is fully applied and the generator needs to be run by the VM.
+            BuiltinResult::Called(name, generator) => self.frames.push(Frame::Generator {
+                generator,
+                span,
+                name,
+                state: GeneratorState::Running,
+            }),
+        }
+
+        Ok(())
+    }
+
+    fn call_value(
+        &mut self,
+        span: LightSpan,
+        parent: Option<(LightSpan, CallFrame)>,
+        callable: Value,
+    ) -> EvalResult<()> {
+        match callable {
+            Value::Builtin(builtin) => self.call_builtin(span, builtin),
+            Value::Thunk(thunk) => self.call_value(span, parent, thunk.value().clone()),
+
+            Value::Closure(closure) => {
+                let lambda = closure.lambda();
+                self.observer.observe_tail_call(self.frames.len(), &lambda);
+
+                // The stack offset is always `stack.len() - arg_count`, and
+                // since this branch handles native Nix functions (which always
+                // take only a single argument and are curried), the offset is
+                // `stack_len - 1`.
+                let stack_offset = self.stack.len() - 1;
+
+                // Reenqueue the parent frame, which should only have
+                // `OpReturn` left. Not throwing it away leads to more
+                // useful error traces.
+                if let Some((parent_span, parent_frame)) = parent {
+                    self.push_call_frame(parent_span, parent_frame);
+                }
+
+                self.push_call_frame(
+                    span,
+                    CallFrame {
+                        lambda,
+                        upvalues: closure.upvalues(),
+                        ip: CodeIdx(0),
+                        stack_offset,
+                    },
+                );
+
+                Ok(())
+            }
+
+            // Attribute sets with a __functor attribute are callable.
+            val @ Value::Attrs(_) => {
+                if let Some((parent_span, parent_frame)) = parent {
+                    self.push_call_frame(parent_span, parent_frame);
+                }
+
+                self.enqueue_generator("__functor call", span, |co| call_functor(co, val));
+                Ok(())
+            }
+
+            val @ Value::Catchable(_) => {
+                // the argument that we tried to apply a catchable to
+                self.stack.pop();
+                // applying a `throw` to anything is still a `throw`, so we just
+                // push it back on the stack.
+                self.stack.push(val);
+                Ok(())
+            }
+
+            v => Err(ErrorKind::NotCallable(v.type_of())).with_span(&span, self),
+        }
+    }
+
+    /// Populate the upvalue fields of a thunk or closure under construction.
+    fn populate_upvalues(
+        &mut self,
+        frame: &mut CallFrame,
+        count: usize,
+        mut upvalues: impl DerefMut<Target = Upvalues>,
+    ) -> EvalResult<()> {
+        for _ in 0..count {
+            match frame.inc_ip() {
+                OpCode::DataStackIdx(StackIdx(stack_idx)) => {
+                    let idx = frame.stack_offset + stack_idx;
+
+                    let val = match self.stack.get(idx) {
+                        Some(val) => val.clone(),
+                        None => {
+                            return frame.error(
+                                self,
+                                ErrorKind::TvixBug {
+                                    msg: "upvalue to be captured was missing on stack",
+                                    metadata: Some(Rc::new(json!({
+                                        "ip": format!("{:#x}", frame.ip.0 - 1),
+                                        "stack_idx(relative)": stack_idx,
+                                        "stack_idx(absolute)": idx,
+                                    }))),
+                                },
+                            );
+                        }
+                    };
+
+                    upvalues.deref_mut().push(val);
+                }
+
+                OpCode::DataUpvalueIdx(upv_idx) => {
+                    upvalues.deref_mut().push(frame.upvalue(upv_idx).clone());
+                }
+
+                OpCode::DataDeferredLocal(idx) => {
+                    upvalues.deref_mut().push(Value::DeferredUpvalue(idx));
+                }
+
+                OpCode::DataCaptureWith => {
+                    // Start the captured with_stack off of the
+                    // current call frame's captured with_stack, ...
+                    let mut captured_with_stack = frame
+                        .upvalues
+                        .with_stack()
+                        .cloned()
+                        // ... or make an empty one if there isn't one already.
+                        .unwrap_or_else(|| Vec::with_capacity(self.with_stack.len()));
+
+                    for idx in &self.with_stack {
+                        captured_with_stack.push(self.stack[*idx].clone());
+                    }
+
+                    upvalues.deref_mut().set_with_stack(captured_with_stack);
+                }
+
+                _ => panic!("compiler error: missing closure operand"),
+            }
+        }
+
+        Ok(())
+    }
+}
+
+// TODO(amjoseph): de-asyncify this
+/// Resolve a dynamically bound identifier (through `with`) by looking
+/// for matching values in the with-stacks carried at runtime.
+async fn resolve_with(
+    co: GenCo,
+    ident: BString,
+    vm_with_len: usize,
+    upvalue_with_len: usize,
+) -> Result<Value, ErrorKind> {
+    /// Fetch and force a value on the with-stack from the VM.
+    async fn fetch_forced_with(co: &GenCo, idx: usize) -> Value {
+        match co.yield_(VMRequest::WithValue(idx)).await {
+            VMResponse::Value(value) => value,
+            msg => panic!(
+                "Tvix bug: VM responded with incorrect generator message: {}",
+                msg
+            ),
+        }
+    }
+
+    /// Fetch and force a value on the *captured* with-stack from the VM.
+    async fn fetch_captured_with(co: &GenCo, idx: usize) -> Value {
+        match co.yield_(VMRequest::CapturedWithValue(idx)).await {
+            VMResponse::Value(value) => value,
+            msg => panic!(
+                "Tvix bug: VM responded with incorrect generator message: {}",
+                msg
+            ),
+        }
+    }
+
+    for with_stack_idx in (0..vm_with_len).rev() {
+        // TODO(tazjin): is this branch still live with the current with-thunking?
+        let with = fetch_forced_with(&co, with_stack_idx).await;
+
+        if with.is_catchable() {
+            return Ok(with);
+        }
+
+        match with.to_attrs()?.select(&ident) {
+            None => continue,
+            Some(val) => return Ok(val.clone()),
+        }
+    }
+
+    for upvalue_with_idx in (0..upvalue_with_len).rev() {
+        let with = fetch_captured_with(&co, upvalue_with_idx).await;
+
+        if with.is_catchable() {
+            return Ok(with);
+        }
+
+        match with.to_attrs()?.select(&ident) {
+            None => continue,
+            Some(val) => return Ok(val.clone()),
+        }
+    }
+
+    Err(ErrorKind::UnknownDynamicVariable(ident.to_string()))
+}
+
+// TODO(amjoseph): de-asyncify this
+async fn add_values(co: GenCo, a: Value, b: Value) -> Result<Value, ErrorKind> {
+    // What we try to do is solely determined by the type of the first value!
+    let result = match (a, b) {
+        (Value::Path(p), v) => {
+            let mut path = p.into_os_string();
+            match generators::request_string_coerce(
+                &co,
+                v,
+                CoercionKind {
+                    strong: false,
+
+                    // Concatenating a Path with something else results in a
+                    // Path, so we don't need to import any paths (paths
+                    // imported by Nix always exist as a string, unless
+                    // converted by the user). In C++ Nix they even may not
+                    // contain any string context, the resulting error of such a
+                    // case can not be replicated by us.
+                    import_paths: false,
+                    // FIXME(raitobezarius): per https://b.tvl.fyi/issues/364, this is a usecase
+                    // for having a `reject_context: true` option here. This didn't occur yet in
+                    // nixpkgs during my evaluations, therefore, I skipped it.
+                },
+            )
+            .await
+            {
+                Ok(vs) => {
+                    path.push(vs.to_os_str()?);
+                    crate::value::canon_path(PathBuf::from(path)).into()
+                }
+                Err(c) => Value::Catchable(Box::new(c)),
+            }
+        }
+        (Value::String(s1), Value::String(s2)) => Value::String(s1.concat(&s2)),
+        (Value::String(s1), v) => generators::request_string_coerce(
+            &co,
+            v,
+            CoercionKind {
+                strong: false,
+                // Behaves the same as string interpolation
+                import_paths: true,
+            },
+        )
+        .await
+        .map(|s2| Value::String(s1.concat(&s2)))
+        .into(),
+        (a @ Value::Integer(_), b) | (a @ Value::Float(_), b) => arithmetic_op!(&a, &b, +)?,
+        (a, b) => {
+            let r1 = generators::request_string_coerce(
+                &co,
+                a,
+                CoercionKind {
+                    strong: false,
+                    import_paths: false,
+                },
+            )
+            .await;
+            let r2 = generators::request_string_coerce(
+                &co,
+                b,
+                CoercionKind {
+                    strong: false,
+                    import_paths: false,
+                },
+            )
+            .await;
+            match (r1, r2) {
+                (Ok(s1), Ok(s2)) => Value::String(s1.concat(&s2)),
+                (Err(c), _) => return Ok(Value::from(c)),
+                (_, Err(c)) => return Ok(Value::from(c)),
+            }
+        }
+    };
+
+    Ok(result)
+}
+
+/// The result of a VM's runtime evaluation.
+pub struct RuntimeResult {
+    pub value: Value,
+    pub warnings: Vec<EvalWarning>,
+}
+
+// TODO(amjoseph): de-asyncify this
+/// Generator that retrieves the final value from the stack, and deep-forces it
+/// before returning.
+async fn final_deep_force(co: GenCo) -> Result<Value, ErrorKind> {
+    let value = generators::request_stack_pop(&co).await;
+    Ok(generators::request_deep_force(&co, value).await)
+}
+
+pub fn run_lambda<IO>(
+    nix_search_path: NixSearchPath,
+    io_handle: IO,
+    observer: &mut dyn RuntimeObserver,
+    source: SourceCode,
+    globals: Rc<GlobalsMap>,
+    lambda: Rc<Lambda>,
+    strict: bool,
+) -> EvalResult<RuntimeResult>
+where
+    IO: AsRef<dyn EvalIO> + 'static,
+{
+    // Retain the top-level span of the expression in this lambda, as
+    // synthetic "calls" in deep_force will otherwise not have a span
+    // to fall back to.
+    //
+    // We exploit the fact that the compiler emits a final instruction
+    // with the span of the entire file for top-level expressions.
+    let root_span = lambda.chunk.get_span(CodeIdx(lambda.chunk.code.len() - 1));
+
+    let mut vm = VM::new(
+        nix_search_path,
+        io_handle,
+        observer,
+        source,
+        globals,
+        root_span.into(),
+    );
+
+    // When evaluating strictly, synthesise a frame that will instruct
+    // the VM to deep-force the final value before returning it.
+    if strict {
+        vm.enqueue_generator("final_deep_force", root_span.into(), final_deep_force);
+    }
+
+    vm.frames.push(Frame::CallFrame {
+        span: root_span.into(),
+        call_frame: CallFrame {
+            lambda,
+            upvalues: Rc::new(Upvalues::with_capacity(0)),
+            ip: CodeIdx(0),
+            stack_offset: 0,
+        },
+    });
+
+    vm.execute()
+}
diff --git a/tvix/eval/src/warnings.rs b/tvix/eval/src/warnings.rs
new file mode 100644
index 0000000000..f537aa913e
--- /dev/null
+++ b/tvix/eval/src/warnings.rs
@@ -0,0 +1,152 @@
+//! Implements warnings that are emitted in cases where code passed to
+//! Tvix exhibits problems that the user could address.
+
+use codemap_diagnostic::{ColorConfig, Diagnostic, Emitter, Level, SpanLabel, SpanStyle};
+
+use crate::SourceCode;
+
+#[derive(Debug)]
+pub enum WarningKind {
+    DeprecatedLiteralURL,
+    UselessInherit,
+    UnusedBinding,
+    ShadowedGlobal(&'static str),
+    DeprecatedLegacyLet,
+    InvalidNixPath(String),
+    UselessBoolOperation(&'static str),
+    DeadCode,
+    EmptyInherit,
+    EmptyLet,
+    ShadowedOutput(String),
+    SRIHashWrongPadding,
+
+    /// Tvix internal warning for features triggered by users that are
+    /// not actually implemented yet, but do not cause runtime failures.
+    NotImplemented(&'static str),
+}
+
+#[derive(Debug)]
+pub struct EvalWarning {
+    pub kind: WarningKind,
+    pub span: codemap::Span,
+}
+
+impl EvalWarning {
+    /// Render a fancy, human-readable output of this warning and
+    /// return it as a String. Note that this version of the output
+    /// does not include any colours or font styles.
+    pub fn fancy_format_str(&self, source: &SourceCode) -> String {
+        let mut out = vec![];
+        Emitter::vec(&mut out, Some(&*source.codemap())).emit(&[self.diagnostic(source)]);
+        String::from_utf8_lossy(&out).to_string()
+    }
+
+    /// Render a fancy, human-readable output of this warning and
+    /// print it to stderr. If rendered in a terminal that supports
+    /// colours and font styles, the output will include those.
+    pub fn fancy_format_stderr(&self, source: &SourceCode) {
+        Emitter::stderr(ColorConfig::Auto, Some(&*source.codemap()))
+            .emit(&[self.diagnostic(source)]);
+    }
+
+    /// Create the optional span label displayed as an annotation on
+    /// the underlined span of the warning.
+    fn span_label(&self) -> Option<String> {
+        match self.kind {
+            WarningKind::UnusedBinding | WarningKind::ShadowedGlobal(_) => {
+                Some("variable declared here".into())
+            }
+            _ => None,
+        }
+    }
+
+    /// Create the primary warning message displayed to users for a
+    /// warning.
+    fn message(&self, source: &SourceCode) -> String {
+        match self.kind {
+            WarningKind::DeprecatedLiteralURL => {
+                "URL literal syntax is deprecated, use a quoted string instead".to_string()
+            }
+
+            WarningKind::UselessInherit => {
+                "inherit does nothing (this variable already exists with the same value)"
+                    .to_string()
+            }
+
+            WarningKind::UnusedBinding => {
+                format!(
+                    "variable '{}' is declared, but never used:",
+                    source.source_slice(self.span)
+                )
+            }
+
+            WarningKind::ShadowedGlobal(name) => {
+                format!("declared variable '{}' shadows a built-in global!", name)
+            }
+
+            WarningKind::DeprecatedLegacyLet => {
+                "legacy `let` syntax used, please rewrite this as `let .. in ...`".to_string()
+            }
+
+            WarningKind::InvalidNixPath(ref err) => {
+                format!("invalid NIX_PATH resulted in a parse error: {}", err)
+            }
+
+            WarningKind::UselessBoolOperation(msg) => {
+                format!("useless operation on boolean: {}", msg)
+            }
+
+            WarningKind::DeadCode => "this code will never be executed".to_string(),
+
+            WarningKind::EmptyInherit => "this `inherit` statement is empty".to_string(),
+
+            WarningKind::EmptyLet => "this `let`-expression contains no bindings".to_string(),
+
+            WarningKind::ShadowedOutput(ref out) => format!(
+                "this derivation's environment shadows the output name {}",
+                out
+            ),
+            WarningKind::SRIHashWrongPadding => "SRI hash has wrong padding".to_string(),
+
+            WarningKind::NotImplemented(what) => {
+                format!("feature not yet implemented in tvix: {}", what)
+            }
+        }
+    }
+
+    /// Return the unique warning code for this variant which can be
+    /// used to refer users to documentation.
+    fn code(&self) -> &'static str {
+        match self.kind {
+            WarningKind::DeprecatedLiteralURL => "W001",
+            WarningKind::UselessInherit => "W002",
+            WarningKind::UnusedBinding => "W003",
+            WarningKind::ShadowedGlobal(_) => "W004",
+            WarningKind::DeprecatedLegacyLet => "W005",
+            WarningKind::InvalidNixPath(_) => "W006",
+            WarningKind::UselessBoolOperation(_) => "W007",
+            WarningKind::DeadCode => "W008",
+            WarningKind::EmptyInherit => "W009",
+            WarningKind::EmptyLet => "W010",
+            WarningKind::ShadowedOutput(_) => "W011",
+            WarningKind::SRIHashWrongPadding => "W012",
+
+            WarningKind::NotImplemented(_) => "W999",
+        }
+    }
+
+    fn diagnostic(&self, source: &SourceCode) -> Diagnostic {
+        let span_label = SpanLabel {
+            label: self.span_label(),
+            span: self.span,
+            style: SpanStyle::Primary,
+        };
+
+        Diagnostic {
+            level: Level::Warning,
+            message: self.message(source),
+            spans: vec![span_label],
+            code: Some(self.code().into()),
+        }
+    }
+}
diff --git a/tvix/eval/tests/nix_oracle.rs b/tvix/eval/tests/nix_oracle.rs
new file mode 100644
index 0000000000..5a5cc0a822
--- /dev/null
+++ b/tvix/eval/tests/nix_oracle.rs
@@ -0,0 +1,171 @@
+//! Tests which use upstream nix as an oracle to test evaluation against
+
+use std::{env, path::PathBuf, process::Command};
+
+use pretty_assertions::assert_eq;
+
+fn nix_binary_path() -> PathBuf {
+    env::var("NIX_INSTANTIATE_BINARY_PATH")
+        .unwrap_or_else(|_| "nix-instantiate".to_owned())
+        .into()
+}
+
+#[derive(Clone, Copy)]
+enum Strictness {
+    Lazy,
+    Strict,
+}
+
+fn nix_eval(expr: &str, strictness: Strictness) -> String {
+    let store_dir = tempfile::tempdir().unwrap();
+
+    let mut args = match strictness {
+        Strictness::Lazy => vec![],
+        Strictness::Strict => vec!["--strict"],
+    };
+    args.extend_from_slice(&["--eval", "-E"]);
+
+    let output = Command::new(nix_binary_path())
+        .args(&args[..])
+        .arg(format!("({expr})"))
+        .env(
+            "NIX_REMOTE",
+            format!(
+                "local?root={}",
+                store_dir
+                    .path()
+                    .canonicalize()
+                    .expect("valid path")
+                    .display()
+            ),
+        )
+        .output()
+        .unwrap();
+    if !output.status.success() {
+        panic!(
+            "nix eval {expr} failed!\n    stdout: {}\n    stderr: {}",
+            String::from_utf8_lossy(&output.stdout),
+            String::from_utf8_lossy(&output.stderr)
+        )
+    }
+
+    String::from_utf8(output.stdout).unwrap()
+}
+
+/// Compare the evaluation of the given nix expression in nix (using the
+/// `NIX_INSTANTIATE_BINARY_PATH` env var to resolve the `nix-instantiate` binary) and tvix, and
+/// assert that the result is identical
+#[track_caller]
+fn compare_eval(expr: &str, strictness: Strictness) {
+    let nix_result = nix_eval(expr, strictness);
+    let mut eval = tvix_eval::Evaluation::new_pure();
+    eval.strict = matches!(strictness, Strictness::Strict);
+    eval.io_handle = Box::new(tvix_eval::StdIO);
+
+    let tvix_result = eval
+        .evaluate(expr, None)
+        .value
+        .expect("tvix evaluation should succeed")
+        .to_string();
+
+    assert_eq!(nix_result.trim(), tvix_result);
+}
+
+/// Generate a suite of tests which call [`compare_eval`] on expressions, checking that nix and tvix
+/// return identical results.
+macro_rules! compare_eval_tests {
+    ($strictness:expr, {}) => {};
+    ($strictness:expr, {$(#[$meta:meta])* $test_name: ident($expr: expr); $($rest:tt)*}) => {
+        #[test]
+        $(#[$meta])*
+        fn $test_name() {
+            compare_eval($expr, $strictness);
+        }
+
+        compare_eval_tests!($strictness, { $($rest)* });
+    }
+}
+
+macro_rules! compare_strict_eval_tests {
+    ($($tests:tt)*) => {
+        compare_eval_tests!(Strictness::Strict, { $($tests)* });
+    }
+}
+
+macro_rules! compare_lazy_eval_tests {
+    ($($tests:tt)*) => {
+        compare_eval_tests!(Strictness::Lazy, { $($tests)* });
+    }
+}
+
+compare_strict_eval_tests! {
+    literal_int("1");
+    add_ints("1 + 1");
+    add_lists("[1 2] ++ [3 4]");
+    add_paths(r#"[
+        (./. + "/")
+        (./foo + "bar")
+        (let name = "bar"; in ./foo + name)
+        (let name = "bar"; in ./foo + "${name}")
+        (let name = "bar"; in ./foo + "/" + "${name}")
+        (let name = "bar"; in ./foo + "/${name}")
+        (./. + ./.)
+    ]"#);
+}
+
+// TODO(sterni): tvix_tests should gain support for something similar in the future,
+// but this requires messing with the path naming which would break compat with
+// C++ Nix's test suite
+compare_lazy_eval_tests! {
+    // Wrap every expression type supported by [Compiler::compile] in a list
+    // with lazy evaluation enabled, so we can check it being thunked or not
+    // against C++ Nix.
+    unthunked_literals_in_list("[ https://tvl.fyi 1 1.2 ]");
+    unthunked_path_in_list("[ ./nix_oracle.rs ]");
+    unthunked_string_literal_in_list("[ \":thonking:\" ]");
+    thunked_unary_ops_in_list("[ (!true) (-1) ]");
+    thunked_bin_ops_in_list(r#"
+      let
+        # Necessary to fool the optimiser for && and ||
+        true' = true;
+        false' = false;
+      in
+      [
+        (true' && false')
+        (true' || false')
+        (false -> true)
+        (40 + 2)
+        (43 - 1)
+        (21 * 2)
+        (126 / 3)
+        ({ } // { bar = null; })
+        (12 == 13)
+        (3 < 2)
+        (4 > 2)
+        (23 >= 42)
+        (33 <= 22)
+        ([ ] ++ [ ])
+        (42 != null)
+      ]
+    "#);
+    thunked_has_attrs_in_list("[ ({ } ? foo) ]");
+    thunked_list_in_list("[ [ 1 2 3 ] ]");
+    thunked_attr_set_in_list("[ { foo = null; } ]");
+    thunked_select_in_list("[ ({ foo = null; }.bar) ]");
+    thunked_assert_in_list("[ (assert false; 12) ]");
+    thunked_if_in_list("[ (if false then 13 else 12) ]");
+    thunked_let_in_list("[ (let foo = 12; in foo) ]");
+    thunked_with_in_list("[ (with { foo = 13; }; fooo) ]");
+    unthunked_identifier_in_list("let foo = 12; in [ foo ]");
+    thunked_lambda_in_list("[ (x: x) ]");
+    thunked_function_application_in_list("[ (builtins.add 1 2) ]");
+    thunked_legacy_let_in_list("[ (let { foo = 12; body = foo; }) ]");
+    unthunked_relative_path("[ ./foo ]");
+    unthunked_home_relative_path("[ ~/foo ]");
+    unthunked_absolute_path("[ /foo ]");
+
+    unthunked_formals_fallback_literal("({ foo ? 12 }: [ foo ]) { }");
+    unthunked_formals_fallback_string_literal("({ foo ? \"wiggly\" }: [ foo ]) { }");
+    thunked_formals_fallback_application("({ foo ? builtins.add 1 2 }: [ foo ]) { }");
+    thunked_formals_fallback_name_resolution_literal("({ foo ? bar, bar ? 12 }: [ foo ]) { }");
+}
diff --git a/tvix/glue/Cargo.toml b/tvix/glue/Cargo.toml
new file mode 100644
index 0000000000..0afdefeaaa
--- /dev/null
+++ b/tvix/glue/Cargo.toml
@@ -0,0 +1,53 @@
+[package]
+name = "tvix-glue"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+async-compression = { version = "0.4.9", features = ["tokio", "gzip", "bzip2", "xz"]}
+bstr = "1.6.0"
+bytes = "1.4.0"
+data-encoding = "2.3.3"
+futures = "0.3.30"
+magic = "0.16.2"
+nix-compat = { path = "../nix-compat" }
+pin-project = "1.1"
+reqwest = { version = "0.11.22", features = ["rustls-tls-native-roots"], default-features = false }
+tvix-build = { path = "../build", default-features = false, features = []}
+tvix-eval = { path = "../eval" }
+tvix-castore = { path = "../castore" }
+tvix-store = { path = "../store", default-features = false, features = []}
+tracing = "0.1.37"
+tokio = "1.28.0"
+tokio-tar = "0.3.1"
+tokio-util = { version = "0.7.9", features = ["io", "io-util", "compat"] }
+thiserror = "1.0.38"
+serde = "1.0.195"
+serde_json = "1.0"
+sha2 = "0.10.8"
+sha1 = "0.10.6"
+md-5 = "0.10.6"
+url = "2.4.0"
+walkdir = "2.4.0"
+
+[dependencies.wu-manber]
+git = "https://github.com/tvlfyi/wu-manber.git"
+
+[dev-dependencies]
+criterion = { version = "0.5", features = ["html_reports"] }
+hex-literal = "0.4.1"
+lazy_static = "1.4.0"
+nix = { version = "0.27.1", features = [ "fs" ] }
+pretty_assertions = "1.4.0"
+rstest = "0.19.0"
+tempfile = "3.8.1"
+
+[features]
+default = ["nix_tests"]
+# Enables running the Nix language test suite from the original C++
+# Nix implementation (at version 2.3) against Tvix.
+nix_tests = []
+
+[[bench]]
+name = "eval"
+harness = false
diff --git a/tvix/glue/benches/eval.rs b/tvix/glue/benches/eval.rs
new file mode 100644
index 0000000000..dfb4fabe44
--- /dev/null
+++ b/tvix/glue/benches/eval.rs
@@ -0,0 +1,77 @@
+use criterion::{black_box, criterion_group, criterion_main, Criterion};
+use lazy_static::lazy_static;
+use std::{env, rc::Rc, sync::Arc, time::Duration};
+use tvix_build::buildservice::DummyBuildService;
+use tvix_castore::{
+    blobservice::{BlobService, MemoryBlobService},
+    directoryservice::{DirectoryService, MemoryDirectoryService},
+};
+use tvix_eval::{builtins::impure_builtins, EvalIO};
+use tvix_glue::{
+    builtins::{add_derivation_builtins, add_fetcher_builtins, add_import_builtins},
+    configure_nix_path,
+    tvix_io::TvixIO,
+    tvix_store_io::TvixStoreIO,
+};
+use tvix_store::pathinfoservice::{MemoryPathInfoService, PathInfoService};
+
+lazy_static! {
+    static ref BLOB_SERVICE: Arc<dyn BlobService> = Arc::new(MemoryBlobService::default());
+    static ref DIRECTORY_SERVICE: Arc<dyn DirectoryService> =
+        Arc::new(MemoryDirectoryService::default());
+    static ref PATH_INFO_SERVICE: Arc<dyn PathInfoService> = Arc::new(MemoryPathInfoService::new(
+        BLOB_SERVICE.clone(),
+        DIRECTORY_SERVICE.clone(),
+    ));
+    static ref TOKIO_RUNTIME: tokio::runtime::Runtime = tokio::runtime::Runtime::new().unwrap();
+}
+
+fn interpret(code: &str) {
+    // TODO: this is a bit annoying.
+    // It'd be nice if we could set this up once and then run evaluate() with a
+    // piece of code. b/262
+
+    // We assemble a complete store in memory.
+    let tvix_store_io = Rc::new(TvixStoreIO::new(
+        BLOB_SERVICE.clone(),
+        DIRECTORY_SERVICE.clone(),
+        PATH_INFO_SERVICE.clone(),
+        Arc::<DummyBuildService>::default(),
+        TOKIO_RUNTIME.handle().clone(),
+    ));
+
+    let mut eval = tvix_eval::Evaluation::new(
+        Box::new(TvixIO::new(tvix_store_io.clone() as Rc<dyn EvalIO>)) as Box<dyn EvalIO>,
+        true,
+    );
+
+    eval.builtins.extend(impure_builtins());
+    add_derivation_builtins(&mut eval, Rc::clone(&tvix_store_io));
+    add_fetcher_builtins(&mut eval, Rc::clone(&tvix_store_io));
+    add_import_builtins(&mut eval, tvix_store_io);
+    configure_nix_path(
+        &mut eval,
+        // The benchmark requires TVIX_BENCH_NIX_PATH to be set, so barf out
+        // early, rather than benchmarking tvix returning an error.
+        &Some(env::var("TVIX_BENCH_NIX_PATH").expect("TVIX_BENCH_NIX_PATH must be set")),
+    );
+
+    let result = eval.evaluate(code, None);
+
+    assert!(result.errors.is_empty());
+}
+
+fn eval_nixpkgs(c: &mut Criterion) {
+    c.bench_function("hello outpath", |b| {
+        b.iter(|| {
+            interpret(black_box("(import <nixpkgs> {}).hello.outPath"));
+        })
+    });
+}
+
+criterion_group!(
+    name = benches;
+    config = Criterion::default().measurement_time(Duration::from_secs(30)).sample_size(10);
+    targets = eval_nixpkgs
+);
+criterion_main!(benches);
diff --git a/tvix/glue/default.nix b/tvix/glue/default.nix
new file mode 100644
index 0000000000..08f5c2228d
--- /dev/null
+++ b/tvix/glue/default.nix
@@ -0,0 +1,8 @@
+{ depot, pkgs, ... }:
+
+(depot.tvix.crates.workspaceMembers.tvix-glue.build.override {
+  runTests = true;
+  testPreRun = ''
+    export SSL_CERT_FILE=${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt;
+  '';
+})
diff --git a/tvix/glue/src/.skip-subtree b/tvix/glue/src/.skip-subtree
new file mode 100644
index 0000000000..a16a2afe1f
--- /dev/null
+++ b/tvix/glue/src/.skip-subtree
@@ -0,0 +1 @@
+Because of the derivation.nix file ...
diff --git a/tvix/glue/src/builtins/derivation.nix b/tvix/glue/src/builtins/derivation.nix
new file mode 100644
index 0000000000..9355cc3a96
--- /dev/null
+++ b/tvix/glue/src/builtins/derivation.nix
@@ -0,0 +1,36 @@
+# LGPL-2.1-or-later
+#
+# taken from: https://github.com/NixOS/nix/blob/master/src/libexpr/primops/derivation.nix
+#
+# TODO: rewrite in native Rust code
+
+/* This is the implementation of the ‘derivation’ builtin function.
+   It's actually a wrapper around the ‘derivationStrict’ primop. */
+
+drvAttrs @ { outputs ? [ "out" ], ... }:
+
+let
+
+  strict = derivationStrict drvAttrs;
+
+  commonAttrs = drvAttrs // (builtins.listToAttrs outputsList) //
+    {
+      all = map (x: x.value) outputsList;
+      inherit drvAttrs;
+    };
+
+  outputToAttrListElement = outputName:
+    {
+      name = outputName;
+      value = commonAttrs // {
+        outPath = builtins.getAttr outputName strict;
+        drvPath = strict.drvPath;
+        type = "derivation";
+        inherit outputName;
+      };
+    };
+
+  outputsList = map outputToAttrListElement outputs;
+
+in
+(builtins.head outputsList).value
diff --git a/tvix/glue/src/builtins/derivation.rs b/tvix/glue/src/builtins/derivation.rs
new file mode 100644
index 0000000000..a7742ae40a
--- /dev/null
+++ b/tvix/glue/src/builtins/derivation.rs
@@ -0,0 +1,552 @@
+//! Implements `builtins.derivation`, the core of what makes Nix build packages.
+use crate::builtins::DerivationError;
+use crate::known_paths::KnownPaths;
+use crate::tvix_store_io::TvixStoreIO;
+use bstr::BString;
+use nix_compat::derivation::{Derivation, Output};
+use nix_compat::nixhash;
+use nix_compat::store_path::{StorePath, StorePathRef};
+use std::collections::{btree_map, BTreeSet};
+use std::rc::Rc;
+use tvix_eval::builtin_macros::builtins;
+use tvix_eval::generators::{self, emit_warning_kind, GenCo};
+use tvix_eval::{
+    AddContext, ErrorKind, NixAttrs, NixContext, NixContextElement, Value, WarningKind,
+};
+
+// Constants used for strangely named fields in derivation inputs.
+const STRUCTURED_ATTRS: &str = "__structuredAttrs";
+const IGNORE_NULLS: &str = "__ignoreNulls";
+
+/// Populate the inputs of a derivation from the build references
+/// found when scanning the derivation's parameters and extracting their contexts.
+fn populate_inputs(drv: &mut Derivation, full_context: NixContext, known_paths: &KnownPaths) {
+    for element in full_context.iter() {
+        match element {
+            NixContextElement::Plain(source) => {
+                let sp = StorePathRef::from_absolute_path(source.as_bytes())
+                    .expect("invalid store path")
+                    .to_owned();
+                drv.input_sources.insert(sp);
+            }
+
+            NixContextElement::Single {
+                name,
+                derivation: derivation_str,
+            } => {
+                // TODO: b/264
+                // We assume derivations to be passed validated, so ignoring rest
+                // and expecting parsing is ok.
+                let (derivation, _rest) =
+                    StorePath::from_absolute_path_full(derivation_str).expect("valid store path");
+
+                #[cfg(debug_assertions)]
+                assert!(
+                    _rest.iter().next().is_none(),
+                    "Extra path not empty for {}",
+                    derivation_str
+                );
+
+                match drv.input_derivations.entry(derivation.clone()) {
+                    btree_map::Entry::Vacant(entry) => {
+                        entry.insert(BTreeSet::from([name.clone()]));
+                    }
+
+                    btree_map::Entry::Occupied(mut entry) => {
+                        entry.get_mut().insert(name.clone());
+                    }
+                }
+            }
+
+            NixContextElement::Derivation(drv_path) => {
+                let (derivation, _rest) =
+                    StorePath::from_absolute_path_full(drv_path).expect("valid store path");
+
+                #[cfg(debug_assertions)]
+                assert!(
+                    _rest.iter().next().is_none(),
+                    "Extra path not empty for {}",
+                    drv_path
+                );
+
+                // We need to know all the outputs *names* of that derivation.
+                let output_names = known_paths
+                    .get_drv_by_drvpath(&derivation)
+                    .expect("no known derivation associated to that derivation path")
+                    .outputs
+                    .keys();
+
+                // FUTUREWORK(performance): ideally, we should be able to clone
+                // cheaply those outputs rather than duplicate them all around.
+                match drv.input_derivations.entry(derivation.clone()) {
+                    btree_map::Entry::Vacant(entry) => {
+                        entry.insert(output_names.cloned().collect());
+                    }
+
+                    btree_map::Entry::Occupied(mut entry) => {
+                        entry.get_mut().extend(output_names.cloned());
+                    }
+                }
+
+                drv.input_sources.insert(derivation);
+            }
+        }
+    }
+}
+
+/// Populate the output configuration of a derivation based on the
+/// parameters passed to the call, configuring a fixed-output derivation output
+/// if necessary.
+///
+/// This function handles all possible combinations of the
+/// parameters, including invalid ones.
+///
+/// Due to the support for SRI hashes, and how these are passed along to
+/// builtins.derivation, outputHash and outputHashAlgo can have values which
+/// need to be further modified before constructing the Derivation struct.
+///
+/// If outputHashAlgo is an SRI hash, outputHashAlgo must either be an empty
+/// string, or the hash algorithm as specified in the (single) SRI (entry).
+/// SRI strings with multiple hash algorithms are not supported.
+///
+/// In case an SRI string was used, the (single) fixed output is populated
+/// with the hash algo name, and the hash digest is populated with the
+/// (lowercase) hex encoding of the digest.
+///
+/// These values are only rewritten for the outputs, not what's passed to env.
+///
+/// The return value may optionally contain a warning.
+fn handle_fixed_output(
+    drv: &mut Derivation,
+    hash_str: Option<String>,      // in nix: outputHash
+    hash_algo_str: Option<String>, // in nix: outputHashAlgo
+    hash_mode_str: Option<String>, // in nix: outputHashmode
+) -> Result<Option<WarningKind>, ErrorKind> {
+    // If outputHash is provided, ensure hash_algo_str is compatible.
+    // If outputHash is not provided, do nothing.
+    if let Some(hash_str) = hash_str {
+        // treat an empty algo as None
+        let hash_algo_str = match hash_algo_str {
+            Some(s) if s.is_empty() => None,
+            Some(s) => Some(s),
+            None => None,
+        };
+
+        // construct a NixHash.
+        let nixhash = nixhash::from_str(&hash_str, hash_algo_str.as_deref())
+            .map_err(DerivationError::InvalidOutputHash)?;
+        let algo = nixhash.algo();
+
+        // construct the fixed output.
+        drv.outputs.insert(
+            "out".to_string(),
+            Output {
+                path: None,
+                ca_hash: match hash_mode_str.as_deref() {
+                    None | Some("flat") => Some(nixhash::CAHash::Flat(nixhash)),
+                    Some("recursive") => Some(nixhash::CAHash::Nar(nixhash)),
+                    Some(other) => {
+                        return Err(DerivationError::InvalidOutputHashMode(other.to_string()))?
+                    }
+                },
+            },
+        );
+
+        // Peek at hash_str once more.
+        // If it was a SRI hash, but is not using the correct length, this means
+        // the padding was wrong. Emit a warning in that case.
+        let sri_prefix = format!("{}-", algo);
+        if let Some(rest) = hash_str.strip_prefix(&sri_prefix) {
+            if data_encoding::BASE64.encode_len(algo.digest_length()) != rest.len() {
+                return Ok(Some(WarningKind::SRIHashWrongPadding));
+            }
+        }
+    }
+    Ok(None)
+}
+
+#[builtins(state = "Rc<TvixStoreIO>")]
+pub(crate) mod derivation_builtins {
+    use std::collections::BTreeMap;
+
+    use crate::builtins::utils::{select_string, strong_importing_coerce_to_string};
+
+    use super::*;
+    use bstr::ByteSlice;
+    use nix_compat::store_path::hash_placeholder;
+    use tvix_eval::generators::Gen;
+    use tvix_eval::{NixContext, NixContextElement, NixString};
+
+    #[builtin("placeholder")]
+    async fn builtin_placeholder(co: GenCo, input: Value) -> Result<Value, ErrorKind> {
+        if input.is_catchable() {
+            return Ok(input);
+        }
+
+        let placeholder = hash_placeholder(
+            input
+                .to_str()
+                .context("looking at output name in builtins.placeholder")?
+                .to_str()?,
+        );
+
+        Ok(placeholder.into())
+    }
+
+    /// Strictly construct a Nix derivation from the supplied arguments.
+    ///
+    /// This is considered an internal function, users usually want to
+    /// use the higher-level `builtins.derivation` instead.
+    #[builtin("derivationStrict")]
+    async fn builtin_derivation_strict(
+        state: Rc<TvixStoreIO>,
+        co: GenCo,
+        input: Value,
+    ) -> Result<Value, ErrorKind> {
+        if input.is_catchable() {
+            return Ok(input);
+        }
+
+        let input = input.to_attrs()?;
+        let name = generators::request_force(&co, input.select_required("name")?.clone()).await;
+
+        if name.is_catchable() {
+            return Ok(name);
+        }
+
+        let name = name.to_str().context("determining derivation name")?;
+        if name.is_empty() {
+            return Err(ErrorKind::Abort("derivation has empty name".to_string()));
+        }
+        let name = name.to_str()?;
+
+        let mut drv = Derivation::default();
+        drv.outputs.insert("out".to_string(), Default::default());
+        let mut input_context = NixContext::new();
+
+        /// Inserts a key and value into the drv.environment BTreeMap, and fails if the
+        /// key did already exist before.
+        fn insert_env(
+            drv: &mut Derivation,
+            k: &str, /* TODO: non-utf8 env keys */
+            v: BString,
+        ) -> Result<(), DerivationError> {
+            if drv.environment.insert(k.into(), v).is_some() {
+                return Err(DerivationError::DuplicateEnvVar(k.into()));
+            }
+            Ok(())
+        }
+
+        // Check whether null attributes should be ignored or passed through.
+        let ignore_nulls = match input.select(IGNORE_NULLS) {
+            Some(b) => generators::request_force(&co, b.clone()).await.as_bool()?,
+            None => false,
+        };
+
+        // peek at the STRUCTURED_ATTRS argument.
+        // If it's set and true, provide a BTreeMap that gets populated while looking at the arguments.
+        // We need it to be a BTreeMap, so iteration order of keys is reproducible.
+        let mut structured_attrs: Option<BTreeMap<String, serde_json::Value>> =
+            match input.select(STRUCTURED_ATTRS) {
+                Some(b) => generators::request_force(&co, b.clone())
+                    .await
+                    .as_bool()?
+                    .then_some(Default::default()),
+                None => None,
+            };
+
+        // Look at the arguments passed to builtins.derivationStrict.
+        // Some set special fields in the Derivation struct, some change
+        // behaviour of other functionality.
+        for (arg_name, arg_value) in input.clone().into_iter_sorted() {
+            let arg_name = arg_name.to_str()?;
+            // force the current value.
+            let value = generators::request_force(&co, arg_value).await;
+
+            // filter out nulls if ignore_nulls is set.
+            if ignore_nulls && matches!(value, Value::Null) {
+                continue;
+            }
+
+            match arg_name {
+                // Command line arguments to the builder.
+                // These are only set in drv.arguments.
+                "args" => {
+                    for arg in value.to_list()? {
+                        match strong_importing_coerce_to_string(&co, arg).await {
+                            Err(cek) => return Ok(Value::from(cek)),
+                            Ok(s) => {
+                                input_context.mimic(&s);
+                                drv.arguments.push(s.to_str()?.to_owned())
+                            }
+                        }
+                    }
+                }
+
+                // If outputs is set, remove the original default `out` output,
+                // and replace it with the list of outputs.
+                "outputs" => {
+                    let outputs = value
+                        .to_list()
+                        .context("looking at the `outputs` parameter of the derivation")?;
+
+                    // Remove the original default `out` output.
+                    drv.outputs.clear();
+
+                    let mut output_names = vec![];
+
+                    for output in outputs {
+                        let output_name = generators::request_force(&co, output)
+                            .await
+                            .to_str()
+                            .context("determining output name")?;
+
+                        input_context.mimic(&output_name);
+
+                        // Populate drv.outputs
+                        if drv
+                            .outputs
+                            .insert(output_name.to_str()?.to_owned(), Default::default())
+                            .is_some()
+                        {
+                            Err(DerivationError::DuplicateOutput(
+                                output_name.to_str_lossy().into_owned(),
+                            ))?
+                        }
+                        output_names.push(output_name.to_str()?.to_owned());
+                    }
+
+                    match structured_attrs.as_mut() {
+                        // add outputs to the json itself (as a list of strings)
+                        Some(structured_attrs) => {
+                            structured_attrs.insert(arg_name.into(), output_names.into());
+                        }
+                        // add drv.environment["outputs"] as a space-separated list
+                        None => {
+                            insert_env(&mut drv, arg_name, output_names.join(" ").into())?;
+                        }
+                    }
+                    // drv.environment[$output_name] is added after the loop,
+                    // with whatever is in drv.outputs[$output_name].
+                }
+
+                // handle builder and system.
+                "builder" | "system" => {
+                    match strong_importing_coerce_to_string(&co, value).await {
+                        Err(cek) => return Ok(Value::from(cek)),
+                        Ok(val_str) => {
+                            input_context.mimic(&val_str);
+
+                            if arg_name == "builder" {
+                                drv.builder = val_str.to_str()?.to_owned();
+                            } else {
+                                drv.system = val_str.to_str()?.to_owned();
+                            }
+
+                            // Either populate drv.environment or structured_attrs.
+                            if let Some(ref mut structured_attrs) = structured_attrs {
+                                // No need to check for dups, we only iterate over every attribute name once
+                                structured_attrs.insert(
+                                    arg_name.to_owned(),
+                                    val_str.to_str()?.to_owned().into(),
+                                );
+                            } else {
+                                insert_env(&mut drv, arg_name, val_str.as_bytes().into())?;
+                            }
+                        }
+                    }
+                }
+
+                // Don't add STRUCTURED_ATTRS if enabled.
+                STRUCTURED_ATTRS if structured_attrs.is_some() => continue,
+                // IGNORE_NULLS is always skipped, even if it's not set to true.
+                IGNORE_NULLS => continue,
+
+                // all other args.
+                _ => {
+                    // In SA case, force and add to structured attrs.
+                    // In non-SA case, coerce to string and add to env.
+                    if let Some(ref mut structured_attrs) = structured_attrs {
+                        let val = generators::request_force(&co, value).await;
+                        if val.is_catchable() {
+                            return Ok(val);
+                        }
+
+                        let (val_json, mut context) = match val.into_contextful_json(&co).await? {
+                            Ok(v) => v,
+                            Err(cek) => return Ok(Value::from(cek)),
+                        };
+
+                        input_context = input_context.join(&mut context);
+
+                        // No need to check for dups, we only iterate over every attribute name once
+                        structured_attrs.insert(arg_name.to_owned(), val_json);
+                    } else {
+                        match strong_importing_coerce_to_string(&co, value).await {
+                            Err(cek) => return Ok(Value::from(cek)),
+                            Ok(val_str) => {
+                                input_context.mimic(&val_str);
+
+                                insert_env(&mut drv, arg_name, val_str.as_bytes().into())?;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        // end of per-argument loop
+
+        // Configure fixed-output derivations if required.
+        {
+            let output_hash = match select_string(&co, &input, "outputHash")
+                .await
+                .context("evaluating the `outputHash` parameter")?
+            {
+                Err(cek) => return Ok(Value::from(cek)),
+                Ok(s) => s,
+            };
+            let output_hash_algo = match select_string(&co, &input, "outputHashAlgo")
+                .await
+                .context("evaluating the `outputHashAlgo` parameter")?
+            {
+                Err(cek) => return Ok(Value::from(cek)),
+                Ok(s) => s,
+            };
+            let output_hash_mode = match select_string(&co, &input, "outputHashMode")
+                .await
+                .context("evaluating the `outputHashMode` parameter")?
+            {
+                Err(cek) => return Ok(Value::from(cek)),
+                Ok(s) => s,
+            };
+
+            if let Some(warning) =
+                handle_fixed_output(&mut drv, output_hash, output_hash_algo, output_hash_mode)?
+            {
+                emit_warning_kind(&co, warning).await;
+            }
+        }
+
+        // Each output name needs to exist in the environment, at this
+        // point initialised as an empty string, as the ATerm serialization of that is later
+        // used for the output path calculation (which will also update output
+        // paths post-calculation, both in drv.environment and drv.outputs)
+        for output in drv.outputs.keys() {
+            if drv
+                .environment
+                .insert(output.to_string(), String::new().into())
+                .is_some()
+            {
+                emit_warning_kind(&co, WarningKind::ShadowedOutput(output.to_string())).await;
+            }
+        }
+
+        if let Some(structured_attrs) = structured_attrs {
+            // configure __json
+            drv.environment.insert(
+                "__json".to_string(),
+                BString::from(serde_json::to_string(&structured_attrs)?),
+            );
+        }
+
+        let mut known_paths = state.as_ref().known_paths.borrow_mut();
+        populate_inputs(&mut drv, input_context, &known_paths);
+
+        // At this point, derivation fields are fully populated from
+        // eval data structures.
+        drv.validate(false)
+            .map_err(DerivationError::InvalidDerivation)?;
+
+        // Calculate the hash_derivation_modulo for the current derivation..
+        debug_assert!(
+            drv.outputs.values().all(|output| { output.path.is_none() }),
+            "outputs should still be unset"
+        );
+
+        // Mutate the Derivation struct and set output paths
+        drv.calculate_output_paths(
+            name,
+            // This one is still intermediate (so not added to known_paths),
+            // as the outputs are still unset.
+            &drv.hash_derivation_modulo(|drv_path| {
+                *known_paths
+                    .get_hash_derivation_modulo(&drv_path.to_owned())
+                    .unwrap_or_else(|| panic!("{} not found", drv_path))
+            }),
+        )
+        .map_err(DerivationError::InvalidDerivation)?;
+
+        let drv_path = drv
+            .calculate_derivation_path(name)
+            .map_err(DerivationError::InvalidDerivation)?;
+
+        // Assemble the attrset to return from this builtin.
+        let out = Value::Attrs(Box::new(NixAttrs::from_iter(
+            drv.outputs
+                .iter()
+                .map(|(name, output)| {
+                    (
+                        name.clone(),
+                        NixString::new_context_from(
+                            NixContextElement::Single {
+                                name: name.clone(),
+                                derivation: drv_path.to_absolute_path(),
+                            }
+                            .into(),
+                            output.path.as_ref().unwrap().to_absolute_path(),
+                        ),
+                    )
+                })
+                .chain(std::iter::once((
+                    "drvPath".to_owned(),
+                    NixString::new_context_from(
+                        NixContextElement::Derivation(drv_path.to_absolute_path()).into(),
+                        drv_path.to_absolute_path(),
+                    ),
+                ))),
+        )));
+
+        // Register the Derivation in known_paths.
+        known_paths.add_derivation(drv_path, drv);
+
+        Ok(out)
+    }
+
+    #[builtin("toFile")]
+    async fn builtin_to_file(co: GenCo, name: Value, content: Value) -> Result<Value, ErrorKind> {
+        if name.is_catchable() {
+            return Ok(name);
+        }
+
+        if content.is_catchable() {
+            return Ok(content);
+        }
+
+        let name = name
+            .to_str()
+            .context("evaluating the `name` parameter of builtins.toFile")?;
+        let content = content
+            .to_contextful_str()
+            .context("evaluating the `content` parameter of builtins.toFile")?;
+
+        if content.iter_derivation().count() > 0 || content.iter_single_outputs().count() > 0 {
+            return Err(ErrorKind::UnexpectedContext);
+        }
+
+        let path =
+            nix_compat::store_path::build_text_path(name.to_str()?, &content, content.iter_plain())
+                .map_err(|_e| {
+                    nix_compat::derivation::DerivationError::InvalidOutputName(
+                        name.to_str_lossy().into_owned(),
+                    )
+                })
+                .map_err(DerivationError::InvalidDerivation)?
+                .to_absolute_path();
+
+        let context: NixContext = NixContextElement::Plain(path.clone()).into();
+
+        // TODO: actually persist the file in the store at that path ...
+
+        Ok(Value::from(NixString::new_context_from(context, path)))
+    }
+}
diff --git a/tvix/glue/src/builtins/errors.rs b/tvix/glue/src/builtins/errors.rs
new file mode 100644
index 0000000000..f6d5745c56
--- /dev/null
+++ b/tvix/glue/src/builtins/errors.rs
@@ -0,0 +1,76 @@
+//! Contains errors that can occur during evaluation of builtins in this crate
+use nix_compat::{
+    nixhash::{self, NixHash},
+    store_path::BuildStorePathError,
+};
+use reqwest::Url;
+use std::rc::Rc;
+use thiserror::Error;
+use tvix_castore::import;
+
+/// Errors related to derivation construction
+#[derive(Debug, Error)]
+pub enum DerivationError {
+    #[error("an output with the name '{0}' is already defined")]
+    DuplicateOutput(String),
+    #[error("fixed-output derivations can only have the default `out`-output")]
+    ConflictingOutputTypes,
+    #[error("the environment variable '{0}' has already been set in this derivation")]
+    DuplicateEnvVar(String),
+    #[error("invalid derivation parameters: {0}")]
+    InvalidDerivation(#[from] nix_compat::derivation::DerivationError),
+    #[error("invalid output hash: {0}")]
+    InvalidOutputHash(#[from] nixhash::Error),
+    #[error("invalid output hash mode: '{0}', only 'recursive' and 'flat` are supported")]
+    InvalidOutputHashMode(String),
+}
+
+impl From<DerivationError> for tvix_eval::ErrorKind {
+    fn from(err: DerivationError) -> Self {
+        tvix_eval::ErrorKind::TvixError(Rc::new(err))
+    }
+}
+
+#[derive(Debug, Error)]
+pub enum FetcherError {
+    #[error("hash mismatch in file downloaded from {url}:\n  wanted: {wanted}\n     got: {got}")]
+    HashMismatch {
+        url: Url,
+        wanted: NixHash,
+        got: NixHash,
+    },
+
+    #[error("Invalid hash type '{0}' for fetcher")]
+    InvalidHashType(&'static str),
+
+    #[error("Unable to parse URL: {0}")]
+    InvalidUrl(#[from] url::ParseError),
+
+    #[error(transparent)]
+    Http(#[from] reqwest::Error),
+
+    #[error(transparent)]
+    Io(#[from] std::io::Error),
+
+    #[error(transparent)]
+    Import(#[from] tvix_castore::import::IngestionError<import::archive::Error>),
+
+    #[error("Error calculating store path for fetcher output: {0}")]
+    StorePath(#[from] BuildStorePathError),
+}
+
+/// Errors related to `builtins.path` and `builtins.filterSource`,
+/// a.k.a. "importing" builtins.
+#[derive(Debug, Error)]
+pub enum ImportError {
+    #[error("non-file '{0}' cannot be imported in 'flat' mode")]
+    FlatImportOfNonFile(String),
+    #[error("hash mismatch at ingestion of '{0}', expected: '{1}', got: '{2}'")]
+    HashMismatch(String, NixHash, NixHash),
+}
+
+impl From<ImportError> for tvix_eval::ErrorKind {
+    fn from(err: ImportError) -> Self {
+        tvix_eval::ErrorKind::TvixError(Rc::new(err))
+    }
+}
diff --git a/tvix/glue/src/builtins/fetchers.rs b/tvix/glue/src/builtins/fetchers.rs
new file mode 100644
index 0000000000..c7602c03e8
--- /dev/null
+++ b/tvix/glue/src/builtins/fetchers.rs
@@ -0,0 +1,186 @@
+//! Contains builtins that fetch paths from the Internet, or local filesystem.
+
+use super::utils::select_string;
+use crate::{
+    fetchers::{url_basename, Fetch},
+    tvix_store_io::TvixStoreIO,
+};
+use nix_compat::nixhash;
+use nix_compat::nixhash::NixHash;
+use std::rc::Rc;
+use tracing::info;
+use tvix_eval::builtin_macros::builtins;
+use tvix_eval::generators::Gen;
+use tvix_eval::generators::GenCo;
+use tvix_eval::{CatchableErrorKind, ErrorKind, Value};
+
+struct NixFetchArgs {
+    url_str: String,
+    name: Option<String>,
+    sha256: Option<[u8; 32]>,
+}
+
+// `fetchurl` and `fetchTarball` accept a single argument, which can either be the URL (as string),
+// or an attrset, where `url`, `sha256` and `name` keys are allowed.
+async fn extract_fetch_args(
+    co: &GenCo,
+    args: Value,
+) -> Result<Result<NixFetchArgs, CatchableErrorKind>, ErrorKind> {
+    if let Ok(url_str) = args.to_str() {
+        // Get the raw bytes, not the ToString repr.
+        let url_str =
+            String::from_utf8(url_str.as_bytes().to_vec()).map_err(|_| ErrorKind::Utf8)?;
+        return Ok(Ok(NixFetchArgs {
+            url_str,
+            name: None,
+            sha256: None,
+        }));
+    }
+
+    let attrs = args.to_attrs().map_err(|_| ErrorKind::TypeError {
+        expected: "attribute set or contextless string",
+        actual: args.type_of(),
+    })?;
+
+    let url_str = match select_string(co, &attrs, "url").await? {
+        Ok(s) => s.ok_or_else(|| ErrorKind::AttributeNotFound { name: "url".into() })?,
+        Err(cek) => return Ok(Err(cek)),
+    };
+    let name = match select_string(co, &attrs, "name").await? {
+        Ok(s) => s,
+        Err(cek) => return Ok(Err(cek)),
+    };
+    let sha256_str = match select_string(co, &attrs, "sha256").await? {
+        Ok(s) => s,
+        Err(cek) => return Ok(Err(cek)),
+    };
+
+    // TODO: disallow other attrset keys, to match Nix' behaviour.
+
+    // parse the sha256 string into a digest.
+    let sha256 = match sha256_str {
+        Some(sha256_str) => {
+            let nixhash = nixhash::from_str(&sha256_str, Some("sha256"))
+                // TODO: DerivationError::InvalidOutputHash should be moved to ErrorKind::InvalidHash and used here instead
+                .map_err(|e| ErrorKind::TvixError(Rc::new(e)))?;
+
+            Some(nixhash.digest_as_bytes().try_into().expect("is sha256"))
+        }
+        None => None,
+    };
+
+    Ok(Ok(NixFetchArgs {
+        url_str,
+        name,
+        sha256,
+    }))
+}
+
+#[allow(unused_variables)] // for the `state` arg, for now
+#[builtins(state = "Rc<TvixStoreIO>")]
+pub(crate) mod fetcher_builtins {
+    use crate::builtins::FetcherError;
+    use url::Url;
+
+    use super::*;
+
+    /// Consumes a fetch.
+    /// If there is enough info to calculate the store path without fetching,
+    /// queue the fetch to be fetched lazily, and return the store path.
+    /// If there's not enough info to calculate it, do the fetch now, and then
+    /// return the store path.
+    fn fetch_lazy(state: Rc<TvixStoreIO>, name: String, fetch: Fetch) -> Result<Value, ErrorKind> {
+        match fetch
+            .store_path(&name)
+            .map_err(|e| ErrorKind::TvixError(Rc::new(e)))?
+        {
+            Some(store_path) => {
+                // Move the fetch to KnownPaths, so it can be actually fetched later.
+                let sp = state
+                    .known_paths
+                    .borrow_mut()
+                    .add_fetch(fetch, &name)
+                    .expect("Tvix bug: should only fail if the store path cannot be calculated");
+
+                debug_assert_eq!(
+                    sp, store_path,
+                    "calculated store path by KnownPaths should match"
+                );
+
+                // Emit the calculated Store Path.
+                Ok(Value::Path(Box::new(store_path.to_absolute_path().into())))
+            }
+            None => {
+                // If we don't have enough info, do the fetch now.
+                info!(?fetch, "triggering required fetch");
+
+                let (store_path, _root_node) = state
+                    .tokio_handle
+                    .block_on(async { state.fetcher.ingest_and_persist(&name, fetch).await })
+                    .map_err(|e| ErrorKind::TvixError(Rc::new(e)))?;
+
+                Ok(Value::Path(Box::new(store_path.to_absolute_path().into())))
+            }
+        }
+    }
+
+    #[builtin("fetchurl")]
+    async fn builtin_fetchurl(
+        state: Rc<TvixStoreIO>,
+        co: GenCo,
+        args: Value,
+    ) -> Result<Value, ErrorKind> {
+        let args = match extract_fetch_args(&co, args).await? {
+            Ok(args) => args,
+            Err(cek) => return Ok(Value::from(cek)),
+        };
+
+        // Derive the name from the URL basename if not set explicitly.
+        let name = args
+            .name
+            .unwrap_or_else(|| url_basename(&args.url_str).to_owned());
+
+        // Parse the URL.
+        let url = Url::parse(&args.url_str)
+            .map_err(|e| ErrorKind::TvixError(Rc::new(FetcherError::InvalidUrl(e))))?;
+
+        fetch_lazy(
+            state,
+            name,
+            Fetch::URL(url, args.sha256.map(NixHash::Sha256)),
+        )
+    }
+
+    #[builtin("fetchTarball")]
+    async fn builtin_fetch_tarball(
+        state: Rc<TvixStoreIO>,
+        co: GenCo,
+        args: Value,
+    ) -> Result<Value, ErrorKind> {
+        let args = match extract_fetch_args(&co, args).await? {
+            Ok(args) => args,
+            Err(cek) => return Ok(Value::from(cek)),
+        };
+
+        // Name defaults to "source" if not set explicitly.
+        const DEFAULT_NAME_FETCH_TARBALL: &str = "source";
+        let name = args
+            .name
+            .unwrap_or_else(|| DEFAULT_NAME_FETCH_TARBALL.to_owned());
+
+        // Parse the URL.
+        let url = Url::parse(&args.url_str)
+            .map_err(|e| ErrorKind::TvixError(Rc::new(FetcherError::InvalidUrl(e))))?;
+
+        fetch_lazy(state, name, Fetch::Tarball(url, args.sha256))
+    }
+
+    #[builtin("fetchGit")]
+    async fn builtin_fetch_git(
+        state: Rc<TvixStoreIO>,
+        co: GenCo,
+        args: Value,
+    ) -> Result<Value, ErrorKind> {
+        Err(ErrorKind::NotImplemented("fetchGit"))
+    }
+}
diff --git a/tvix/glue/src/builtins/import.rs b/tvix/glue/src/builtins/import.rs
new file mode 100644
index 0000000000..219695b69f
--- /dev/null
+++ b/tvix/glue/src/builtins/import.rs
@@ -0,0 +1,287 @@
+//! Implements builtins used to import paths in the store.
+
+use crate::builtins::errors::ImportError;
+use std::path::Path;
+use tvix_castore::import::ingest_entries;
+use tvix_eval::{
+    builtin_macros::builtins,
+    generators::{self, GenCo},
+    ErrorKind, EvalIO, Value,
+};
+
+use std::rc::Rc;
+
+async fn filtered_ingest(
+    state: Rc<TvixStoreIO>,
+    co: GenCo,
+    path: &Path,
+    filter: Option<&Value>,
+) -> Result<tvix_castore::proto::node::Node, ErrorKind> {
+    let mut entries: Vec<walkdir::DirEntry> = vec![];
+    let mut it = walkdir::WalkDir::new(path)
+        .follow_links(false)
+        .follow_root_links(false)
+        .contents_first(false)
+        .into_iter();
+
+    // Skip root node.
+    entries.push(
+        it.next()
+            .ok_or_else(|| ErrorKind::IO {
+                path: Some(path.to_path_buf()),
+                error: std::io::Error::new(std::io::ErrorKind::NotFound, "No root node emitted")
+                    .into(),
+            })?
+            .map_err(|err| ErrorKind::IO {
+                path: Some(path.to_path_buf()),
+                error: std::io::Error::from(err).into(),
+            })?,
+    );
+
+    while let Some(entry) = it.next() {
+        // Entry could be a NotFound, if the root path specified does not exist.
+        let entry = entry.map_err(|err| ErrorKind::IO {
+            path: err.path().map(|p| p.to_path_buf()),
+            error: std::io::Error::from(err).into(),
+        })?;
+
+        // As per Nix documentation `:doc builtins.filterSource`.
+        let file_type = if entry.file_type().is_dir() {
+            "directory"
+        } else if entry.file_type().is_file() {
+            "regular"
+        } else if entry.file_type().is_symlink() {
+            "symlink"
+        } else {
+            "unknown"
+        };
+
+        let should_keep: bool = if let Some(filter) = filter {
+            generators::request_force(
+                &co,
+                generators::request_call_with(
+                    &co,
+                    filter.clone(),
+                    [
+                        Value::String(entry.path().as_os_str().as_encoded_bytes().into()),
+                        Value::String(file_type.into()),
+                    ],
+                )
+                .await,
+            )
+            .await
+            .as_bool()?
+        } else {
+            true
+        };
+
+        if !should_keep {
+            if file_type == "directory" {
+                it.skip_current_dir();
+            }
+            continue;
+        }
+
+        entries.push(entry);
+    }
+
+    let dir_entries = entries.into_iter().rev().map(Ok);
+
+    state.tokio_handle.block_on(async {
+        let entries = tvix_castore::import::fs::dir_entries_to_ingestion_stream(
+            &state.blob_service,
+            dir_entries,
+            path,
+        );
+        ingest_entries(&state.directory_service, entries)
+            .await
+            .map_err(|e| ErrorKind::IO {
+                path: Some(path.to_path_buf()),
+                error: Rc::new(std::io::Error::new(std::io::ErrorKind::Other, e)),
+            })
+    })
+}
+
+#[builtins(state = "Rc<TvixStoreIO>")]
+mod import_builtins {
+    use std::rc::Rc;
+
+    use super::*;
+
+    use nix_compat::nixhash::{CAHash, NixHash};
+    use tvix_eval::generators::Gen;
+    use tvix_eval::{generators::GenCo, ErrorKind, Value};
+    use tvix_eval::{NixContextElement, NixString};
+
+    use tvix_castore::B3Digest;
+
+    use crate::tvix_store_io::TvixStoreIO;
+
+    #[builtin("path")]
+    async fn builtin_path(
+        state: Rc<TvixStoreIO>,
+        co: GenCo,
+        args: Value,
+    ) -> Result<Value, ErrorKind> {
+        let args = args.to_attrs()?;
+        let path = args.select_required("path")?;
+        let path = generators::request_force(&co, path.clone())
+            .await
+            .to_path()?;
+        let name: String = if let Some(name) = args.select("name") {
+            generators::request_force(&co, name.clone())
+                .await
+                .to_str()?
+                .as_bstr()
+                .to_string()
+        } else {
+            tvix_store::import::path_to_name(&path)
+                .expect("Failed to derive the default name out of the path")
+                .to_string()
+        };
+        let filter = args.select("filter");
+        let recursive_ingestion = args
+            .select("recursive")
+            .map(|r| r.as_bool())
+            .transpose()?
+            .unwrap_or(true); // Yes, yes, Nix, by default, puts `recursive = true;`.
+        let expected_sha256 = args
+            .select("sha256")
+            .map(|h| {
+                h.to_str().and_then(|expected| {
+                    let expected = expected.into_bstring().to_string();
+                    // TODO: ensure that we fail if this is not a valid str.
+                    nix_compat::nixhash::from_str(&expected, None).map_err(|_err| {
+                        // TODO: a better error would be nice, we use
+                        // DerivationError::InvalidOutputHash usually for derivation construction.
+                        // This is not a derivation construction, should we move it outside and
+                        // generalize?
+                        ErrorKind::TypeError {
+                            expected: "sha256",
+                            actual: "not a sha256",
+                        }
+                    })
+                })
+            })
+            .transpose()?;
+
+        // FUTUREWORK(performance): this opens the file instead of using a stat-like
+        // system call to the file.
+        if !recursive_ingestion && state.open(path.as_ref()).is_err() {
+            Err(ImportError::FlatImportOfNonFile(
+                path.to_string_lossy().to_string(),
+            ))?;
+        }
+
+        let root_node = filtered_ingest(state.clone(), co, path.as_ref(), filter).await?;
+        let ca: CAHash = if recursive_ingestion {
+            CAHash::Nar(NixHash::Sha256(state.tokio_handle.block_on(async {
+                Ok::<_, tvix_eval::ErrorKind>(
+                    state
+                        .path_info_service
+                        .as_ref()
+                        .calculate_nar(&root_node)
+                        .await
+                        .map_err(|e| ErrorKind::TvixError(Rc::new(e)))?
+                        .1,
+                )
+            })?))
+        } else {
+            let digest: B3Digest = match root_node {
+                tvix_castore::proto::node::Node::File(ref fnode) => {
+                    // It's already validated.
+                    fnode.digest.clone().try_into().unwrap()
+                }
+                // We cannot hash anything else than file in flat import mode.
+                _ => {
+                    return Err(ImportError::FlatImportOfNonFile(
+                        path.to_string_lossy().to_string(),
+                    )
+                    .into())
+                }
+            };
+
+            // FUTUREWORK: avoid hashing again.
+            CAHash::Flat(NixHash::Sha256(
+                state
+                    .tokio_handle
+                    .block_on(async { state.blob_to_sha256_hash(digest).await })?,
+            ))
+        };
+
+        let obtained_hash = ca.hash().clone().into_owned();
+        let (path_info, _hash, output_path) = state.tokio_handle.block_on(async {
+            state
+                .node_to_path_info(name.as_ref(), path.as_ref(), ca, root_node)
+                .await
+        })?;
+
+        if let Some(expected_sha256) = expected_sha256 {
+            if obtained_hash != expected_sha256 {
+                Err(ImportError::HashMismatch(
+                    path.to_string_lossy().to_string(),
+                    expected_sha256,
+                    obtained_hash,
+                ))?;
+            }
+        }
+
+        let _: tvix_store::proto::PathInfo = state.tokio_handle.block_on(async {
+            // This is necessary to cause the coercion of the error type.
+            Ok::<_, std::io::Error>(state.path_info_service.as_ref().put(path_info).await?)
+        })?;
+
+        // We need to attach context to the final output path.
+        let outpath = output_path.to_absolute_path();
+
+        Ok(
+            NixString::new_context_from(NixContextElement::Plain(outpath.clone()).into(), outpath)
+                .into(),
+        )
+    }
+
+    #[builtin("filterSource")]
+    async fn builtin_filter_source(
+        state: Rc<TvixStoreIO>,
+        co: GenCo,
+        #[lazy] filter: Value,
+        path: Value,
+    ) -> Result<Value, ErrorKind> {
+        let p = path.to_path()?;
+        let root_node = filtered_ingest(Rc::clone(&state), co, &p, Some(&filter)).await?;
+        let name = tvix_store::import::path_to_name(&p)?;
+
+        let outpath = state
+            .tokio_handle
+            .block_on(async {
+                let (_, nar_sha256) = state
+                    .path_info_service
+                    .as_ref()
+                    .calculate_nar(&root_node)
+                    .await?;
+
+                state
+                    .register_node_in_path_info_service(
+                        name,
+                        &p,
+                        CAHash::Nar(NixHash::Sha256(nar_sha256)),
+                        root_node,
+                    )
+                    .await
+            })
+            .map_err(|err| ErrorKind::IO {
+                path: Some(p.to_path_buf()),
+                error: err.into(),
+            })?
+            .to_absolute_path();
+
+        Ok(
+            NixString::new_context_from(NixContextElement::Plain(outpath.clone()).into(), outpath)
+                .into(),
+        )
+    }
+}
+
+pub use import_builtins::builtins as import_builtins;
+
+use crate::tvix_store_io::TvixStoreIO;
diff --git a/tvix/glue/src/builtins/mod.rs b/tvix/glue/src/builtins/mod.rs
new file mode 100644
index 0000000000..0c7bcc880a
--- /dev/null
+++ b/tvix/glue/src/builtins/mod.rs
@@ -0,0 +1,797 @@
+//! Contains builtins that deal with the store or builder.
+
+use std::rc::Rc;
+
+use crate::tvix_store_io::TvixStoreIO;
+
+mod derivation;
+mod errors;
+mod fetchers;
+mod import;
+mod utils;
+
+pub use errors::{DerivationError, FetcherError, ImportError};
+
+/// Adds derivation-related builtins to the passed [tvix_eval::Evaluation].
+///
+/// These are `derivation` and `derivationStrict`.
+///
+/// As they need to interact with `known_paths`, we also need to pass in
+/// `known_paths`.
+pub fn add_derivation_builtins<IO>(eval: &mut tvix_eval::Evaluation<IO>, io: Rc<TvixStoreIO>) {
+    eval.builtins
+        .extend(derivation::derivation_builtins::builtins(Rc::clone(&io)));
+
+    // Add the actual `builtins.derivation` from compiled Nix code
+    eval.src_builtins
+        .push(("derivation", include_str!("derivation.nix")));
+}
+
+/// Adds fetcher builtins to the passed [tvix_eval::Evaluation]:
+///
+/// * `fetchurl`
+/// * `fetchTarball`
+/// * `fetchGit`
+pub fn add_fetcher_builtins<IO>(eval: &mut tvix_eval::Evaluation<IO>, io: Rc<TvixStoreIO>) {
+    eval.builtins
+        .extend(fetchers::fetcher_builtins::builtins(Rc::clone(&io)));
+}
+
+/// Adds import-related builtins to the passed [tvix_eval::Evaluation].
+///
+/// These are `filterSource` and `path`
+///
+/// As they need to interact with the store implementation, we pass [`TvixStoreIO`].
+pub fn add_import_builtins<IO>(eval: &mut tvix_eval::Evaluation<IO>, io: Rc<TvixStoreIO>) {
+    eval.builtins.extend(import::import_builtins(io));
+
+    // TODO(raitobezarius): evaluate expressing filterSource as Nix code using path (b/372)
+}
+
+#[cfg(test)]
+mod tests {
+    use std::{fs, rc::Rc, sync::Arc};
+
+    use crate::tvix_store_io::TvixStoreIO;
+
+    use super::{add_derivation_builtins, add_fetcher_builtins, add_import_builtins};
+    use nix_compat::store_path::hash_placeholder;
+    use rstest::rstest;
+    use tempfile::TempDir;
+    use tvix_build::buildservice::DummyBuildService;
+    use tvix_eval::{EvalIO, EvaluationResult};
+    use tvix_store::utils::construct_services;
+
+    /// evaluates a given nix expression and returns the result.
+    /// Takes care of setting up the evaluator so it knows about the
+    // `derivation` builtin.
+    fn eval(str: &str) -> EvaluationResult {
+        // We assemble a complete store in memory.
+        let runtime = tokio::runtime::Runtime::new().expect("Failed to build a Tokio runtime");
+        let (blob_service, directory_service, path_info_service) = runtime
+            .block_on(async { construct_services("memory://", "memory://", "memory://").await })
+            .expect("Failed to construct store services in memory");
+
+        let io = Rc::new(TvixStoreIO::new(
+            blob_service,
+            directory_service,
+            path_info_service.into(),
+            Arc::<DummyBuildService>::default(),
+            runtime.handle().clone(),
+        ));
+
+        let mut eval = tvix_eval::Evaluation::new(io.clone() as Rc<dyn EvalIO>, false);
+
+        add_derivation_builtins(&mut eval, Rc::clone(&io));
+        add_fetcher_builtins(&mut eval, Rc::clone(&io));
+        add_import_builtins(&mut eval, io);
+
+        // run the evaluation itself.
+        eval.evaluate(str, None)
+    }
+
+    #[test]
+    fn derivation() {
+        let result = eval(
+            r#"(derivation { name = "foo"; builder = "/bin/sh"; system = "x86_64-linux";}).outPath"#,
+        );
+
+        assert!(result.errors.is_empty(), "expect evaluation to succeed");
+        let value = result.value.expect("must be some");
+
+        match value {
+            tvix_eval::Value::String(s) => {
+                assert_eq!(*s, "/nix/store/xpcvxsx5sw4rbq666blz6sxqlmsqphmr-foo",);
+            }
+            _ => panic!("unexpected value type: {:?}", value),
+        }
+    }
+
+    /// a derivation with an empty name is an error.
+    #[test]
+    fn derivation_empty_name_fail() {
+        let result = eval(
+            r#"(derivation { name = ""; builder = "/bin/sh"; system = "x86_64-linux";}).outPath"#,
+        );
+
+        assert!(!result.errors.is_empty(), "expect evaluation to fail");
+    }
+
+    /// construct some calls to builtins.derivation and compare produced output
+    /// paths.
+    #[rstest]
+    #[case::r_sha256(r#"(builtins.derivation { name = "foo"; builder = "/bin/sh"; system = "x86_64-linux"; outputHashMode = "recursive"; outputHashAlgo = "sha256"; outputHash = "sha256-Q3QXOoy+iN4VK2CflvRulYvPZXYgF0dO7FoF7CvWFTA="; }).outPath"#, "/nix/store/17wgs52s7kcamcyin4ja58njkf91ipq8-foo")]
+    #[case::r_sha256_other_name(r#"(builtins.derivation { name = "foo2"; builder = "/bin/sh"; system = "x86_64-linux"; outputHashMode = "recursive"; outputHashAlgo = "sha256"; outputHash = "sha256-Q3QXOoy+iN4VK2CflvRulYvPZXYgF0dO7FoF7CvWFTA="; }).outPath"#, "/nix/store/gi0p8vd635vpk1nq029cz3aa3jkhar5k-foo2")]
+    #[case::r_sha1(r#"(builtins.derivation { name = "foo"; builder = "/bin/sh"; system = "x86_64-linux"; outputHashMode = "recursive"; outputHashAlgo = "sha1"; outputHash = "sha1-VUCRC+16gU5lcrLYHlPSUyx0Y/Q="; }).outPath"#, "/nix/store/p5sammmhpa84ama7ymkbgwwzrilva24x-foo")]
+    #[case::r_md5(r#"(builtins.derivation { name = "foo"; builder = "/bin/sh"; system = "x86_64-linux"; outputHashMode = "recursive"; outputHashAlgo = "md5"; outputHash = "md5-07BzhNET7exJ6qYjitX/AA=="; }).outPath"#, "/nix/store/gmmxgpy1jrzs86r5y05wy6wiy2m15xgi-foo")]
+    #[case::r_sha512(r#"(builtins.derivation { name = "foo"; builder = "/bin/sh"; system = "x86_64-linux"; outputHashMode = "recursive"; outputHashAlgo = "sha512"; outputHash = "sha512-DPkYCnZKuoY6Z7bXLwkYvBMcZ3JkLLLc5aNPCnAvlHDdwr8SXBIZixmVwjPDS0r9NGxUojNMNQqUilG26LTmtg=="; }).outPath"#, "/nix/store/lfi2bfyyap88y45mfdwi4j99gkaxaj19-foo")]
+    #[case::r_sha256_base16(r#"(builtins.derivation { name = "foo"; builder = "/bin/sh"; system = "x86_64-linux"; outputHashMode = "recursive"; outputHashAlgo = "sha256"; outputHash = "4374173a8cbe88de152b609f96f46e958bcf65762017474eec5a05ec2bd61530"; }).outPath"#, "/nix/store/17wgs52s7kcamcyin4ja58njkf91ipq8-foo")]
+    #[case::r_sha256_nixbase32(r#"(builtins.derivation { name = "foo"; builder = "/bin/sh"; system = "x86_64-linux"; outputHashMode = "recursive"; outputHashAlgo = "sha256"; outputHash = "0c0msqmyq1asxi74f5r0frjwz2wmdvs9d7v05caxx25yihx1fx23"; }).outPath"#, "/nix/store/17wgs52s7kcamcyin4ja58njkf91ipq8-foo")]
+    #[case::r_sha256_base64(r#"(builtins.derivation { name = "foo"; builder = "/bin/sh"; system = "x86_64-linux"; outputHashMode = "recursive"; outputHashAlgo = "sha256"; outputHash = "Q3QXOoy+iN4VK2CflvRulYvPZXYgF0dO7FoF7CvWFTA="; }).outPath"#, "/nix/store/17wgs52s7kcamcyin4ja58njkf91ipq8-foo")]
+    #[case::r_sha256_base64_nopad(r#"(builtins.derivation { name = "foo"; builder = "/bin/sh"; system = "x86_64-linux"; outputHashMode = "recursive"; outputHashAlgo = "sha256"; outputHash = "sha256-fgIr3TyFGDAXP5+qoAaiMKDg/a1MlT6Fv/S/DaA24S8="; }).outPath"#, "/nix/store/xm1l9dx4zgycv9qdhcqqvji1z88z534b-foo")]
+    #[case::sha256(r#"(builtins.derivation { name = "foo"; builder = "/bin/sh"; system = "x86_64-linux"; outputHashMode = "flat"; outputHashAlgo = "sha256"; outputHash = "sha256-Q3QXOoy+iN4VK2CflvRulYvPZXYgF0dO7FoF7CvWFTA="; }).outPath"#, "/nix/store/q4pkwkxdib797fhk22p0k3g1q32jmxvf-foo")]
+    #[case::sha256_other_name(r#"(builtins.derivation { name = "foo2"; builder = "/bin/sh"; system = "x86_64-linux"; outputHashMode = "flat"; outputHashAlgo = "sha256"; outputHash = "sha256-Q3QXOoy+iN4VK2CflvRulYvPZXYgF0dO7FoF7CvWFTA="; }).outPath"#, "/nix/store/znw17xlmx9r6gw8izjkqxkl6s28sza4l-foo2")]
+    #[case::sha1(r#"(builtins.derivation { name = "foo"; builder = "/bin/sh"; system = "x86_64-linux"; outputHashMode = "flat"; outputHashAlgo = "sha1"; outputHash = "sha1-VUCRC+16gU5lcrLYHlPSUyx0Y/Q="; }).outPath"#, "/nix/store/zgpnjjmga53d8srp8chh3m9fn7nnbdv6-foo")]
+    #[case::md5(r#"(builtins.derivation { name = "foo"; builder = "/bin/sh"; system = "x86_64-linux"; outputHashMode = "flat"; outputHashAlgo = "md5"; outputHash = "md5-07BzhNET7exJ6qYjitX/AA=="; }).outPath"#, "/nix/store/jfhcwnq1852ccy9ad9nakybp2wadngnd-foo")]
+    #[case::sha512(r#"(builtins.derivation { name = "foo"; builder = "/bin/sh"; system = "x86_64-linux"; outputHashMode = "flat"; outputHashAlgo = "sha512"; outputHash = "sha512-DPkYCnZKuoY6Z7bXLwkYvBMcZ3JkLLLc5aNPCnAvlHDdwr8SXBIZixmVwjPDS0r9NGxUojNMNQqUilG26LTmtg=="; }).outPath"#, "/nix/store/as736rr116ian9qzg457f96j52ki8bm3-foo")]
+    #[case::r_sha256_outputhashalgo_omitted(r#"(builtins.derivation { name = "foo"; builder = "/bin/sh"; system = "x86_64-linux"; outputHashMode = "recursive"; outputHash = "sha256-Q3QXOoy+iN4VK2CflvRulYvPZXYgF0dO7FoF7CvWFTA="; }).outPath"#, "/nix/store/17wgs52s7kcamcyin4ja58njkf91ipq8-foo")]
+    #[case::r_sha256_outputhashalgo_and_outputhashmode_omitted(r#"(builtins.derivation { name = "foo"; builder = "/bin/sh"; system = "x86_64-linux"; outputHash = "sha256-Q3QXOoy+iN4VK2CflvRulYvPZXYgF0dO7FoF7CvWFTA="; }).outPath"#, "/nix/store/q4pkwkxdib797fhk22p0k3g1q32jmxvf-foo")]
+    #[case::outputhash_omitted(r#"(builtins.derivation { name = "foo"; builder = "/bin/sh"; system = "x86_64-linux"; }).outPath"#, "/nix/store/xpcvxsx5sw4rbq666blz6sxqlmsqphmr-foo")]
+    #[case::multiple_outputs(r#"(builtins.derivation { name = "foo"; builder = "/bin/sh"; outputs = ["foo" "bar"]; system = "x86_64-linux"; }).outPath"#, "/nix/store/hkwdinvz2jpzgnjy9lv34d2zxvclj4s3-foo-foo")]
+    #[case::args(r#"(builtins.derivation { name = "foo"; builder = "/bin/sh"; args = ["--foo" "42" "--bar"]; system = "x86_64-linux"; }).outPath"#, "/nix/store/365gi78n2z7vwc1bvgb98k0a9cqfp6as-foo")]
+    #[case::full(r#"
+                   let
+                     bar = builtins.derivation {
+                       name = "bar";
+                       builder = ":";
+                       system = ":";
+                       outputHash = "08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba";
+                       outputHashAlgo = "sha256";
+                       outputHashMode = "recursive";
+                     };
+                   in
+                   (builtins.derivation {
+                     name = "foo";
+                     builder = ":";
+                     system = ":";
+                     inherit bar;
+                   }).outPath
+        "#, "/nix/store/5vyvcwah9l9kf07d52rcgdk70g2f4y13-foo")]
+    #[case::pass_as_file(r#"(builtins.derivation { "name" = "foo"; passAsFile = ["bar"]; bar = "baz"; system = ":"; builder = ":";}).outPath"#, "/nix/store/25gf0r1ikgmh4vchrn8qlc4fnqlsa5a1-foo")]
+    // __ignoreNulls = true, but nothing set to null
+    #[case::ignore_nulls_true_no_arg_drvpath(r#"(builtins.derivation { name = "foo"; system = ":"; builder = ":"; __ignoreNulls = true; }).drvPath"#, "/nix/store/xa96w6d7fxrlkk60z1fmx2ffp2wzmbqx-foo.drv")]
+    #[case::ignore_nulls_true_no_arg_outpath(r#"(builtins.derivation { name = "foo"; system = ":"; builder = ":"; __ignoreNulls = true; }).outPath"#, "/nix/store/pk2agn9za8r9bxsflgh1y7fyyrmwcqkn-foo")]
+    // __ignoreNulls = true, with a null arg, same paths
+    #[case::ignore_nulls_true_drvpath(r#"(builtins.derivation { name = "foo"; system = ":"; builder = ":"; __ignoreNulls = true; ignoreme = null; }).drvPath"#, "/nix/store/xa96w6d7fxrlkk60z1fmx2ffp2wzmbqx-foo.drv")]
+    #[case::ignore_nulls_true_outpath(r#"(builtins.derivation { name = "foo"; system = ":"; builder = ":"; __ignoreNulls = true; ignoreme = null; }).outPath"#, "/nix/store/pk2agn9za8r9bxsflgh1y7fyyrmwcqkn-foo")]
+    // __ignoreNulls = false
+    #[case::ignore_nulls_false_no_arg_drvpath(r#"(builtins.derivation { name = "foo"; system = ":"; builder = ":"; __ignoreNulls = false; }).drvPath"#, "/nix/store/xa96w6d7fxrlkk60z1fmx2ffp2wzmbqx-foo.drv")]
+    #[case::ignore_nulls_false_no_arg_outpath(r#"(builtins.derivation { name = "foo"; system = ":"; builder = ":"; __ignoreNulls = false; }).outPath"#, "/nix/store/pk2agn9za8r9bxsflgh1y7fyyrmwcqkn-foo")]
+    // __ignoreNulls = false, with a null arg
+    #[case::ignore_nulls_fales_arg_path_drvpath(r#"(builtins.derivation { name = "foo"; system = ":"; builder = ":"; __ignoreNulls = false; foo = null; }).drvPath"#, "/nix/store/xwkwbajfiyhdqmksrbzm0s4g4ib8d4ms-foo.drv")]
+    #[case::ignore_nulls_fales_arg_path_outpath(r#"(builtins.derivation { name = "foo"; system = ":"; builder = ":"; __ignoreNulls = false; foo = null; }).outPath"#, "/nix/store/2n2jqm6l7r2ahi19m58pl896ipx9cyx6-foo")]
+    // structured attrs set to false will render an empty string inside env
+    #[case::structured_attrs_false_drvpath(r#"(builtins.derivation { name = "foo"; system = ":"; builder = ":"; __structuredAttrs = false; foo = "bar"; }).drvPath"#, "/nix/store/qs39krwr2lsw6ac910vqx4pnk6m63333-foo.drv")]
+    #[case::structured_attrs_false_outpath(r#"(builtins.derivation { name = "foo"; system = ":"; builder = ":"; __structuredAttrs = false; foo = "bar"; }).outPath"#, "/nix/store/9yy3764rdip3fbm8ckaw4j9y7vh4d231-foo")]
+    // simple structured attrs
+    #[case::structured_attrs_simple_drvpath(r#"(builtins.derivation { name = "foo"; system = ":"; builder = ":"; __structuredAttrs = true; foo = "bar"; }).drvPath"#, "/nix/store/k6rlb4k10cb9iay283037ml1nv3xma2f-foo.drv")]
+    #[case::structured_attrs_simple_outpath(r#"(builtins.derivation { name = "foo"; system = ":"; builder = ":"; __structuredAttrs = true; foo = "bar"; }).outPath"#, "/nix/store/6lmv3hyha1g4cb426iwjyifd7nrdv1xn-foo")]
+    // structured attrs with outputsCheck
+    #[case::structured_attrs_output_checks_drvpath(r#"(builtins.derivation { name = "foo"; system = ":"; builder = ":"; __structuredAttrs = true; foo = "bar"; outputChecks = {out = {maxClosureSize = 256 * 1024 * 1024; disallowedRequisites = [ "dev" ];};}; }).drvPath"#, "/nix/store/fx9qzpchh5wchchhy39bwsml978d6wp1-foo.drv")]
+    #[case::structured_attrs_output_checks_outpath(r#"(builtins.derivation { name = "foo"; system = ":"; builder = ":"; __structuredAttrs = true; foo = "bar"; outputChecks = {out = {maxClosureSize = 256 * 1024 * 1024; disallowedRequisites = [ "dev" ];};}; }).outPath"#, "/nix/store/pcywah1nwym69rzqdvpp03sphfjgyw1l-foo")]
+    // structured attrs and __ignoreNulls. ignoreNulls is inactive (so foo ends up in __json, yet __ignoreNulls itself is not present.
+    #[case::structured_attrs_and_ignore_nulls_drvpath(r#"(builtins.derivation { name = "foo"; system = ":"; builder = ":"; __ignoreNulls = false; foo = null; __structuredAttrs = true; }).drvPath"#, "/nix/store/rldskjdcwa3p7x5bqy3r217va1jsbjsc-foo.drv")]
+    // structured attrs, setting outputs.
+    #[case::structured_attrs_outputs_drvpath(r#"(builtins.derivation { name = "test"; system = "aarch64-linux"; builder = "/bin/sh"; __structuredAttrs = true; outputs = [ "out"]; }).drvPath"#, "/nix/store/6sgawp30zibsh525p7c948xxd22y2ngy-test.drv")]
+    fn test_outpath(#[case] code: &str, #[case] expected_path: &str) {
+        let value = eval(code).value.expect("must succeed");
+
+        match value {
+            tvix_eval::Value::String(s) => {
+                assert_eq!(*s, expected_path);
+            }
+            _ => panic!("unexpected value type: {:?}", value),
+        }
+    }
+
+    /// construct some calls to builtins.derivation that should be rejected
+    #[rstest]
+    #[case::invalid_outputhash(r#"(builtins.derivation { name = "foo"; builder = "/bin/sh"; system = "x86_64-linux"; outputHashMode = "recursive"; outputHashAlgo = "sha256"; outputHash = "sha256-00"; }).outPath"#)]
+    #[case::sha1_and_sha256(r#"(builtins.derivation { name = "foo"; builder = "/bin/sh"; system = "x86_64-linux"; outputHashMode = "recursive"; outputHashAlgo = "sha1"; outputHash = "sha256-Q3QXOoy+iN4VK2CflvRulYvPZXYgF0dO7FoF7CvWFTA="; }).outPath"#)]
+    #[case::duplicate_output_names(r#"(builtins.derivation { name = "foo"; builder = "/bin/sh"; outputs = ["foo" "foo"]; system = "x86_64-linux"; }).outPath"#)]
+    fn test_outpath_invalid(#[case] code: &str) {
+        let resp = eval(code);
+        assert!(resp.value.is_none(), "Value should be None");
+        assert!(
+            !resp.errors.is_empty(),
+            "There should have been some errors"
+        );
+    }
+
+    /// Construct two FODs with the same name, and same known output (but
+    /// slightly different recipe), ensure they have the same output hash.
+    #[test]
+    fn test_fod_outpath() {
+        let code = r#"
+          (builtins.derivation { name = "foo"; builder = "/bin/sh"; system = "x86_64-linux"; outputHash = "sha256-Q3QXOoy+iN4VK2CflvRulYvPZXYgF0dO7FoF7CvWFTA="; }).outPath ==
+          (builtins.derivation { name = "foo"; builder = "/bin/aa"; system = "x86_64-linux"; outputHash = "sha256-Q3QXOoy+iN4VK2CflvRulYvPZXYgF0dO7FoF7CvWFTA="; }).outPath
+        "#;
+
+        let value = eval(code).value.expect("must succeed");
+        match value {
+            tvix_eval::Value::Bool(v) => {
+                assert!(v);
+            }
+            _ => panic!("unexpected value type: {:?}", value),
+        }
+    }
+
+    /// Construct two FODs with the same name, and same known output (but
+    /// slightly different recipe), ensure they have the same output hash.
+    #[test]
+    fn test_fod_outpath_different_name() {
+        let code = r#"
+          (builtins.derivation { name = "foo"; builder = "/bin/sh"; system = "x86_64-linux"; outputHash = "sha256-Q3QXOoy+iN4VK2CflvRulYvPZXYgF0dO7FoF7CvWFTA="; }).outPath ==
+          (builtins.derivation { name = "foo"; builder = "/bin/aa"; system = "x86_64-linux"; outputHash = "sha256-Q3QXOoy+iN4VK2CflvRulYvPZXYgF0dO7FoF7CvWFTA="; }).outPath
+        "#;
+
+        let value = eval(code).value.expect("must succeed");
+        match value {
+            tvix_eval::Value::Bool(v) => {
+                assert!(v);
+            }
+            _ => panic!("unexpected value type: {:?}", value),
+        }
+    }
+
+    /// Construct two derivations with the same parameters except one of them lost a context string
+    /// for a dependency, causing the loss of an element in the `inputDrvs` derivation. Therefore,
+    /// making `outPath` different.
+    #[test]
+    fn test_unsafe_discard_string_context() {
+        let code = r#"
+        let
+            dep = builtins.derivation { name = "foo"; builder = "/bin/sh"; system = "x86_64-linux"; };
+        in
+          (builtins.derivation { name = "foo"; builder = "/bin/sh"; system = "x86_64-linux"; env = "${dep}"; }).outPath !=
+          (builtins.derivation { name = "foo"; builder = "/bin/sh"; system = "x86_64-linux"; env = "${builtins.unsafeDiscardStringContext dep}"; }).outPath
+        "#;
+
+        let value = eval(code).value.expect("must succeed");
+        match value {
+            tvix_eval::Value::Bool(v) => {
+                assert!(v);
+            }
+            _ => panic!("unexpected value type: {:?}", value),
+        }
+    }
+
+    /// Construct an attribute set that coerces to a derivation and verify that the return type is
+    /// a string.
+    #[test]
+    fn test_unsafe_discard_string_context_of_coercible() {
+        let code = r#"
+        let
+            dep = builtins.derivation { name = "foo"; builder = "/bin/sh"; system = "x86_64-linux"; };
+            attr = { __toString = _: dep; };
+        in
+            builtins.typeOf (builtins.unsafeDiscardStringContext attr) == "string"
+        "#;
+
+        let value = eval(code).value.expect("must succeed");
+        match value {
+            tvix_eval::Value::Bool(v) => {
+                assert!(v);
+            }
+            _ => panic!("unexpected value type: {:?}", value),
+        }
+    }
+
+    #[rstest]
+    #[case::input_in_args(r#"
+                   let
+                     bar = builtins.derivation {
+                       name = "bar";
+                       builder = ":";
+                       system = ":";
+                       outputHash = "08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba";
+                       outputHashAlgo = "sha256";
+                       outputHashMode = "recursive";
+                     };
+                   in
+                   (builtins.derivation {
+                     name = "foo";
+                     builder = ":";
+                     args = [ "${bar}" ];
+                     system = ":";
+                   }).drvPath
+        "#, "/nix/store/50yl2gmmljyl0lzyrp1mcyhn53vhjhkd-foo.drv")]
+    fn test_inputs_derivation_from_context(#[case] code: &str, #[case] expected_drvpath: &str) {
+        let eval_result = eval(code);
+
+        let value = eval_result.value.expect("must succeed");
+
+        match value {
+            tvix_eval::Value::String(s) => {
+                assert_eq!(*s, expected_drvpath);
+            }
+
+            _ => panic!("unexpected value type: {:?}", value),
+        };
+    }
+
+    #[test]
+    fn builtins_placeholder_hashes() {
+        assert_eq!(
+            hash_placeholder("out").as_str(),
+            "/1rz4g4znpzjwh1xymhjpm42vipw92pr73vdgl6xs1hycac8kf2n9"
+        );
+
+        assert_eq!(
+            hash_placeholder("").as_str(),
+            "/171rf4jhx57xqz3p7swniwkig249cif71pa08p80mgaf0mqz5bmr"
+        );
+    }
+
+    /// constructs calls to builtins.derivation that should succeed, but produce warnings
+    #[rstest]
+    #[case::r_sha256_wrong_padding(r#"(builtins.derivation { name = "foo"; builder = "/bin/sh"; system = "x86_64-linux"; outputHashMode = "recursive"; outputHashAlgo = "sha256"; outputHash = "sha256-fgIr3TyFGDAXP5+qoAaiMKDg/a1MlT6Fv/S/DaA24S8===="; }).outPath"#, "/nix/store/xm1l9dx4zgycv9qdhcqqvji1z88z534b-foo")]
+    fn builtins_derivation_hash_wrong_padding_warn(
+        #[case] code: &str,
+        #[case] expected_path: &str,
+    ) {
+        let eval_result = eval(code);
+
+        let value = eval_result.value.expect("must succeed");
+
+        match value {
+            tvix_eval::Value::String(s) => {
+                assert_eq!(*s, expected_path);
+            }
+            _ => panic!("unexpected value type: {:?}", value),
+        }
+
+        assert!(
+            !eval_result.warnings.is_empty(),
+            "warnings should not be empty"
+        );
+    }
+
+    /// Invokes `builtins.filterSource` on various carefully-crated subdirs, and
+    /// ensures the resulting store paths matches what Nix produces.
+    /// @fixtures is replaced to the fixtures directory.
+    #[rstest]
+    #[cfg(target_family = "unix")]
+    #[case::complicated_filter_nothing(
+        r#"(builtins.filterSource (p: t: true) @fixtures)"#,
+        "/nix/store/bqh6kd0x3vps2rzagzpl7qmbbgnx19cp-import_fixtures"
+    )]
+    #[case::complicated_filter_everything(
+        r#"(builtins.filterSource (p: t: false) @fixtures)"#,
+        "/nix/store/giq6czz24lpjg97xxcxk6rg950lcpib1-import_fixtures"
+    )]
+    #[case::simple_dir_with_one_file_filter_dirs(
+        r#"(builtins.filterSource (p: t: t != "directory") @fixtures/a_dir)"#,
+        "/nix/store/8vbqaxapywkvv1hacdja3pi075r14d43-a_dir"
+    )]
+    #[case::simple_dir_with_one_file_filter_files(
+        r#"(builtins.filterSource (p: t: t != "regular") @fixtures/a_dir)"#,
+        "/nix/store/zphlqc93s2iq4xm393l06hzf8hp85r4z-a_dir"
+    )]
+    #[case::simple_dir_with_one_file_filter_symlinks(
+        r#"(builtins.filterSource (p: t: t != "symlink") @fixtures/a_dir)"#,
+        "/nix/store/8vbqaxapywkvv1hacdja3pi075r14d43-a_dir"
+    )]
+    #[case::simple_dir_with_one_file_filter_nothing(
+        r#"(builtins.filterSource (p: t: true) @fixtures/a_dir)"#,
+        "/nix/store/8vbqaxapywkvv1hacdja3pi075r14d43-a_dir"
+    )]
+    #[case::simple_dir_with_one_file_filter_everything(
+        r#"(builtins.filterSource (p: t: false) @fixtures/a_dir)"#,
+        "/nix/store/zphlqc93s2iq4xm393l06hzf8hp85r4z-a_dir"
+    )]
+    #[case::simple_dir_with_one_dir_filter_dirs(
+        r#"builtins.filterSource (p: t: t != "directory") @fixtures/b_dir"#,
+        "/nix/store/xzsfzdgrxg93icaamjm8zq1jq6xvf2fz-b_dir"
+    )]
+    #[case::simple_dir_with_one_dir_filter_files(
+        r#"builtins.filterSource (p: t: t != "regular") @fixtures/b_dir"#,
+        "/nix/store/8rjx64mm7173xp60rahv7cl3ixfkv3rf-b_dir"
+    )]
+    #[case::simple_dir_with_one_dir_filter_symlinks(
+        r#"builtins.filterSource (p: t: t != "symlink") @fixtures/b_dir"#,
+        "/nix/store/8rjx64mm7173xp60rahv7cl3ixfkv3rf-b_dir"
+    )]
+    #[case::simple_dir_with_one_dir_filter_nothing(
+        r#"builtins.filterSource (p: t: true) @fixtures/b_dir"#,
+        "/nix/store/8rjx64mm7173xp60rahv7cl3ixfkv3rf-b_dir"
+    )]
+    #[case::simple_dir_with_one_dir_filter_everything(
+        r#"builtins.filterSource (p: t: false) @fixtures/b_dir"#,
+        "/nix/store/xzsfzdgrxg93icaamjm8zq1jq6xvf2fz-b_dir"
+    )]
+    #[case::simple_dir_with_one_symlink_to_file_filter_dirs(
+        r#"builtins.filterSource (p: t: t != "directory") @fixtures/c_dir"#,
+        "/nix/store/riigfmmzzrq65zqiffcjk5sbqr9c9h09-c_dir"
+    )]
+    #[case::simple_dir_with_one_symlink_to_file_filter_files(
+        r#"builtins.filterSource (p: t: t != "regular") @fixtures/c_dir"#,
+        "/nix/store/riigfmmzzrq65zqiffcjk5sbqr9c9h09-c_dir"
+    )]
+    #[case::simple_dir_with_one_symlink_to_file_filter_symlinks(
+        r#"builtins.filterSource (p: t: t != "symlink") @fixtures/c_dir"#,
+        "/nix/store/y5g1fz04vzjvf422q92qmv532axj5q26-c_dir"
+    )]
+    #[case::simple_dir_with_one_symlink_to_file_filter_nothing(
+        r#"builtins.filterSource (p: t: true) @fixtures/c_dir"#,
+        "/nix/store/riigfmmzzrq65zqiffcjk5sbqr9c9h09-c_dir"
+    )]
+    #[case::simple_dir_with_one_symlink_to_file_filter_everything(
+        r#"builtins.filterSource (p: t: false) @fixtures/c_dir"#,
+        "/nix/store/y5g1fz04vzjvf422q92qmv532axj5q26-c_dir"
+    )]
+    #[case::simple_dir_with_dangling_symlink_filter_dirs(
+        r#"builtins.filterSource (p: t: t != "directory") @fixtures/d_dir"#,
+        "/nix/store/f2d1aixwiqy4lbzrd040ala2s4m2z199-d_dir"
+    )]
+    #[case::simple_dir_with_dangling_symlink_filter_files(
+        r#"builtins.filterSource (p: t: t != "regular") @fixtures/d_dir"#,
+        "/nix/store/f2d1aixwiqy4lbzrd040ala2s4m2z199-d_dir"
+    )]
+    #[case::simple_dir_with_dangling_symlink_filter_symlinks(
+        r#"builtins.filterSource (p: t: t != "symlink") @fixtures/d_dir"#,
+        "/nix/store/7l371xax8kknhpska4wrmyll1mzlhzvl-d_dir"
+    )]
+    #[case::simple_dir_with_dangling_symlink_filter_nothing(
+        r#"builtins.filterSource (p: t: true) @fixtures/d_dir"#,
+        "/nix/store/f2d1aixwiqy4lbzrd040ala2s4m2z199-d_dir"
+    )]
+    #[case::simple_dir_with_dangling_symlink_filter_everything(
+        r#"builtins.filterSource (p: t: false) @fixtures/d_dir"#,
+        "/nix/store/7l371xax8kknhpska4wrmyll1mzlhzvl-d_dir"
+    )]
+    #[case::simple_symlinked_dir_with_one_file_filter_dirs(
+        r#"builtins.filterSource (p: t: t != "directory") @fixtures/symlink_to_a_dir"#,
+        "/nix/store/apmdprm8fwl2zrjpbyfcd99zrnhvf47q-symlink_to_a_dir"
+    )]
+    #[case::simple_symlinked_dir_with_one_file_filter_files(
+        r#"builtins.filterSource (p: t: t != "regular") @fixtures/symlink_to_a_dir"#,
+        "/nix/store/apmdprm8fwl2zrjpbyfcd99zrnhvf47q-symlink_to_a_dir"
+    )]
+    #[case::simple_symlinked_dir_with_one_file_filter_symlinks(
+        r#"builtins.filterSource (p: t: t != "symlink") @fixtures/symlink_to_a_dir"#,
+        "/nix/store/apmdprm8fwl2zrjpbyfcd99zrnhvf47q-symlink_to_a_dir"
+    )]
+    #[case::simple_symlinked_dir_with_one_file_filter_nothing(
+        r#"builtins.filterSource (p: t: true) @fixtures/symlink_to_a_dir"#,
+        "/nix/store/apmdprm8fwl2zrjpbyfcd99zrnhvf47q-symlink_to_a_dir"
+    )]
+    #[case::simple_symlinked_dir_with_one_file_filter_everything(
+        r#"builtins.filterSource (p: t: false) @fixtures/symlink_to_a_dir"#,
+        "/nix/store/apmdprm8fwl2zrjpbyfcd99zrnhvf47q-symlink_to_a_dir"
+    )]
+    fn builtins_filter_source_succeed(#[case] code: &str, #[case] expected_outpath: &str) {
+        // populate the fixtures dir
+        let temp = TempDir::new().expect("create temporary directory");
+        let p = temp.path().join("import_fixtures");
+
+        // create the fixtures directory.
+        // We produce them at runtime rather than shipping it inside the source
+        // tree, as git can't model certain things - like directories without any
+        // items.
+        {
+            fs::create_dir(&p).expect("creating import_fixtures");
+
+            // `/a_dir` contains an empty `a_file` file
+            fs::create_dir(p.join("a_dir")).expect("creating /a_dir");
+            fs::write(p.join("a_dir").join("a_file"), "").expect("creating /a_dir/a_file");
+
+            // `/a_file` is an empty file
+            fs::write(p.join("a_file"), "").expect("creating /a_file");
+
+            // `/b_dir` contains an empty "a_dir" directory
+            fs::create_dir_all(p.join("b_dir").join("a_dir")).expect("creating /b_dir/a_dir");
+
+            // `/c_dir` contains a `symlink_to_a_file` symlink, pointing to `../a_dir/a_file`.
+            fs::create_dir(p.join("c_dir")).expect("creating /c_dir");
+            std::os::unix::fs::symlink(
+                "../a_dir/a_file",
+                p.join("c_dir").join("symlink_to_a_file"),
+            )
+            .expect("creating /c_dir/symlink_to_a_file");
+
+            // `/d_dir` contains a `dangling_symlink`, pointing to `a_dir/a_file`,
+            // which does not exist.
+            fs::create_dir(p.join("d_dir")).expect("creating /d_dir");
+            std::os::unix::fs::symlink("a_dir/a_file", p.join("d_dir").join("dangling_symlink"))
+                .expect("creating /d_dir/dangling_symlink");
+
+            // `/symlink_to_a_dir` is a symlink to `a_dir`, which exists.
+            std::os::unix::fs::symlink("a_dir", p.join("symlink_to_a_dir"))
+                .expect("creating /symlink_to_a_dir");
+        }
+
+        // replace @fixtures with the temporary path containing the fixtures
+        let code_replaced = code.replace("@fixtures", &p.to_string_lossy());
+
+        let eval_result = eval(&code_replaced);
+
+        let value = eval_result.value.expect("must succeed");
+
+        match value {
+            tvix_eval::Value::String(s) => {
+                assert_eq!(expected_outpath, s.as_bstr());
+            }
+            _ => panic!("unexpected value type: {:?}", value),
+        }
+
+        assert!(eval_result.errors.is_empty(), "errors should be empty");
+    }
+
+    // Space is an illegal character.
+    #[rstest]
+    #[case(
+        r#"(builtins.path { name = "valid-name"; path = @fixtures + "/te st"; recursive = true; })"#,
+        true
+    )]
+    // Space is still an illegal character.
+    #[case(
+        r#"(builtins.path { name = "invalid name"; path = @fixtures + "/te st"; recursive = true; })"#,
+        false
+    )]
+    fn builtins_path_recursive_rename(#[case] code: &str, #[case] success: bool) {
+        // populate the fixtures dir
+        let temp = TempDir::new().expect("create temporary directory");
+        let p = temp.path().join("import_fixtures");
+
+        // create the fixtures directory.
+        // We produce them at runtime rather than shipping it inside the source
+        // tree, as git can't model certain things - like directories without any
+        // items.
+        {
+            fs::create_dir(&p).expect("creating import_fixtures");
+            fs::write(p.join("te st"), "").expect("creating `/te st`");
+        }
+        // replace @fixtures with the temporary path containing the fixtures
+        let code_replaced = code.replace("@fixtures", &p.to_string_lossy());
+
+        let eval_result = eval(&code_replaced);
+
+        let value = eval_result.value;
+
+        if success {
+            match value.expect("expected successful evaluation on legal rename") {
+                tvix_eval::Value::String(s) => {
+                    assert_eq!(
+                        "/nix/store/nd5z11x7zjqqz44rkbhc6v7yifdkn659-valid-name",
+                        s.as_bstr()
+                    );
+                }
+                v => panic!("unexpected value type: {:?}", v),
+            }
+        } else {
+            assert!(value.is_none(), "unexpected success on illegal store paths");
+        }
+    }
+
+    // Space is an illegal character.
+    #[rstest]
+    #[case(
+        r#"(builtins.path { name = "valid-name"; path = @fixtures + "/te st"; recursive = false; })"#,
+        true
+    )]
+    // Space is still an illegal character.
+    #[case(
+        r#"(builtins.path { name = "invalid name"; path = @fixtures + "/te st"; recursive = false; })"#,
+        false
+    )]
+    // The non-recursive variant passes explicitly `recursive = false;`
+    fn builtins_path_nonrecursive_rename(#[case] code: &str, #[case] success: bool) {
+        // populate the fixtures dir
+        let temp = TempDir::new().expect("create temporary directory");
+        let p = temp.path().join("import_fixtures");
+
+        // create the fixtures directory.
+        // We produce them at runtime rather than shipping it inside the source
+        // tree, as git can't model certain things - like directories without any
+        // items.
+        {
+            fs::create_dir(&p).expect("creating import_fixtures");
+            fs::write(p.join("te st"), "").expect("creating `/te st`");
+        }
+        // replace @fixtures with the temporary path containing the fixtures
+        let code_replaced = code.replace("@fixtures", &p.to_string_lossy());
+
+        let eval_result = eval(&code_replaced);
+
+        let value = eval_result.value;
+
+        if success {
+            match value.expect("expected successful evaluation on legal rename") {
+                tvix_eval::Value::String(s) => {
+                    assert_eq!(
+                        "/nix/store/il2rmfbqgs37rshr8w7x64hd4d3b4bsa-valid-name",
+                        s.as_bstr()
+                    );
+                }
+                v => panic!("unexpected value type: {:?}", v),
+            }
+        } else {
+            assert!(value.is_none(), "unexpected success on illegal store paths");
+        }
+    }
+
+    #[rstest]
+    #[case(
+        r#"(builtins.path { name = "valid-name"; path = @fixtures + "/te st"; recursive = false; sha256 = "sha256-47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU="; })"#,
+        true
+    )]
+    #[case(
+        r#"(builtins.path { name = "valid-name"; path = @fixtures + "/te st"; recursive = true; sha256 = "sha256-47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU="; })"#,
+        false
+    )]
+    #[case(
+        r#"(builtins.path { name = "valid-name"; path = @fixtures + "/te st"; recursive = true; sha256 = "sha256-d6xi4mKdjkX2JFicDIv5niSzpyI0m/Hnm8GGAIU04kY="; })"#,
+        true
+    )]
+    #[case(
+        r#"(builtins.path { name = "valid-name"; path = @fixtures + "/te st"; recursive = false; sha256 = "sha256-d6xi4mKdjkX2JFicDIv5niSzpyI0m/Hnm8GGAIU04kY="; })"#,
+        false
+    )]
+    fn builtins_path_fod_locking(#[case] code: &str, #[case] exp_success: bool) {
+        // populate the fixtures dir
+        let temp = TempDir::new().expect("create temporary directory");
+        let p = temp.path().join("import_fixtures");
+
+        // create the fixtures directory.
+        // We produce them at runtime rather than shipping it inside the source
+        // tree, as git can't model certain things - like directories without any
+        // items.
+        {
+            fs::create_dir(&p).expect("creating import_fixtures");
+            fs::write(p.join("te st"), "").expect("creating `/te st`");
+        }
+        // replace @fixtures with the temporary path containing the fixtures
+        let code_replaced = code.replace("@fixtures", &p.to_string_lossy());
+
+        let eval_result = eval(&code_replaced);
+
+        let value = eval_result.value;
+
+        if exp_success {
+            assert!(
+                value.is_some(),
+                "expected successful evaluation on legal rename and valid FOD sha256"
+            );
+        } else {
+            assert!(value.is_none(), "unexpected success on invalid FOD sha256");
+        }
+    }
+
+    #[rstest]
+    #[case(
+        r#"(builtins.path { name = "valid-path"; path = @fixtures + "/te st dir"; filter = _: _: true; })"#,
+        "/nix/store/i28jmi4fwym4fw3flkrkp2mdxx50pdy0-valid-path"
+    )]
+    #[case(
+        r#"(builtins.path { name = "valid-path"; path = @fixtures + "/te st dir"; filter = _: _: false; })"#,
+        "/nix/store/pwza2ij9gk1fmzhbjnynmfv2mq2sgcap-valid-path"
+    )]
+    fn builtins_path_filter(#[case] code: &str, #[case] expected_outpath: &str) {
+        // populate the fixtures dir
+        let temp = TempDir::new().expect("create temporary directory");
+        let p = temp.path().join("import_fixtures");
+
+        // create the fixtures directory.
+        // We produce them at runtime rather than shipping it inside the source
+        // tree, as git can't model certain things - like directories without any
+        // items.
+        {
+            fs::create_dir(&p).expect("creating import_fixtures");
+            fs::create_dir(p.join("te st dir")).expect("creating `/te st dir`");
+            fs::write(p.join("te st dir").join("test"), "").expect("creating `/te st dir/test`");
+        }
+        // replace @fixtures with the temporary path containing the fixtures
+        let code_replaced = code.replace("@fixtures", &p.to_string_lossy());
+
+        let eval_result = eval(&code_replaced);
+
+        let value = eval_result.value.expect("must succeed");
+
+        match value {
+            tvix_eval::Value::String(s) => {
+                assert_eq!(expected_outpath, s.as_bstr());
+            }
+            _ => panic!("unexpected value type: {:?}", value),
+        }
+
+        assert!(eval_result.errors.is_empty(), "errors should be empty");
+    }
+
+    // All tests filter out some unsupported (not representable in castore) nodes, confirming
+    // invalid, but filtered-out nodes don't prevent ingestion of a path.
+    #[rstest]
+    #[cfg(target_family = "unix")]
+    // There is a set of invalid filetypes.
+    // We write various filter functions filtering them out, but usually leaving
+    // some behind.
+    // In case there's still invalid filetypes left after the filtering, we
+    // expect the evaluation to fail.
+    #[case::fail_kept_unknowns(
+        r#"(builtins.filterSource (p: t: t == "unknown") @fixtures)"#,
+        false
+    )]
+    // We filter all invalid filetypes, so the evaluation has to succeed.
+    #[case::succeed_filter_unknowns(
+        r#"(builtins.filterSource (p: t: t != "unknown") @fixtures)"#,
+        true
+    )]
+    #[case::fail_kept_charnode(
+        r#"(builtins.filterSource (p: t: (builtins.baseNameOf p) != "a_charnode") @fixtures)"#,
+        false
+    )]
+    #[case::fail_kept_socket(
+        r#"(builtins.filterSource (p: t: (builtins.baseNameOf p) != "a_socket") @fixtures)"#,
+        false
+    )]
+    #[case::fail_kept_fifo(
+        r#"(builtins.filterSource (p: t: (builtins.baseNameOf p) != "a_fifo") @fixtures)"#,
+        false
+    )]
+    fn builtins_filter_source_unsupported_files(#[case] code: &str, #[case] exp_success: bool) {
+        use nix::errno::Errno;
+        use nix::sys::stat;
+        use nix::unistd;
+        use std::os::unix::net::UnixListener;
+        use tempfile::TempDir;
+
+        // We prepare a directory containing some unsupported file nodes:
+        // - character device
+        // - socket
+        // - FIFO
+        // and we run the evaluation inside that CWD.
+        //
+        // block devices cannot be tested because we don't have the right permissions.
+        let temp = TempDir::with_prefix("foo").expect("Failed to create a temporary directory");
+
+        // read, write, execute to the owner.
+        unistd::mkfifo(&temp.path().join("a_fifo"), stat::Mode::S_IRWXU)
+            .expect("Failed to create the FIFO");
+
+        UnixListener::bind(temp.path().join("a_socket")).expect("Failed to create the socket");
+
+        stat::mknod(
+            &temp.path().join("a_charnode"),
+            stat::SFlag::S_IFCHR,
+            stat::Mode::S_IRWXU,
+            0,
+        )
+        .inspect_err(|e| {
+            if *e == Errno::EPERM {
+                eprintln!(
+                    "\
+Missing permissions to create a character device node with mknod(2).
+Please run this test as root or set CAP_MKNOD."
+                );
+            }
+        })
+        .expect("Failed to create a character device node");
+
+        let code_replaced = code.replace("@fixtures", &temp.path().to_string_lossy());
+        let eval_result = eval(&code_replaced);
+
+        if exp_success {
+            assert!(
+                eval_result.value.is_some(),
+                "unexpected failure on a directory of unsupported file types but all filtered: {:?}",
+                eval_result.errors
+            );
+        } else {
+            assert!(
+                eval_result.value.is_none(),
+                "unexpected success on unsupported file type ingestion: {:?}",
+                eval_result.value
+            );
+        }
+    }
+}
diff --git a/tvix/glue/src/builtins/utils.rs b/tvix/glue/src/builtins/utils.rs
new file mode 100644
index 0000000000..586169beeb
--- /dev/null
+++ b/tvix/glue/src/builtins/utils.rs
@@ -0,0 +1,36 @@
+use bstr::ByteSlice;
+use tvix_eval::{
+    generators::{self, GenCo},
+    CatchableErrorKind, CoercionKind, ErrorKind, NixAttrs, NixString, Value,
+};
+
+pub(super) async fn strong_importing_coerce_to_string(
+    co: &GenCo,
+    val: Value,
+) -> Result<NixString, CatchableErrorKind> {
+    let val = generators::request_force(co, val).await;
+    generators::request_string_coerce(
+        co,
+        val,
+        CoercionKind {
+            strong: true,
+            import_paths: true,
+        },
+    )
+    .await
+}
+
+pub(super) async fn select_string(
+    co: &GenCo,
+    attrs: &NixAttrs,
+    key: &str,
+) -> Result<Result<Option<String>, CatchableErrorKind>, ErrorKind> {
+    if let Some(attr) = attrs.select(key) {
+        match strong_importing_coerce_to_string(co, attr.clone()).await {
+            Err(cek) => return Ok(Err(cek)),
+            Ok(str) => return Ok(Ok(Some(str.to_str()?.to_owned()))),
+        }
+    }
+
+    Ok(Ok(None))
+}
diff --git a/tvix/glue/src/fetchers/decompression.rs b/tvix/glue/src/fetchers/decompression.rs
new file mode 100644
index 0000000000..f96fa60e34
--- /dev/null
+++ b/tvix/glue/src/fetchers/decompression.rs
@@ -0,0 +1,222 @@
+#![allow(dead_code)] // TODO
+
+use std::{
+    io, mem,
+    pin::Pin,
+    task::{Context, Poll},
+};
+
+use async_compression::tokio::bufread::{BzDecoder, GzipDecoder, XzDecoder};
+use futures::ready;
+use pin_project::pin_project;
+use tokio::io::{AsyncBufRead, AsyncRead, BufReader, ReadBuf};
+
+const GZIP_MAGIC: [u8; 2] = [0x1f, 0x8b];
+const BZIP2_MAGIC: [u8; 3] = *b"BZh";
+const XZ_MAGIC: [u8; 6] = [0xfd, 0x37, 0x7a, 0x58, 0x5a, 0x00];
+const BYTES_NEEDED: usize = 6;
+
+#[derive(Debug, Clone, Copy)]
+enum Algorithm {
+    Gzip,
+    Bzip2,
+    Xz,
+}
+
+impl Algorithm {
+    fn from_magic(magic: &[u8]) -> Option<Self> {
+        if magic.starts_with(&GZIP_MAGIC) {
+            Some(Self::Gzip)
+        } else if magic.starts_with(&BZIP2_MAGIC) {
+            Some(Self::Bzip2)
+        } else if magic.starts_with(&XZ_MAGIC) {
+            Some(Self::Xz)
+        } else {
+            None
+        }
+    }
+}
+
+#[pin_project]
+struct WithPreexistingBuffer<R> {
+    buffer: Vec<u8>,
+    #[pin]
+    inner: R,
+}
+
+impl<R> AsyncRead for WithPreexistingBuffer<R>
+where
+    R: AsyncRead,
+{
+    fn poll_read(
+        self: Pin<&mut Self>,
+        cx: &mut Context<'_>,
+        buf: &mut ReadBuf<'_>,
+    ) -> Poll<io::Result<()>> {
+        let this = self.project();
+        if !this.buffer.is_empty() {
+            // TODO: check if the buffer fits first
+            buf.put_slice(this.buffer);
+            this.buffer.clear();
+        }
+        this.inner.poll_read(cx, buf)
+    }
+}
+
+#[pin_project(project = DecompressedReaderInnerProj)]
+enum DecompressedReaderInner<R> {
+    Unknown {
+        buffer: Vec<u8>,
+        #[pin]
+        inner: Option<R>,
+    },
+    Gzip(#[pin] GzipDecoder<BufReader<WithPreexistingBuffer<R>>>),
+    Bzip2(#[pin] BzDecoder<BufReader<WithPreexistingBuffer<R>>>),
+    Xz(#[pin] XzDecoder<BufReader<WithPreexistingBuffer<R>>>),
+}
+
+impl<R> DecompressedReaderInner<R>
+where
+    R: AsyncBufRead,
+{
+    fn switch_to(&mut self, algorithm: Algorithm) {
+        let (buffer, inner) = match self {
+            DecompressedReaderInner::Unknown { buffer, inner } => {
+                (mem::take(buffer), inner.take().unwrap())
+            }
+            DecompressedReaderInner::Gzip(_)
+            | DecompressedReaderInner::Bzip2(_)
+            | DecompressedReaderInner::Xz(_) => unreachable!(),
+        };
+        let inner = BufReader::new(WithPreexistingBuffer { buffer, inner });
+
+        *self = match algorithm {
+            Algorithm::Gzip => Self::Gzip(GzipDecoder::new(inner)),
+            Algorithm::Bzip2 => Self::Bzip2(BzDecoder::new(inner)),
+            Algorithm::Xz => Self::Xz(XzDecoder::new(inner)),
+        }
+    }
+}
+
+impl<R> AsyncRead for DecompressedReaderInner<R>
+where
+    R: AsyncBufRead,
+{
+    fn poll_read(
+        self: Pin<&mut Self>,
+        cx: &mut Context<'_>,
+        buf: &mut ReadBuf<'_>,
+    ) -> Poll<io::Result<()>> {
+        match self.project() {
+            DecompressedReaderInnerProj::Unknown { .. } => {
+                unreachable!("Can't call poll_read on Unknown")
+            }
+            DecompressedReaderInnerProj::Gzip(inner) => inner.poll_read(cx, buf),
+            DecompressedReaderInnerProj::Bzip2(inner) => inner.poll_read(cx, buf),
+            DecompressedReaderInnerProj::Xz(inner) => inner.poll_read(cx, buf),
+        }
+    }
+}
+
+#[pin_project]
+pub struct DecompressedReader<R> {
+    #[pin]
+    inner: DecompressedReaderInner<R>,
+    switch_to: Option<Algorithm>,
+}
+
+impl<R> DecompressedReader<R> {
+    pub fn new(inner: R) -> Self {
+        Self {
+            inner: DecompressedReaderInner::Unknown {
+                buffer: vec![0; BYTES_NEEDED],
+                inner: Some(inner),
+            },
+            switch_to: None,
+        }
+    }
+}
+
+impl<R> AsyncRead for DecompressedReader<R>
+where
+    R: AsyncBufRead + Unpin,
+{
+    fn poll_read(
+        self: Pin<&mut Self>,
+        cx: &mut Context<'_>,
+        buf: &mut ReadBuf<'_>,
+    ) -> Poll<io::Result<()>> {
+        let mut this = self.project();
+        let (buffer, inner) = match this.inner.as_mut().project() {
+            DecompressedReaderInnerProj::Gzip(inner) => return inner.poll_read(cx, buf),
+            DecompressedReaderInnerProj::Bzip2(inner) => return inner.poll_read(cx, buf),
+            DecompressedReaderInnerProj::Xz(inner) => return inner.poll_read(cx, buf),
+            DecompressedReaderInnerProj::Unknown { buffer, inner } => (buffer, inner),
+        };
+
+        let mut our_buf = ReadBuf::new(buffer);
+        if let Err(e) = ready!(inner.as_pin_mut().unwrap().poll_read(cx, &mut our_buf)) {
+            return Poll::Ready(Err(e));
+        }
+
+        let data = our_buf.filled();
+        if data.len() >= BYTES_NEEDED {
+            if let Some(algorithm) = Algorithm::from_magic(data) {
+                this.inner.as_mut().switch_to(algorithm);
+            } else {
+                return Poll::Ready(Err(io::Error::new(
+                    io::ErrorKind::InvalidData,
+                    "tar data not gz, bzip2, or xz compressed",
+                )));
+            }
+            this.inner.poll_read(cx, buf)
+        } else {
+            cx.waker().wake_by_ref();
+            Poll::Pending
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use std::path::Path;
+
+    use async_compression::tokio::bufread::GzipEncoder;
+    use futures::TryStreamExt;
+    use rstest::rstest;
+    use tokio::io::{AsyncReadExt, BufReader};
+    use tokio_tar::Archive;
+
+    use super::*;
+
+    #[tokio::test]
+    async fn gzip() {
+        let data = b"abcdefghijk";
+        let mut enc = GzipEncoder::new(&data[..]);
+        let mut gzipped = vec![];
+        enc.read_to_end(&mut gzipped).await.unwrap();
+
+        let mut reader = DecompressedReader::new(BufReader::new(&gzipped[..]));
+        let mut round_tripped = vec![];
+        reader.read_to_end(&mut round_tripped).await.unwrap();
+
+        assert_eq!(data[..], round_tripped[..]);
+    }
+
+    #[rstest]
+    #[case::gzip(include_bytes!("../tests/blob.tar.gz"))]
+    #[case::bzip2(include_bytes!("../tests/blob.tar.bz2"))]
+    #[case::xz(include_bytes!("../tests/blob.tar.xz"))]
+    #[tokio::test]
+    async fn compressed_tar(#[case] data: &[u8]) {
+        let reader = DecompressedReader::new(BufReader::new(data));
+        let mut archive = Archive::new(reader);
+        let mut entries: Vec<_> = archive.entries().unwrap().try_collect().await.unwrap();
+
+        assert_eq!(entries.len(), 1);
+        assert_eq!(entries[0].path().unwrap().as_ref(), Path::new("empty"));
+        let mut data = String::new();
+        entries[0].read_to_string(&mut data).await.unwrap();
+        assert_eq!(data, "");
+    }
+}
diff --git a/tvix/glue/src/fetchers/mod.rs b/tvix/glue/src/fetchers/mod.rs
new file mode 100644
index 0000000000..342dfd84e8
--- /dev/null
+++ b/tvix/glue/src/fetchers/mod.rs
@@ -0,0 +1,445 @@
+use futures::TryStreamExt;
+use md5::Md5;
+use nix_compat::{
+    nixhash::{CAHash, HashAlgo, NixHash},
+    store_path::{build_ca_path, BuildStorePathError, StorePathRef},
+};
+use sha1::Sha1;
+use sha2::{digest::Output, Digest, Sha256, Sha512};
+use tokio::io::{AsyncBufRead, AsyncRead, AsyncWrite};
+use tokio_util::io::InspectReader;
+use tracing::warn;
+use tvix_castore::{
+    blobservice::BlobService,
+    directoryservice::DirectoryService,
+    proto::{node::Node, FileNode},
+};
+use tvix_store::{pathinfoservice::PathInfoService, proto::PathInfo};
+use url::Url;
+
+use crate::builtins::FetcherError;
+
+mod decompression;
+use decompression::DecompressedReader;
+
+/// Representing options for doing a fetch.
+#[derive(Clone, Eq, PartialEq)]
+pub enum Fetch {
+    /// Fetch a literal file from the given URL, with an optional expected
+    /// NixHash of it.
+    /// TODO: check if this is *always* sha256, and if so, make it [u8; 32].
+    URL(Url, Option<NixHash>),
+
+    /// Fetch a tarball from the given URL and unpack.
+    /// The file must be a tape archive (.tar), optionally compressed with gzip,
+    /// bzip2 or xz.
+    /// The top-level path component of the files in the tarball is removed,
+    /// so it is best if the tarball contains a single directory at top level.
+    /// Optionally, a sha256 digest can be provided to verify the unpacked
+    /// contents against.
+    Tarball(Url, Option<[u8; 32]>),
+
+    /// TODO
+    Git(),
+}
+
+// Drops potentially sensitive username and password from a URL.
+fn redact_url(url: &Url) -> Url {
+    let mut url = url.to_owned();
+    if !url.username().is_empty() {
+        let _ = url.set_username("redacted");
+    }
+
+    if url.password().is_some() {
+        let _ = url.set_password(Some("redacted"));
+    }
+
+    url
+}
+
+impl std::fmt::Debug for Fetch {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            Fetch::URL(url, exp_hash) => {
+                let url = redact_url(url);
+                if let Some(exp_hash) = exp_hash {
+                    write!(f, "URL [url: {}, exp_hash: Some({})]", &url, exp_hash)
+                } else {
+                    write!(f, "URL [url: {}, exp_hash: None]", &url)
+                }
+            }
+            Fetch::Tarball(url, exp_digest) => {
+                let url = redact_url(url);
+                if let Some(exp_digest) = exp_digest {
+                    write!(
+                        f,
+                        "Tarball [url: {}, exp_hash: Some({})]",
+                        url,
+                        NixHash::Sha256(*exp_digest)
+                    )
+                } else {
+                    write!(f, "Tarball [url: {}, exp_hash: None]", url)
+                }
+            }
+            Fetch::Git() => todo!(),
+        }
+    }
+}
+
+impl Fetch {
+    /// If the [Fetch] contains an expected hash upfront, returns the resulting
+    /// store path.
+    /// This doesn't do any fetching.
+    pub fn store_path<'a>(
+        &self,
+        name: &'a str,
+    ) -> Result<Option<StorePathRef<'a>>, BuildStorePathError> {
+        let ca_hash = match self {
+            Fetch::URL(_, Some(nixhash)) => CAHash::Flat(nixhash.clone()),
+            Fetch::Tarball(_, Some(nar_sha256)) => CAHash::Nar(NixHash::Sha256(*nar_sha256)),
+            _ => return Ok(None),
+        };
+
+        // calculate the store path of this fetch
+        build_ca_path(name, &ca_hash, Vec::<String>::new(), false).map(Some)
+    }
+}
+
+/// Knows how to fetch a given [Fetch].
+pub struct Fetcher<BS, DS, PS> {
+    http_client: reqwest::Client,
+    blob_service: BS,
+    directory_service: DS,
+    path_info_service: PS,
+}
+
+impl<BS, DS, PS> Fetcher<BS, DS, PS> {
+    pub fn new(blob_service: BS, directory_service: DS, path_info_service: PS) -> Self {
+        Self {
+            http_client: reqwest::Client::new(),
+            blob_service,
+            directory_service,
+            path_info_service,
+        }
+    }
+
+    /// Constructs a HTTP request to the passed URL, and returns a AsyncReadBuf to it.
+    /// In case the URI uses the file:// scheme, use tokio::fs to open it.
+    async fn download(&self, url: Url) -> Result<Box<dyn AsyncBufRead + Unpin>, FetcherError> {
+        match url.scheme() {
+            "file" => {
+                let f = tokio::fs::File::open(url.to_file_path().map_err(|_| {
+                    // "Returns Err if the host is neither empty nor "localhost"
+                    // (except on Windows, where file: URLs may have a non-local host)"
+                    FetcherError::Io(std::io::Error::new(
+                        std::io::ErrorKind::Other,
+                        "invalid host for file:// scheme",
+                    ))
+                })?)
+                .await?;
+                Ok(Box::new(tokio::io::BufReader::new(f)))
+            }
+            _ => {
+                let resp = self.http_client.get(url).send().await?;
+                Ok(Box::new(tokio_util::io::StreamReader::new(
+                    resp.bytes_stream().map_err(|e| {
+                        let e = e.without_url();
+                        warn!(%e, "failed to get response body");
+                        std::io::Error::new(std::io::ErrorKind::BrokenPipe, e)
+                    }),
+                )))
+            }
+        }
+    }
+}
+
+/// Copies all data from the passed reader to the passed writer.
+/// Afterwards, it also returns the resulting [Digest], as well as the number of
+/// bytes copied.
+/// The exact hash function used is left generic over all [Digest].
+async fn hash<D: Digest + std::io::Write>(
+    mut r: impl AsyncRead + Unpin,
+    mut w: impl AsyncWrite + Unpin,
+) -> std::io::Result<(Output<D>, u64)> {
+    let mut hasher = D::new();
+    let bytes_copied = tokio::io::copy(
+        &mut InspectReader::new(&mut r, |d| hasher.write_all(d).unwrap()),
+        &mut w,
+    )
+    .await?;
+    Ok((hasher.finalize(), bytes_copied))
+}
+
+impl<BS, DS, PS> Fetcher<BS, DS, PS>
+where
+    BS: BlobService + Clone + 'static,
+    DS: DirectoryService + Clone,
+    PS: PathInfoService,
+{
+    /// Ingest the data from a specified [Fetch].
+    /// On success, return the root node, a content digest and length.
+    /// Returns an error if there was a failure during fetching, or the contents
+    /// didn't match the previously communicated hash contained inside the FetchArgs.
+    pub async fn ingest(&self, fetch: Fetch) -> Result<(Node, CAHash, u64), FetcherError> {
+        match fetch {
+            Fetch::URL(url, exp_hash) => {
+                // Construct a AsyncRead reading from the data as its downloaded.
+                let mut r = self.download(url.clone()).await?;
+
+                // Construct a AsyncWrite to write into the BlobService.
+                let mut blob_writer = self.blob_service.open_write().await;
+
+                // Copy the contents from the download reader to the blob writer.
+                // Calculate the digest of the file received, depending on the
+                // communicated expected hash (or sha256 if none provided).
+                let (actual_hash, blob_size) = match exp_hash
+                    .as_ref()
+                    .map(NixHash::algo)
+                    .unwrap_or_else(|| HashAlgo::Sha256)
+                {
+                    HashAlgo::Sha256 => hash::<Sha256>(&mut r, &mut blob_writer).await.map(
+                        |(digest, bytes_written)| (NixHash::Sha256(digest.into()), bytes_written),
+                    )?,
+                    HashAlgo::Md5 => hash::<Md5>(&mut r, &mut blob_writer).await.map(
+                        |(digest, bytes_written)| (NixHash::Md5(digest.into()), bytes_written),
+                    )?,
+                    HashAlgo::Sha1 => hash::<Sha1>(&mut r, &mut blob_writer).await.map(
+                        |(digest, bytes_written)| (NixHash::Sha1(digest.into()), bytes_written),
+                    )?,
+                    HashAlgo::Sha512 => hash::<Sha512>(&mut r, &mut blob_writer).await.map(
+                        |(digest, bytes_written)| {
+                            (NixHash::Sha512(Box::new(digest.into())), bytes_written)
+                        },
+                    )?,
+                };
+
+                if let Some(exp_hash) = exp_hash {
+                    if exp_hash != actual_hash {
+                        return Err(FetcherError::HashMismatch {
+                            url,
+                            wanted: exp_hash,
+                            got: actual_hash,
+                        });
+                    }
+                }
+
+                // Construct and return the FileNode describing the downloaded contents.
+                Ok((
+                    Node::File(FileNode {
+                        name: vec![].into(),
+                        digest: blob_writer.close().await?.into(),
+                        size: blob_size,
+                        executable: false,
+                    }),
+                    CAHash::Flat(actual_hash),
+                    blob_size,
+                ))
+            }
+            Fetch::Tarball(url, exp_nar_sha256) => {
+                // Construct a AsyncRead reading from the data as its downloaded.
+                let r = self.download(url.clone()).await?;
+
+                // Pop compression.
+                let r = DecompressedReader::new(r);
+                // Open the archive.
+                let archive = tokio_tar::Archive::new(r);
+
+                // Ingest the archive, get the root node
+                let node = tvix_castore::import::archive::ingest_archive(
+                    self.blob_service.clone(),
+                    self.directory_service.clone(),
+                    archive,
+                )
+                .await?;
+
+                // If an expected NAR sha256 was provided, compare with the one
+                // calculated from our root node.
+                // Even if no expected NAR sha256 has been provided, we need
+                // the actual one later.
+                let (nar_size, actual_nar_sha256) = self
+                    .path_info_service
+                    .calculate_nar(&node)
+                    .await
+                    .map_err(|e| {
+                        // convert the generic Store error to an IO error.
+                        FetcherError::Io(e.into())
+                    })?;
+
+                if let Some(exp_nar_sha256) = exp_nar_sha256 {
+                    if exp_nar_sha256 != actual_nar_sha256 {
+                        return Err(FetcherError::HashMismatch {
+                            url,
+                            wanted: NixHash::Sha256(exp_nar_sha256),
+                            got: NixHash::Sha256(actual_nar_sha256),
+                        });
+                    }
+                }
+
+                Ok((
+                    node,
+                    CAHash::Nar(NixHash::Sha256(actual_nar_sha256)),
+                    nar_size,
+                ))
+            }
+            Fetch::Git() => todo!(),
+        }
+    }
+
+    /// Ingests the data from a specified [Fetch], persists the returned node
+    /// in the PathInfoService, and returns the calculated StorePath, as well as
+    /// the root node pointing to the contents.
+    /// The root node can be used to descend into the data without doing the
+    /// lookup to the PathInfoService again.
+    pub async fn ingest_and_persist<'a>(
+        &self,
+        name: &'a str,
+        fetch: Fetch,
+    ) -> Result<(StorePathRef<'a>, Node), FetcherError> {
+        // Fetch file, return the (unnamed) (File)Node of its contents, ca hash and filesize.
+        let (node, ca_hash, size) = self.ingest(fetch).await?;
+
+        // Calculate the store path to return later, which is done with the ca_hash.
+        let store_path = build_ca_path(name, &ca_hash, Vec::<String>::new(), false)?;
+
+        // Rename the node name to match the Store Path.
+        let node = node.rename(store_path.to_string().into());
+
+        // If the resulting hash is not a CAHash::Nar, we also need to invoke
+        // `calculate_nar` to calculate this representation, as it's required in
+        // the [PathInfo].
+        let (nar_size, nar_sha256) = match &ca_hash {
+            CAHash::Flat(_nix_hash) => self
+                .path_info_service
+                .calculate_nar(&node)
+                .await
+                .map_err(|e| FetcherError::Io(e.into()))?,
+            CAHash::Nar(NixHash::Sha256(nar_sha256)) => (size, *nar_sha256),
+            CAHash::Nar(_) => unreachable!("Tvix bug: fetch returned non-sha256 CAHash::Nar"),
+            CAHash::Text(_) => unreachable!("Tvix bug: fetch returned CAHash::Text"),
+        };
+
+        // Construct the PathInfo and persist it.
+        let path_info = PathInfo {
+            node: Some(tvix_castore::proto::Node { node: Some(node) }),
+            references: vec![],
+            narinfo: Some(tvix_store::proto::NarInfo {
+                nar_size,
+                nar_sha256: nar_sha256.to_vec().into(),
+                signatures: vec![],
+                reference_names: vec![],
+                deriver: None,
+                ca: Some(ca_hash.into()),
+            }),
+        };
+
+        let path_info = self
+            .path_info_service
+            .put(path_info)
+            .await
+            .map_err(|e| FetcherError::Io(e.into()))?;
+
+        Ok((store_path, path_info.node.unwrap().node.unwrap()))
+    }
+}
+
+/// Attempts to mimic `nix::libutil::baseNameOf`
+pub(crate) fn url_basename(s: &str) -> &str {
+    if s.is_empty() {
+        return "";
+    }
+
+    let mut last = s.len() - 1;
+    if s.chars().nth(last).unwrap() == '/' && last > 0 {
+        last -= 1;
+    }
+
+    if last == 0 {
+        return "";
+    }
+
+    let pos = match s[..=last].rfind('/') {
+        Some(pos) => {
+            if pos == last - 1 {
+                0
+            } else {
+                pos
+            }
+        }
+        None => 0,
+    };
+
+    &s[(pos + 1)..=last]
+}
+
+#[cfg(test)]
+mod tests {
+    mod fetch {
+        use nix_compat::nixbase32;
+
+        use crate::fetchers::Fetch;
+
+        use super::super::*;
+
+        #[test]
+        fn fetchurl_store_path() {
+            let url = Url::parse("https://raw.githubusercontent.com/aaptel/notmuch-extract-patch/f732a53e12a7c91a06755ebfab2007adc9b3063b/notmuch-extract-patch").unwrap();
+            let exp_hash = NixHash::Sha256(
+                nixbase32::decode_fixed("0nawkl04sj7psw6ikzay7kydj3dhd0fkwghcsf5rzaw4bmp4kbax")
+                    .unwrap(),
+            );
+
+            let fetch = Fetch::URL(url, Some(exp_hash));
+            assert_eq!(
+                "06qi00hylriyfm0nl827crgjvbax84mz-notmuch-extract-patch",
+                &fetch
+                    .store_path("notmuch-extract-patch")
+                    .unwrap()
+                    .unwrap()
+                    .to_string(),
+            )
+        }
+
+        #[test]
+        fn fetch_tarball_store_path() {
+            let url = Url::parse("https://github.com/NixOS/nixpkgs/archive/91050ea1e57e50388fa87a3302ba12d188ef723a.tar.gz").unwrap();
+            let exp_nixbase32 =
+                nixbase32::decode_fixed("1hf6cgaci1n186kkkjq106ryf8mmlq9vnwgfwh625wa8hfgdn4dm")
+                    .unwrap();
+            let fetch = Fetch::Tarball(url, Some(exp_nixbase32));
+
+            assert_eq!(
+                "7adgvk5zdfq4pwrhsm3n9lzypb12gw0g-source",
+                &fetch.store_path("source").unwrap().unwrap().to_string(),
+            )
+        }
+    }
+
+    mod url_basename {
+        use super::super::*;
+
+        #[test]
+        fn empty_path() {
+            assert_eq!(url_basename(""), "");
+        }
+
+        #[test]
+        fn path_on_root() {
+            assert_eq!(url_basename("/dir"), "dir");
+        }
+
+        #[test]
+        fn relative_path() {
+            assert_eq!(url_basename("dir/foo"), "foo");
+        }
+
+        #[test]
+        fn root_with_trailing_slash() {
+            assert_eq!(url_basename("/"), "");
+        }
+
+        #[test]
+        fn trailing_slash() {
+            assert_eq!(url_basename("/dir/"), "dir");
+        }
+    }
+}
diff --git a/tvix/glue/src/fetchurl.nix b/tvix/glue/src/fetchurl.nix
new file mode 100644
index 0000000000..3f182a5a31
--- /dev/null
+++ b/tvix/glue/src/fetchurl.nix
@@ -0,0 +1,53 @@
+# SPDX-License-Identifier: LGPL-2.1
+#
+# This file is vendored from C++ Nix, as it needs to be bundled with
+# an evaluator to be able to evaluate nixpkgs.
+#
+# Source: https://github.com/NixOS/nix/blob/2.3.16/corepkgs/fetchurl.nix
+
+{ system ? "" # obsolete
+, url
+, hash ? "" # an SRI hash
+
+  # Legacy hash specification
+, md5 ? ""
+, sha1 ? ""
+, sha256 ? ""
+, sha512 ? ""
+, outputHash ? if hash != "" then hash else if sha512 != "" then sha512 else if sha1 != "" then sha1 else if md5 != "" then md5 else sha256
+, outputHashAlgo ? if hash != "" then "" else if sha512 != "" then "sha512" else if sha1 != "" then "sha1" else if md5 != "" then "md5" else "sha256"
+
+, executable ? false
+, unpack ? false
+, name ? baseNameOf (toString url)
+}:
+
+derivation {
+  builder = "builtin:fetchurl";
+
+  # New-style output content requirements.
+  inherit outputHashAlgo outputHash;
+  outputHashMode = if unpack || executable then "recursive" else "flat";
+
+  inherit name url executable unpack;
+
+  system = "builtin";
+
+  # No need to double the amount of network traffic
+  preferLocalBuild = true;
+
+  impureEnvVars = [
+    # We borrow these environment variables from the caller to allow
+    # easy proxy configuration.  This is impure, but a fixed-output
+    # derivation like fetchurl is allowed to do so since its result is
+    # by definition pure.
+    "http_proxy"
+    "https_proxy"
+    "ftp_proxy"
+    "all_proxy"
+    "no_proxy"
+  ];
+
+  # To make "nix-prefetch-url" work.
+  urls = [ url ];
+}
diff --git a/tvix/glue/src/known_paths.rs b/tvix/glue/src/known_paths.rs
new file mode 100644
index 0000000000..290c9d5b69
--- /dev/null
+++ b/tvix/glue/src/known_paths.rs
@@ -0,0 +1,289 @@
+//! This module implements logic required for persisting known paths
+//! during an evaluation.
+//!
+//! Tvix needs to be able to keep track of each Nix store path that it
+//! knows about during the scope of a single evaluation and its
+//! related builds.
+//!
+//! This data is required to find the derivation needed to actually trigger the
+//! build, if necessary.
+
+use nix_compat::{
+    derivation::Derivation,
+    store_path::{BuildStorePathError, StorePath, StorePathRef},
+};
+use std::collections::HashMap;
+
+use crate::fetchers::Fetch;
+
+/// Struct keeping track of all known Derivations in the current evaluation.
+/// This keeps both the Derivation struct, as well as the "Hash derivation
+/// modulo".
+#[derive(Debug, Default)]
+pub struct KnownPaths {
+    /// All known derivation or FOD hashes.
+    ///
+    /// Keys are derivation paths, values are a tuple of the "hash derivation
+    /// modulo" and the Derivation struct itself.
+    derivations: HashMap<StorePath, ([u8; 32], Derivation)>,
+
+    /// A map from output path to (one) drv path.
+    /// Note that in the case of FODs, multiple drvs can produce the same output
+    /// path. We use one of them.
+    outputs_to_drvpath: HashMap<StorePath, StorePath>,
+
+    /// A map from output path to fetches (and their names).
+    outputs_to_fetches: HashMap<StorePath, (String, Fetch)>,
+}
+
+impl KnownPaths {
+    /// Fetch the opaque "hash derivation modulo" for a given derivation path.
+    pub fn get_hash_derivation_modulo(&self, drv_path: &StorePath) -> Option<&[u8; 32]> {
+        self.derivations
+            .get(drv_path)
+            .map(|(hash_derivation_modulo, _derivation)| hash_derivation_modulo)
+    }
+
+    /// Return a reference to the Derivation for a given drv path.
+    pub fn get_drv_by_drvpath(&self, drv_path: &StorePath) -> Option<&Derivation> {
+        self.derivations
+            .get(drv_path)
+            .map(|(_hash_derivation_modulo, derivation)| derivation)
+    }
+
+    /// Return the drv path of the derivation producing the passed output path.
+    /// Note there can be multiple Derivations producing the same output path in
+    /// flight; this function will only return one of them.
+    pub fn get_drv_path_for_output_path(&self, output_path: &StorePath) -> Option<&StorePath> {
+        self.outputs_to_drvpath.get(output_path)
+    }
+
+    /// Insert a new [Derivation] into this struct.
+    /// The Derivation struct must pass validation, and its output paths need to
+    /// be fully calculated.
+    /// All input derivations this refers to must also be inserted to this
+    /// struct.
+    pub fn add_derivation(&mut self, drv_path: StorePath, drv: Derivation) {
+        // check input derivations to have been inserted.
+        #[cfg(debug_assertions)]
+        {
+            for input_drv_path in drv.input_derivations.keys() {
+                debug_assert!(self.derivations.contains_key(input_drv_path));
+            }
+        }
+
+        // compute the hash derivation modulo
+        let hash_derivation_modulo = drv.hash_derivation_modulo(|drv_path| {
+            self.get_hash_derivation_modulo(&drv_path.to_owned())
+                .unwrap_or_else(|| panic!("{} not found", drv_path))
+                .to_owned()
+        });
+
+        // For all output paths, update our lookup table.
+        // We only write into the lookup table once.
+        for output in drv.outputs.values() {
+            self.outputs_to_drvpath
+                .entry(output.path.as_ref().expect("missing store path").clone())
+                .or_insert(drv_path.to_owned());
+        }
+
+        // insert the derivation itself
+        #[allow(unused_variables)] // assertions on this only compiled in debug builds
+        let old = self
+            .derivations
+            .insert(drv_path.to_owned(), (hash_derivation_modulo, drv));
+
+        #[cfg(debug_assertions)]
+        {
+            if let Some(old) = old {
+                debug_assert!(
+                    old.0 == hash_derivation_modulo,
+                    "hash derivation modulo for a given derivation should always be calculated the same"
+                );
+            }
+        }
+    }
+
+    /// Insert a new [Fetch] into this struct, which *must* have an expected
+    /// hash (otherwise we wouldn't be able to calculate the store path).
+    /// Fetches without a known hash need to be fetched inside builtins.
+    pub fn add_fetch<'a>(
+        &mut self,
+        fetch: Fetch,
+        name: &'a str,
+    ) -> Result<StorePathRef<'a>, BuildStorePathError> {
+        let store_path = fetch
+            .store_path(name)?
+            .expect("Tvix bug: fetch must have an expected hash");
+        // insert the fetch.
+        self.outputs_to_fetches
+            .insert(store_path.to_owned(), (name.to_owned(), fetch));
+
+        Ok(store_path)
+    }
+
+    /// Return the name and fetch producing the passed output path.
+    /// Note there can also be (multiple) Derivations producing the same output path.
+    pub fn get_fetch_for_output_path(&self, output_path: &StorePath) -> Option<(String, Fetch)> {
+        self.outputs_to_fetches
+            .get(output_path)
+            .map(|(name, fetch)| (name.to_owned(), fetch.to_owned()))
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use nix_compat::{derivation::Derivation, nixbase32, nixhash::NixHash, store_path::StorePath};
+    use url::Url;
+
+    use crate::fetchers::Fetch;
+
+    use super::KnownPaths;
+    use hex_literal::hex;
+    use lazy_static::lazy_static;
+
+    lazy_static! {
+        static ref BAR_DRV: Derivation = Derivation::from_aterm_bytes(include_bytes!(
+            "tests/ss2p4wmxijn652haqyd7dckxwl4c7hxx-bar.drv"
+        ))
+        .expect("must parse");
+        static ref FOO_DRV: Derivation = Derivation::from_aterm_bytes(include_bytes!(
+            "tests/ch49594n9avinrf8ip0aslidkc4lxkqv-foo.drv"
+        ))
+        .expect("must parse");
+        static ref BAR_DRV_PATH: StorePath =
+            StorePath::from_bytes(b"ss2p4wmxijn652haqyd7dckxwl4c7hxx-bar.drv").expect("must parse");
+        static ref FOO_DRV_PATH: StorePath =
+            StorePath::from_bytes(b"ch49594n9avinrf8ip0aslidkc4lxkqv-foo.drv").expect("must parse");
+        static ref BAR_OUT_PATH: StorePath =
+            StorePath::from_bytes(b"mp57d33657rf34lzvlbpfa1gjfv5gmpg-bar").expect("must parse");
+        static ref FOO_OUT_PATH: StorePath =
+            StorePath::from_bytes(b"fhaj6gmwns62s6ypkcldbaj2ybvkhx3p-foo").expect("must parse");
+
+        static ref FETCH_URL : Fetch = Fetch::URL(
+            Url::parse("https://raw.githubusercontent.com/aaptel/notmuch-extract-patch/f732a53e12a7c91a06755ebfab2007adc9b3063b/notmuch-extract-patch").unwrap(),
+            Some(NixHash::Sha256(nixbase32::decode_fixed("0nawkl04sj7psw6ikzay7kydj3dhd0fkwghcsf5rzaw4bmp4kbax").unwrap()))
+        );
+        static ref FETCH_URL_OUT_PATH: StorePath = StorePath::from_bytes(b"06qi00hylriyfm0nl827crgjvbax84mz-notmuch-extract-patch").unwrap();
+
+        static ref FETCH_TARBALL : Fetch = Fetch::Tarball(
+            Url::parse("https://github.com/NixOS/nixpkgs/archive/91050ea1e57e50388fa87a3302ba12d188ef723a.tar.gz").unwrap(),
+            Some(nixbase32::decode_fixed("1hf6cgaci1n186kkkjq106ryf8mmlq9vnwgfwh625wa8hfgdn4dm").unwrap())
+        );
+        static ref FETCH_TARBALL_OUT_PATH: StorePath = StorePath::from_bytes(b"7adgvk5zdfq4pwrhsm3n9lzypb12gw0g-source").unwrap();
+    }
+
+    /// ensure we don't allow acdding a Derivation that depends on another,
+    /// not-yet-added Derivation.
+    #[test]
+    #[should_panic]
+    fn drv_reject_if_missing_input_drv() {
+        let mut known_paths = KnownPaths::default();
+
+        // FOO_DRV depends on BAR_DRV, which wasn't added.
+        known_paths.add_derivation(FOO_DRV_PATH.clone(), FOO_DRV.clone());
+    }
+
+    #[test]
+    fn drv_happy_path() {
+        let mut known_paths = KnownPaths::default();
+
+        // get_drv_by_drvpath should return None for non-existing Derivations,
+        // same as get_hash_derivation_modulo and get_drv_path_for_output_path
+        assert_eq!(None, known_paths.get_drv_by_drvpath(&BAR_DRV_PATH));
+        assert_eq!(None, known_paths.get_hash_derivation_modulo(&BAR_DRV_PATH));
+        assert_eq!(
+            None,
+            known_paths.get_drv_path_for_output_path(&BAR_OUT_PATH)
+        );
+
+        // Add BAR_DRV
+        known_paths.add_derivation(BAR_DRV_PATH.clone(), BAR_DRV.clone());
+
+        // We should get it back
+        assert_eq!(
+            Some(&BAR_DRV.clone()),
+            known_paths.get_drv_by_drvpath(&BAR_DRV_PATH)
+        );
+
+        // Test get_drv_path_for_output_path
+        assert_eq!(
+            Some(&BAR_DRV_PATH.clone()),
+            known_paths.get_drv_path_for_output_path(&BAR_OUT_PATH)
+        );
+
+        // It should be possible to get the hash derivation modulo.
+        assert_eq!(
+            Some(&hex!(
+                "c79aebd0ce3269393d4a1fde2cbd1d975d879b40f0bf40a48f550edc107fd5df"
+            )),
+            known_paths.get_hash_derivation_modulo(&BAR_DRV_PATH.clone())
+        );
+
+        // Now insert FOO_DRV too. It shouldn't panic, as BAR_DRV is already
+        // added.
+        known_paths.add_derivation(FOO_DRV_PATH.clone(), FOO_DRV.clone());
+
+        assert_eq!(
+            Some(&FOO_DRV.clone()),
+            known_paths.get_drv_by_drvpath(&FOO_DRV_PATH)
+        );
+        assert_eq!(
+            Some(&hex!(
+                "af030d36d63d3d7f56a71adaba26b36f5fa1f9847da5eed953ed62e18192762f"
+            )),
+            known_paths.get_hash_derivation_modulo(&FOO_DRV_PATH.clone())
+        );
+
+        // Test get_drv_path_for_output_path
+        assert_eq!(
+            Some(&FOO_DRV_PATH.clone()),
+            known_paths.get_drv_path_for_output_path(&FOO_OUT_PATH)
+        );
+    }
+
+    #[test]
+    fn fetch_happy_path() {
+        let mut known_paths = KnownPaths::default();
+
+        // get_fetch_for_output_path should return None for new fetches.
+        assert!(known_paths
+            .get_fetch_for_output_path(&FETCH_TARBALL_OUT_PATH)
+            .is_none());
+
+        // add_fetch should return the properly calculated store paths.
+        assert_eq!(
+            *FETCH_TARBALL_OUT_PATH,
+            known_paths
+                .add_fetch(FETCH_TARBALL.clone(), "source")
+                .unwrap()
+                .to_owned()
+        );
+
+        assert_eq!(
+            *FETCH_URL_OUT_PATH,
+            known_paths
+                .add_fetch(FETCH_URL.clone(), "notmuch-extract-patch")
+                .unwrap()
+                .to_owned()
+        );
+
+        // We should be able to get these fetches out, when asking for their out path.
+        let (got_name, got_fetch) = known_paths
+            .get_fetch_for_output_path(&FETCH_URL_OUT_PATH)
+            .expect("must be some");
+
+        assert_eq!("notmuch-extract-patch", got_name);
+        assert_eq!(FETCH_URL.clone(), got_fetch);
+
+        // … multiple times.
+        let (got_name, got_fetch) = known_paths
+            .get_fetch_for_output_path(&FETCH_URL_OUT_PATH)
+            .expect("must be some");
+
+        assert_eq!("notmuch-extract-patch", got_name);
+        assert_eq!(FETCH_URL.clone(), got_fetch);
+    }
+
+    // TODO: add test panicking about missing digest
+}
diff --git a/tvix/glue/src/lib.rs b/tvix/glue/src/lib.rs
new file mode 100644
index 0000000000..2e5a3be103
--- /dev/null
+++ b/tvix/glue/src/lib.rs
@@ -0,0 +1,23 @@
+pub mod builtins;
+pub mod fetchers;
+pub mod known_paths;
+pub mod refscan;
+pub mod tvix_build;
+pub mod tvix_io;
+pub mod tvix_store_io;
+
+#[cfg(test)]
+mod tests;
+
+/// Tell the Evaluator to resolve `<nix>` to the path `/__corepkgs__`,
+/// which has special handling in [tvix_io::TvixIO].
+/// This is used in nixpkgs to import `fetchurl.nix` from `<nix>`.
+pub fn configure_nix_path<IO>(
+    eval: &mut tvix_eval::Evaluation<IO>,
+    nix_search_path: &Option<String>,
+) {
+    eval.nix_path = nix_search_path
+        .as_ref()
+        .map(|p| format!("nix=/__corepkgs__:{}", p))
+        .or_else(|| Some("nix=/__corepkgs__".to_string()));
+}
diff --git a/tvix/glue/src/refscan.rs b/tvix/glue/src/refscan.rs
new file mode 100644
index 0000000000..0e0bb6c778
--- /dev/null
+++ b/tvix/glue/src/refscan.rs
@@ -0,0 +1,115 @@
+//! Simple scanner for non-overlapping, known references of Nix store paths in a
+//! given string.
+//!
+//! This is used for determining build references (see
+//! //tvix/eval/docs/build-references.md for more details).
+//!
+//! The scanner itself is using the Wu-Manber string-matching algorithm, using
+//! our fork of the `wu-mamber` crate.
+
+use std::collections::BTreeSet;
+use wu_manber::TwoByteWM;
+
+pub const STORE_PATH_LEN: usize = "/nix/store/00000000000000000000000000000000".len();
+
+/// Represents a "primed" reference scanner with an automaton that knows the set
+/// of store paths to scan for.
+pub struct ReferenceScanner<P: Ord + AsRef<[u8]>> {
+    candidates: Vec<P>,
+    searcher: Option<TwoByteWM>,
+    matches: Vec<usize>,
+}
+
+impl<P: Clone + Ord + AsRef<[u8]>> ReferenceScanner<P> {
+    /// Construct a new `ReferenceScanner` that knows how to scan for the given
+    /// candidate store paths.
+    pub fn new(candidates: Vec<P>) -> Self {
+        let searcher = if candidates.is_empty() {
+            None
+        } else {
+            Some(TwoByteWM::new(&candidates))
+        };
+
+        ReferenceScanner {
+            searcher,
+            candidates,
+            matches: Default::default(),
+        }
+    }
+
+    /// Scan the given str for all non-overlapping matches and collect them
+    /// in the scanner.
+    pub fn scan<S: AsRef<[u8]>>(&mut self, haystack: S) {
+        if haystack.as_ref().len() < STORE_PATH_LEN {
+            return;
+        }
+
+        if let Some(searcher) = &self.searcher {
+            for m in searcher.find(haystack) {
+                self.matches.push(m.pat_idx);
+            }
+        }
+    }
+
+    /// Finalise the reference scanner and return the resulting matches.
+    pub fn finalise(self) -> BTreeSet<P> {
+        self.matches
+            .into_iter()
+            .map(|idx| self.candidates[idx].clone())
+            .collect()
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    // The actual derivation of `nixpkgs.hello`.
+    const HELLO_DRV: &str = r#"Derive([("out","/nix/store/33l4p0pn0mybmqzaxfkpppyh7vx1c74p-hello-2.12.1","","")],[("/nix/store/6z1jfnqqgyqr221zgbpm30v91yfj3r45-bash-5.1-p16.drv",["out"]),("/nix/store/ap9g09fxbicj836zm88d56dn3ff4clxl-stdenv-linux.drv",["out"]),("/nix/store/pf80kikyxr63wrw56k00i1kw6ba76qik-hello-2.12.1.tar.gz.drv",["out"])],["/nix/store/9krlzvny65gdc8s7kpb6lkx8cd02c25b-default-builder.sh"],"x86_64-linux","/nix/store/4xw8n979xpivdc46a9ndcvyhwgif00hz-bash-5.1-p16/bin/bash",["-e","/nix/store/9krlzvny65gdc8s7kpb6lkx8cd02c25b-default-builder.sh"],[("buildInputs",""),("builder","/nix/store/4xw8n979xpivdc46a9ndcvyhwgif00hz-bash-5.1-p16/bin/bash"),("cmakeFlags",""),("configureFlags",""),("depsBuildBuild",""),("depsBuildBuildPropagated",""),("depsBuildTarget",""),("depsBuildTargetPropagated",""),("depsHostHost",""),("depsHostHostPropagated",""),("depsTargetTarget",""),("depsTargetTargetPropagated",""),("doCheck","1"),("doInstallCheck",""),("mesonFlags",""),("name","hello-2.12.1"),("nativeBuildInputs",""),("out","/nix/store/33l4p0pn0mybmqzaxfkpppyh7vx1c74p-hello-2.12.1"),("outputs","out"),("patches",""),("pname","hello"),("propagatedBuildInputs",""),("propagatedNativeBuildInputs",""),("src","/nix/store/pa10z4ngm0g83kx9mssrqzz30s84vq7k-hello-2.12.1.tar.gz"),("stdenv","/nix/store/cp65c8nk29qq5cl1wyy5qyw103cwmax7-stdenv-linux"),("strictDeps",""),("system","x86_64-linux"),("version","2.12.1")])"#;
+
+    #[test]
+    fn test_no_patterns() {
+        let mut scanner: ReferenceScanner<String> = ReferenceScanner::new(vec![]);
+
+        scanner.scan(HELLO_DRV);
+
+        let result = scanner.finalise();
+
+        assert_eq!(result.len(), 0);
+    }
+
+    #[test]
+    fn test_single_match() {
+        let mut scanner = ReferenceScanner::new(vec![
+            "/nix/store/4xw8n979xpivdc46a9ndcvyhwgif00hz-bash-5.1-p16".to_string(),
+        ]);
+        scanner.scan(HELLO_DRV);
+
+        let result = scanner.finalise();
+
+        assert_eq!(result.len(), 1);
+        assert!(result.contains("/nix/store/4xw8n979xpivdc46a9ndcvyhwgif00hz-bash-5.1-p16"));
+    }
+
+    #[test]
+    fn test_multiple_matches() {
+        let candidates = vec![
+            // these exist in the drv:
+            "/nix/store/33l4p0pn0mybmqzaxfkpppyh7vx1c74p-hello-2.12.1".to_string(),
+            "/nix/store/pf80kikyxr63wrw56k00i1kw6ba76qik-hello-2.12.1.tar.gz.drv".to_string(),
+            "/nix/store/cp65c8nk29qq5cl1wyy5qyw103cwmax7-stdenv-linux".to_string(),
+            // this doesn't:
+            "/nix/store/fn7zvafq26f0c8b17brs7s95s10ibfzs-emacs-28.2.drv".to_string(),
+        ];
+
+        let mut scanner = ReferenceScanner::new(candidates.clone());
+        scanner.scan(HELLO_DRV);
+
+        let result = scanner.finalise();
+        assert_eq!(result.len(), 3);
+
+        for c in candidates[..3].iter() {
+            assert!(result.contains(c));
+        }
+    }
+}
diff --git a/tvix/glue/src/tests/0hm2f1psjpcwg8fijsmr4wwxrx59s092-bar.drv b/tvix/glue/src/tests/0hm2f1psjpcwg8fijsmr4wwxrx59s092-bar.drv
new file mode 100644
index 0000000000..a4fea3c5f4
--- /dev/null
+++ b/tvix/glue/src/tests/0hm2f1psjpcwg8fijsmr4wwxrx59s092-bar.drv
@@ -0,0 +1 @@
+Derive([("out","/nix/store/4q0pg5zpfmznxscq3avycvf9xdvx50n3-bar","r:sha256","08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba")],[],[],":",":",[],[("builder",":"),("name","bar"),("out","/nix/store/4q0pg5zpfmznxscq3avycvf9xdvx50n3-bar"),("outputHash","08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba"),("outputHashAlgo","sha256"),("outputHashMode","recursive"),("system",":")])
\ No newline at end of file
diff --git a/tvix/glue/src/tests/blob.tar.bz2 b/tvix/glue/src/tests/blob.tar.bz2
new file mode 100644
index 0000000000..d74b913912
--- /dev/null
+++ b/tvix/glue/src/tests/blob.tar.bz2
Binary files differdiff --git a/tvix/glue/src/tests/blob.tar.gz b/tvix/glue/src/tests/blob.tar.gz
new file mode 100644
index 0000000000..c2bae55078
--- /dev/null
+++ b/tvix/glue/src/tests/blob.tar.gz
Binary files differdiff --git a/tvix/glue/src/tests/blob.tar.xz b/tvix/glue/src/tests/blob.tar.xz
new file mode 100644
index 0000000000..324a99d895
--- /dev/null
+++ b/tvix/glue/src/tests/blob.tar.xz
Binary files differdiff --git a/tvix/glue/src/tests/ch49594n9avinrf8ip0aslidkc4lxkqv-foo.drv b/tvix/glue/src/tests/ch49594n9avinrf8ip0aslidkc4lxkqv-foo.drv
new file mode 100644
index 0000000000..1699c2a75e
--- /dev/null
+++ b/tvix/glue/src/tests/ch49594n9avinrf8ip0aslidkc4lxkqv-foo.drv
@@ -0,0 +1 @@
+Derive([("out","/nix/store/fhaj6gmwns62s6ypkcldbaj2ybvkhx3p-foo","","")],[("/nix/store/ss2p4wmxijn652haqyd7dckxwl4c7hxx-bar.drv",["out"])],[],":",":",[],[("bar","/nix/store/mp57d33657rf34lzvlbpfa1gjfv5gmpg-bar"),("builder",":"),("name","foo"),("out","/nix/store/fhaj6gmwns62s6ypkcldbaj2ybvkhx3p-foo"),("system",":")])
\ No newline at end of file
diff --git a/tvix/glue/src/tests/mod.rs b/tvix/glue/src/tests/mod.rs
new file mode 100644
index 0000000000..8e1572b6e3
--- /dev/null
+++ b/tvix/glue/src/tests/mod.rs
@@ -0,0 +1,153 @@
+use std::{rc::Rc, sync::Arc};
+
+use pretty_assertions::assert_eq;
+use std::path::PathBuf;
+use tvix_build::buildservice::DummyBuildService;
+use tvix_castore::{
+    blobservice::{BlobService, MemoryBlobService},
+    directoryservice::{DirectoryService, MemoryDirectoryService},
+};
+use tvix_eval::{EvalIO, Value};
+use tvix_store::pathinfoservice::{MemoryPathInfoService, PathInfoService};
+
+use rstest::rstest;
+
+use crate::{
+    builtins::{add_derivation_builtins, add_fetcher_builtins, add_import_builtins},
+    configure_nix_path,
+    tvix_io::TvixIO,
+    tvix_store_io::TvixStoreIO,
+};
+
+fn eval_test(code_path: PathBuf, expect_success: bool) {
+    assert_eq!(
+        code_path.extension().unwrap(),
+        "nix",
+        "test files always end in .nix"
+    );
+    let exp_path = code_path.with_extension("exp");
+    let exp_xml_path = code_path.with_extension("exp.xml");
+
+    let code = std::fs::read_to_string(&code_path).expect("should be able to read test code");
+
+    if exp_xml_path.exists() {
+        // We can't test them at the moment because we don't have XML output yet.
+        // Checking for success / failure only is a bit disingenious.
+        return;
+    }
+
+    let blob_service = Arc::new(MemoryBlobService::default()) as Arc<dyn BlobService>;
+    let directory_service =
+        Arc::new(MemoryDirectoryService::default()) as Arc<dyn DirectoryService>;
+    let path_info_service = Box::new(MemoryPathInfoService::new(
+        blob_service.clone(),
+        directory_service.clone(),
+    )) as Box<dyn PathInfoService>;
+    let tokio_runtime = tokio::runtime::Runtime::new().unwrap();
+
+    let tvix_store_io = Rc::new(TvixStoreIO::new(
+        blob_service,
+        directory_service,
+        path_info_service.into(),
+        Arc::new(DummyBuildService::default()),
+        tokio_runtime.handle().clone(),
+    ));
+    // Wrap with TvixIO, so <nix/fetchurl.nix can be imported.
+    let mut eval = tvix_eval::Evaluation::new(
+        Box::new(TvixIO::new(tvix_store_io.clone() as Rc<dyn EvalIO>)) as Box<dyn EvalIO>,
+        true,
+    );
+
+    eval.strict = true;
+    add_derivation_builtins(&mut eval, tvix_store_io.clone());
+    add_fetcher_builtins(&mut eval, tvix_store_io.clone());
+    add_import_builtins(&mut eval, tvix_store_io.clone());
+    configure_nix_path(&mut eval, &None);
+
+    let result = eval.evaluate(code, Some(code_path.clone()));
+    let failed = match result.value {
+        Some(Value::Catchable(_)) => true,
+        _ => !result.errors.is_empty(),
+    };
+    if expect_success && failed {
+        panic!(
+            "{}: evaluation of eval-okay test should succeed, but failed with {:?}",
+            code_path.display(),
+            result.errors,
+        );
+    }
+
+    if !expect_success && failed {
+        return;
+    }
+
+    let value = result.value.unwrap();
+    let result_str = value.to_string();
+
+    if let Ok(exp) = std::fs::read_to_string(exp_path) {
+        if expect_success {
+            assert_eq!(
+                result_str,
+                exp.trim(),
+                "{}: result value representation (left) must match expectation (right)",
+                code_path.display()
+            );
+        } else {
+            assert_ne!(
+                result_str,
+                exp.trim(),
+                "{}: test passed unexpectedly!  consider moving it out of notyetpassing",
+                code_path.display()
+            );
+        }
+    } else if expect_success {
+        panic!(
+            "{}: should be able to read test expectation",
+            code_path.display()
+        );
+    } else {
+        panic!(
+            "{}: test should have failed, but succeeded with output {}",
+            code_path.display(),
+            result_str
+        );
+    }
+}
+
+// eval-okay-* tests contain a snippet of Nix code, and an expectation
+// of the produced string output of the evaluator.
+//
+// These evaluations are always supposed to succeed, i.e. all snippets
+// are guaranteed to be valid Nix code.
+#[rstest]
+fn eval_okay(#[files("src/tests/tvix_tests/eval-okay-*.nix")] code_path: PathBuf) {
+    eval_test(code_path, true)
+}
+
+// eval-okay-* tests from the original Nix test suite.
+#[cfg(feature = "nix_tests")]
+#[rstest]
+fn nix_eval_okay(#[files("src/tests/nix_tests/eval-okay-*.nix")] code_path: PathBuf) {
+    eval_test(code_path, true)
+}
+
+// eval-okay-* tests from the original Nix test suite which do not yet pass for tvix
+//
+// Eventually there will be none of these left, and this function
+// will disappear :) Until then, to run these tests, use `cargo test
+// --features expected_failures`.
+//
+// Please don't submit failing tests unless they're in
+// notyetpassing; this makes the test suite much more useful for
+// regression testing, since there should always be zero non-ignored
+// failing tests.
+//
+// NOTE: There's no such test anymore. `rstest` does not handle empty directories, so, we
+// just comment it for now.
+//
+// #[rstest]
+// fn nix_eval_okay_currently_failing(
+//     #[files("src/tests/nix_tests/notyetpassing/eval-okay-*.nix")] code_path: PathBuf,
+// ) {
+//     eval_test(code_path, false)
+// }
diff --git a/tvix/glue/src/tests/nix_tests/eval-okay-context-introspection.exp b/tvix/glue/src/tests/nix_tests/eval-okay-context-introspection.exp
new file mode 100644
index 0000000000..03b400cc88
--- /dev/null
+++ b/tvix/glue/src/tests/nix_tests/eval-okay-context-introspection.exp
@@ -0,0 +1 @@
+[ true true true true true true ]
diff --git a/tvix/glue/src/tests/nix_tests/eval-okay-context-introspection.nix b/tvix/glue/src/tests/nix_tests/eval-okay-context-introspection.nix
new file mode 100644
index 0000000000..354376b895
--- /dev/null
+++ b/tvix/glue/src/tests/nix_tests/eval-okay-context-introspection.nix
@@ -0,0 +1,42 @@
+let
+  drv = derivation {
+    name = "fail";
+    builder = "/bin/false";
+    system = "x86_64-linux";
+    outputs = [ "out" "foo" ];
+  };
+
+  path = "${./eval-okay-context-introspection.nix}";
+
+  desired-context = {
+    "${builtins.unsafeDiscardStringContext path}" = {
+      path = true;
+    };
+    "${builtins.unsafeDiscardStringContext drv.drvPath}" = {
+      outputs = [ "foo" "out" ];
+      allOutputs = true;
+    };
+  };
+
+  combo-path = "${path}${drv.outPath}${drv.foo.outPath}${drv.drvPath}";
+  legit-context = builtins.getContext combo-path;
+
+  reconstructed-path = builtins.appendContext
+    (builtins.unsafeDiscardStringContext combo-path)
+    desired-context;
+
+  # Eta rule for strings with context.
+  etaRule = str:
+    str == builtins.appendContext
+      (builtins.unsafeDiscardStringContext str)
+      (builtins.getContext str);
+
+in
+[
+  (legit-context == desired-context)
+  (reconstructed-path == combo-path)
+  (etaRule "foo")
+  (etaRule drv.drvPath)
+  (etaRule drv.foo.outPath)
+  (etaRule (builtins.unsafeDiscardOutputDependency drv.drvPath))
+]
diff --git a/tvix/glue/src/tests/nix_tests/eval-okay-context.exp b/tvix/glue/src/tests/nix_tests/eval-okay-context.exp
new file mode 100644
index 0000000000..2f535bdbc4
--- /dev/null
+++ b/tvix/glue/src/tests/nix_tests/eval-okay-context.exp
@@ -0,0 +1 @@
+"foo eval-okay-context.nix bar"
diff --git a/tvix/glue/src/tests/nix_tests/eval-okay-context.nix b/tvix/glue/src/tests/nix_tests/eval-okay-context.nix
new file mode 100644
index 0000000000..c873211862
--- /dev/null
+++ b/tvix/glue/src/tests/nix_tests/eval-okay-context.nix
@@ -0,0 +1,6 @@
+let s = "foo ${builtins.substring 33 100 (baseNameOf "${./eval-okay-context.nix}")} bar";
+in
+if s != "foo eval-okay-context.nix bar"
+then abort "context not discarded"
+else builtins.unsafeDiscardStringContext s
+
diff --git a/tvix/glue/src/tests/ss2p4wmxijn652haqyd7dckxwl4c7hxx-bar.drv b/tvix/glue/src/tests/ss2p4wmxijn652haqyd7dckxwl4c7hxx-bar.drv
new file mode 100644
index 0000000000..559e93ed0e
--- /dev/null
+++ b/tvix/glue/src/tests/ss2p4wmxijn652haqyd7dckxwl4c7hxx-bar.drv
@@ -0,0 +1 @@
+Derive([("out","/nix/store/mp57d33657rf34lzvlbpfa1gjfv5gmpg-bar","r:sha1","0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33")],[],[],":",":",[],[("builder",":"),("name","bar"),("out","/nix/store/mp57d33657rf34lzvlbpfa1gjfv5gmpg-bar"),("outputHash","0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33"),("outputHashAlgo","sha1"),("outputHashMode","recursive"),("system",":")])
\ No newline at end of file
diff --git a/tvix/glue/src/tests/tvix_tests/eval-okay-context-introspection.exp b/tvix/glue/src/tests/tvix_tests/eval-okay-context-introspection.exp
new file mode 100644
index 0000000000..a136b0035e
--- /dev/null
+++ b/tvix/glue/src/tests/tvix_tests/eval-okay-context-introspection.exp
@@ -0,0 +1 @@
+[ true true true true true true true true true true true true true ]
diff --git a/tvix/glue/src/tests/tvix_tests/eval-okay-context-introspection.nix b/tvix/glue/src/tests/tvix_tests/eval-okay-context-introspection.nix
new file mode 100644
index 0000000000..ecd8ab0073
--- /dev/null
+++ b/tvix/glue/src/tests/tvix_tests/eval-okay-context-introspection.nix
@@ -0,0 +1,83 @@
+# Contrary to the Nix tests, this one does not make any use of `builtins.appendContext`
+# It's a weaker yet interesting test by abusing knowledge on how does our builtins
+# performs propagation.
+let
+  drv = derivation {
+    name = "fail";
+    builder = "/bin/false";
+    system = "x86_64-linux";
+    outputs = [ "out" "foo" ];
+  };
+
+  # `substr` propagates context, we truncate to an empty string and concatenate to the target
+  # to infect it with the context of `copied`.
+  appendContextFrom = copied: target: (builtins.substring 0 0 "${copied}") + "${target}";
+
+  # `split` discards (!!) contexts, we first split by `/` (there's at least one such `/` by
+  # virtue of `target` being a store path, i.e. starting with `$store_root/$derivation_name`)
+  # then, we reassemble the list into a proper string.
+  discardContext = target: builtins.concatStringsSep "" (builtins.split "(.*)" "${target}");
+
+  # Note that this should never return true for any attribute set.
+  hasContextInAttrKeys = attrs: builtins.any builtins.hasContext (builtins.attrNames attrs);
+
+  path = "${./eval-okay-context-introspection.nix}";
+
+  # This is a context-less attribute set, which should be exactly the same
+  # as `builtins.getContext combo-path`.
+  desired-context = {
+    "${builtins.unsafeDiscardStringContext path}" = {
+      path = true;
+    };
+    "${builtins.unsafeDiscardStringContext drv.drvPath}" = {
+      outputs = [ "foo" "out" ];
+      allOutputs = true;
+    };
+  };
+
+  combo-path = "${path}${drv.outPath}${drv.foo.outPath}${drv.drvPath}";
+  legit-context = builtins.getContext combo-path;
+
+  reconstructed-path = appendContextFrom combo-path
+    (builtins.unsafeDiscardStringContext combo-path);
+
+  an-str = {
+    a = "${drv}";
+  };
+  an-list = {
+    b = [ drv ];
+  };
+
+  # Eta rule for strings with context.
+  etaRule = str:
+    str == appendContextFrom
+      str
+      (builtins.unsafeDiscardStringContext str);
+
+  etaRule' = str:
+    str == appendContextFrom
+      str
+      (discardContext str);
+
+in
+[
+  (!hasContextInAttrKeys desired-context)
+  (legit-context."${builtins.unsafeDiscardStringContext path}".path)
+  (legit-context."${builtins.unsafeDiscardStringContext drv.drvPath}".outputs == [ "foo" "out" ])
+  # `allOutputs` is present only on DrvClosure-style context string, i.e. the
+  # context string of a drvPath itself, not an outPath.
+  (!builtins.hasAttr "allOutputs" (builtins.getContext drv.outPath)."${builtins.unsafeDiscardStringContext drv.drvPath}")
+  (builtins.hasAttr "allOutputs" legit-context."${builtins.unsafeDiscardStringContext drv.drvPath}")
+  (builtins.hasAttr "allOutputs" (builtins.getContext drv.drvPath)."${builtins.unsafeDiscardStringContext drv.drvPath}")
+  (legit-context == desired-context) # FIXME(raitobezarius): this should not use `builtins.seq`, this is a consequence of excessive laziness of Tvix, I believe.
+  (reconstructed-path == combo-path)
+  # Those are too slow?
+  # (etaRule' "foo")
+  # (etaRule' combo-path)
+  (etaRule "foo")
+  (etaRule drv.drvPath)
+  (etaRule drv.foo.outPath)
+  # `toJSON` tests
+  (builtins.hasContext (builtins.toJSON an-str))
+  (builtins.hasContext (builtins.toJSON an-list))
+]
diff --git a/tvix/glue/src/tests/tvix_tests/eval-okay-context-propagation.exp b/tvix/glue/src/tests/tvix_tests/eval-okay-context-propagation.exp
new file mode 100644
index 0000000000..ff56f6ca18
--- /dev/null
+++ b/tvix/glue/src/tests/tvix_tests/eval-okay-context-propagation.exp
@@ -0,0 +1 @@
+[ true true true true true true true true true true true true true true true true true true true true true true true true true true true true true true true true true true ]
diff --git a/tvix/glue/src/tests/tvix_tests/eval-okay-context-propagation.nix b/tvix/glue/src/tests/tvix_tests/eval-okay-context-propagation.nix
new file mode 100644
index 0000000000..41e7f207b9
--- /dev/null
+++ b/tvix/glue/src/tests/tvix_tests/eval-okay-context-propagation.nix
@@ -0,0 +1,119 @@
+# We test various propagation of contexts under other builtins here.
+let
+  drv = derivation {
+    name = "fail";
+    builder = "/bin/false";
+    system = "x86_64-linux";
+    outputs = [ "out" "foo" ];
+  };
+  other-drv = derivation {
+    name = "other-fail";
+    builder = "/bin/false";
+    system = "x86_64-linux";
+    outputs = [ "out" "bar" ];
+  };
+  a-path-drv = builtins.path {
+    name = "a-path-drv";
+    path = ./eval-okay-context-introspection.nix;
+  };
+  another-path-drv = builtins.filterSource (_: true) ./eval-okay-context-introspection.nix;
+
+  # `substr` propagates context, we truncate to an empty string and concatenate to the target
+  # to infect it with the context of `copied`.
+  appendContextFrom = copied: target: (builtins.substring 0 0 copied) + target;
+
+  path = "${./eval-okay-context-introspection.nix}";
+
+  combo-path = "${path}${drv.outPath}${drv.foo.outPath}${drv.drvPath}";
+
+  mergeContext = a: b:
+    builtins.getContext a // builtins.getContext b;
+
+  preserveContext = origin: result:
+    builtins.getContext "${result}" == builtins.getContext "${origin}";
+
+  preserveContexts = origins: result:
+    let union = builtins.foldl' (x: y: x // y) { } (builtins.map (d: builtins.getContext "${d}") origins);
+    in
+    union == builtins.getContext "${result}";
+in
+[
+  # `toFile` should produce context.
+  (builtins.hasContext "${(builtins.toFile "myself" "${./eval-okay-context-introspection.nix}")}")
+  # `derivation` should produce context.
+  (builtins.hasContext "${drv}")
+  # `builtins.path` / `builtins.filterSource` should produce context.
+  (builtins.hasContext "${a-path-drv}")
+  (builtins.hasContext "${another-path-drv}")
+  # Low-level test to ensure that interpolation is working as expected.
+  (builtins.length (builtins.attrNames (builtins.getContext "${drv}${other-drv}")) == 2)
+  (builtins.getContext "${drv}${other-drv}" == mergeContext drv other-drv)
+  # Those three next tests are extremely related.
+  # To test interpolation, we need concatenation to be working and vice versa.
+  # In addition, we need `builtins.substring` empty string propagation to attach context
+  # in absence of `builtins.appendContext`.
+  # The previous test should ensure that we don't test vacuous truths.
+  # Substring preserves contexts.
+  (preserveContext combo-path (builtins.substring 0 0 combo-path)) # <- FIXME: broken
+  # Interpolation preserves contexts.
+  (preserveContext "${drv}${other-drv}" (appendContextFrom drv other-drv))
+  # Concatenation preserves contexts.
+  (preserveContext "${drv}${other-drv}" (drv + other-drv))
+  # Special case when Nix does not assert that the length argument is non-negative
+  # when the starting index is ≥ than the string's length.
+  # FIXME: those three are broken too, NON DETERMINISTIC!!!
+  (preserveContext combo-path (builtins.substring 5 (-5) (builtins.substring 0 0 combo-path)))
+  (preserveContext combo-path (toString combo-path))
+  # No replacement should yield at least the same context.
+  (preserveContext combo-path (builtins.replaceStrings [ ] [ ] combo-path))
+  # This is an idempotent replacement, it should yield therefore to full preservation of the context.
+  (preserveContext "${drv}${drv}" (builtins.replaceStrings [ "${drv}" ] [ "${drv}" ] "${drv}"))
+  # There's no context here, so no context should appear from `drv`.
+  (preserveContext "abc" (builtins.replaceStrings [ "${drv}" ] [ "${drv}" ] "abc"))
+  # Context should appear by a successful replacement.
+  (preserveContext "${drv}" (builtins.replaceStrings [ "a" ] [ "${drv}" ] "a"))
+  # We test multiple successful replacements.
+  (preserveContexts [ drv other-drv ] (builtins.replaceStrings [ "a" "b" ] [ "${drv}" "${other-drv}" ] "ab"))
+  # We test *empty* string replacements.
+  (preserveContext "${drv}" (builtins.replaceStrings [ "" ] [ "${drv}" ] "abc"))
+  (preserveContext "${drv}" (builtins.replaceStrings [ "" ] [ "${drv}" ] ""))
+  # There should be no context in a parsed derivation name.
+  (!builtins.any builtins.hasContext (builtins.attrValues (builtins.parseDrvName "${drv.name}")))
+  # Nix does not propagate contexts for `match`.
+  (!builtins.any builtins.hasContext (builtins.match "(.*)" "${drv}"))
+  # `dirOf` preserves contexts of non-paths.
+  (preserveContext "${drv}" (builtins.dirOf "${drv}"))
+  (preserveContext "abc" (builtins.dirOf "abc"))
+  # `baseNameOf propagates context of argument
+  (preserveContext "${drv}" (builtins.baseNameOf drv))
+  (preserveContext "abc" (builtins.baseNameOf "abc"))
+  # `concatStringsSep` preserves contexts of both arguments.
+  (preserveContexts [ drv other-drv ] (builtins.concatStringsSep "${other-drv}" (map toString [ drv drv drv drv drv ])))
+  (preserveContext drv (builtins.concatStringsSep "|" (map toString [ drv drv drv drv drv ])))
+  (preserveContext other-drv (builtins.concatStringsSep "${other-drv}" [ "abc" "def" ]))
+  # `attrNames` will never ever produce context.
+  (preserveContext "abc" (toString (builtins.attrNames { a = { }; b = { }; c = { }; })))
+  # `toJSON` preserves context of its inputs.
+  (preserveContexts [ drv other-drv ] (builtins.toJSON {
+    a = [ drv ];
+    b = [ other-drv ];
+  }))
+  (preserveContexts [ drv other-drv ] (builtins.toJSON {
+    a.deep = [ drv ];
+    b = [ other-drv ];
+  }))
+  (preserveContexts [ drv other-drv ] (builtins.toJSON {
+    a = "${drv}";
+    b = [ other-drv ];
+  }))
+  (preserveContexts [ drv other-drv ] (builtins.toJSON {
+    a.deep = "${drv}";
+    b = [ other-drv ];
+  }))
+  (preserveContexts [ drv other-drv ] (builtins.toJSON {
+    a = "${drv} ${other-drv}";
+  }))
+  (preserveContexts [ drv other-drv ] (builtins.toJSON {
+    a.b.c.d.e.f = "${drv} ${other-drv}";
+  }))
+]
diff --git a/tvix/glue/src/tests/tvix_tests/eval-okay-fetchtarball.exp b/tvix/glue/src/tests/tvix_tests/eval-okay-fetchtarball.exp
new file mode 100644
index 0000000000..c7332c0503
--- /dev/null
+++ b/tvix/glue/src/tests/tvix_tests/eval-okay-fetchtarball.exp
@@ -0,0 +1 @@
+[ /nix/store/7adgvk5zdfq4pwrhsm3n9lzypb12gw0g-source /nix/store/7adgvk5zdfq4pwrhsm3n9lzypb12gw0g-source /nix/store/7adgvk5zdfq4pwrhsm3n9lzypb12gw0g-source /nix/store/7adgvk5zdfq4pwrhsm3n9lzypb12gw0g-source /nix/store/7adgvk5zdfq4pwrhsm3n9lzypb12gw0g-source /nix/store/md9dsn2zwa6aj7zzalvjwwwx82whcyva-some-name ]
diff --git a/tvix/glue/src/tests/tvix_tests/eval-okay-fetchtarball.nix b/tvix/glue/src/tests/tvix_tests/eval-okay-fetchtarball.nix
new file mode 100644
index 0000000000..e454f12444
--- /dev/null
+++ b/tvix/glue/src/tests/tvix_tests/eval-okay-fetchtarball.nix
@@ -0,0 +1,42 @@
+[
+  # (fetchTarball "url") cannot be tested, as that one has to fetch from the
+  # internet to calculate the path.
+
+  # with url and sha256
+  (builtins.fetchTarball {
+    url = "https://github.com/NixOS/nixpkgs/archive/91050ea1e57e50388fa87a3302ba12d188ef723a.tar.gz";
+    sha256 = "1hf6cgaci1n186kkkjq106ryf8mmlq9vnwgfwh625wa8hfgdn4dm";
+  })
+
+  # with url and sha256 (as SRI)
+  (builtins.fetchTarball {
+    url = "https://github.com/NixOS/nixpkgs/archive/91050ea1e57e50388fa87a3302ba12d188ef723a.tar.gz";
+    sha256 = "sha256-tRHbnoNI8SIM5O5xuxOmtSLnswEByzmnQcGGyNRjxsE=";
+  })
+
+  # with another url, it actually doesn't matter (no .gz prefix)
+  (builtins.fetchTarball {
+    url = "https://github.com/NixOS/nixpkgs/archive/91050ea1e57e50388fa87a3302ba12d188ef723a.tar";
+    sha256 = "sha256-tRHbnoNI8SIM5O5xuxOmtSLnswEByzmnQcGGyNRjxsE=";
+  })
+
+  # also with an entirely different url, it doesn't change
+  (builtins.fetchTarball {
+    url = "https://test.example/owo";
+    sha256 = "sha256-tRHbnoNI8SIM5O5xuxOmtSLnswEByzmnQcGGyNRjxsE=";
+  })
+
+  # … because `name` defaults to source, and that (and the sha256 affect the store path)
+  (builtins.fetchTarball {
+    name = "source";
+    url = "https://test.example/owo";
+    sha256 = "sha256-tRHbnoNI8SIM5O5xuxOmtSLnswEByzmnQcGGyNRjxsE=";
+  })
+
+  # … so changing name causes the hash to change.
+  (builtins.fetchTarball {
+    name = "some-name";
+    url = "https://test.example/owo";
+    sha256 = "sha256-tRHbnoNI8SIM5O5xuxOmtSLnswEByzmnQcGGyNRjxsE=";
+  })
+]
diff --git a/tvix/glue/src/tests/tvix_tests/eval-okay-fetchurl.exp b/tvix/glue/src/tests/tvix_tests/eval-okay-fetchurl.exp
new file mode 100644
index 0000000000..37a04d577c
--- /dev/null
+++ b/tvix/glue/src/tests/tvix_tests/eval-okay-fetchurl.exp
@@ -0,0 +1 @@
+[ /nix/store/y0r1p1cqmlvm0yqkz3gxvkc1p8kg2sz8-null /nix/store/06qi00hylriyfm0nl827crgjvbax84mz-notmuch-extract-patch /nix/store/06qi00hylriyfm0nl827crgjvbax84mz-notmuch-extract-patch /nix/store/06qi00hylriyfm0nl827crgjvbax84mz-notmuch-extract-patch ]
diff --git a/tvix/glue/src/tests/tvix_tests/eval-okay-fetchurl.nix b/tvix/glue/src/tests/tvix_tests/eval-okay-fetchurl.nix
new file mode 100644
index 0000000000..8a39101525
--- /dev/null
+++ b/tvix/glue/src/tests/tvix_tests/eval-okay-fetchurl.nix
@@ -0,0 +1,25 @@
+[
+  # (fetchurl "url") needs to immediately fetch, but our options without
+  # internet access are fairly limited.
+  # TODO: populate some fixtures at a known location instead.
+  (builtins.fetchurl "file:///dev/null")
+
+  # fetchurl with url and sha256
+  (builtins.fetchurl {
+    url = "https://raw.githubusercontent.com/aaptel/notmuch-extract-patch/f732a53e12a7c91a06755ebfab2007adc9b3063b/notmuch-extract-patch";
+    sha256 = "0nawkl04sj7psw6ikzay7kydj3dhd0fkwghcsf5rzaw4bmp4kbax";
+  })
+
+  # fetchurl with url and sha256 (as SRI)
+  (builtins.fetchurl {
+    url = "https://raw.githubusercontent.com/aaptel/notmuch-extract-patch/f732a53e12a7c91a06755ebfab2007adc9b3063b/notmuch-extract-patch";
+    sha256 = "sha256-Xa1Jbl2Eq5+L0ww+Ph1osA3Z/Dxe/RkN1/dITQCdXFk=";
+  })
+
+  # fetchurl with another url, but same name
+  (builtins.fetchurl {
+    url = "https://test.example/owo";
+    name = "notmuch-extract-patch";
+    sha256 = "sha256-Xa1Jbl2Eq5+L0ww+Ph1osA3Z/Dxe/RkN1/dITQCdXFk=";
+  })
+]
diff --git a/tvix/glue/src/tvix_build.rs b/tvix/glue/src/tvix_build.rs
new file mode 100644
index 0000000000..e9eb1725ef
--- /dev/null
+++ b/tvix/glue/src/tvix_build.rs
@@ -0,0 +1,439 @@
+//! This module contains glue code translating from
+//! [nix_compat::derivation::Derivation] to [tvix_build::proto::BuildRequest].
+
+use std::collections::{BTreeMap, BTreeSet};
+
+use bytes::Bytes;
+use nix_compat::{derivation::Derivation, nixbase32};
+use sha2::{Digest, Sha256};
+use tvix_build::proto::{
+    build_request::{AdditionalFile, BuildConstraints, EnvVar},
+    BuildRequest,
+};
+use tvix_castore::proto::{self, node::Node};
+
+/// These are the environment variables that Nix sets in its sandbox for every
+/// build.
+const NIX_ENVIRONMENT_VARS: [(&str, &str); 12] = [
+    ("HOME", "/homeless-shelter"),
+    ("NIX_BUILD_CORES", "0"), // TODO: make this configurable?
+    ("NIX_BUILD_TOP", "/"),
+    ("NIX_LOG_FD", "2"),
+    ("NIX_STORE", "/nix/store"),
+    ("PATH", "/path-not-set"),
+    ("PWD", "/build"),
+    ("TEMP", "/build"),
+    ("TEMPDIR", "/build"),
+    ("TERM", "xterm-256color"),
+    ("TMP", "/build"),
+    ("TMPDIR", "/build"),
+];
+
+/// Takes a [Derivation] and turns it into a [BuildRequest].
+/// It assumes the Derivation has been validated.
+/// It needs two lookup functions:
+/// - one translating input sources to a castore node
+///   (`fn_input_sources_to_node`)
+/// - one translating a tuple of drv path and (a subset of their) output names to
+///   castore nodes of the selected outpus (`fn_input_drvs_to_output_nodes`).
+#[allow(clippy::mutable_key_type)]
+pub(crate) fn derivation_to_build_request(
+    derivation: &Derivation,
+    inputs: BTreeSet<Node>,
+) -> std::io::Result<BuildRequest> {
+    debug_assert!(derivation.validate(true).is_ok(), "drv must validate");
+
+    // produce command_args, which is builder and arguments in a Vec.
+    let mut command_args: Vec<String> = Vec::with_capacity(derivation.arguments.len() + 1);
+    command_args.push(derivation.builder.clone());
+    command_args.extend_from_slice(&derivation.arguments);
+
+    // produce output_paths, which is the absolute path of each output (sorted)
+    let mut output_paths: Vec<String> = derivation
+        .outputs
+        .values()
+        .map(|e| e.path_str()[1..].to_owned())
+        .collect();
+
+    // Sort the outputs. We can use sort_unstable, as these are unique strings.
+    output_paths.sort_unstable();
+
+    // Produce environment_vars and additional files.
+    // We use a BTreeMap while producing, and only realize the resulting Vec
+    // while populating BuildRequest, so we don't need to worry about ordering.
+    let mut environment_vars: BTreeMap<String, Bytes> = BTreeMap::new();
+    let mut additional_files: BTreeMap<String, Bytes> = BTreeMap::new();
+
+    // Start with some the ones that nix magically sets:
+    environment_vars.extend(
+        NIX_ENVIRONMENT_VARS
+            .iter()
+            .map(|(k, v)| (k.to_string(), Bytes::from_static(v.as_bytes()))),
+    );
+
+    // extend / overwrite with the keys set in the derivation environment itself.
+    // TODO: check if this order is correct, and environment vars set in the
+    // *Derivation actually* have priority.
+    environment_vars.extend(
+        derivation
+            .environment
+            .iter()
+            .map(|(k, v)| (k.clone(), Bytes::from(v.to_vec()))),
+    );
+
+    handle_pass_as_file(&mut environment_vars, &mut additional_files)?;
+
+    // TODO: handle __json (structured attrs, provide JSON file and source-able bash script)
+
+    // Produce constraints.
+    let constraints = Some(BuildConstraints {
+        system: derivation.system.clone(),
+        min_memory: 0,
+        available_ro_paths: vec![],
+        // in case this is a fixed-output derivation, allow network access.
+        network_access: derivation.outputs.len() == 1
+            && derivation
+                .outputs
+                .get("out")
+                .expect("invalid derivation")
+                .is_fixed(),
+        provide_bin_sh: true,
+    });
+
+    let build_request = BuildRequest {
+        command_args,
+        outputs: output_paths,
+
+        // Turn this into a sorted-by-key Vec<EnvVar>.
+        environment_vars: environment_vars
+            .into_iter()
+            .map(|(key, value)| EnvVar { key, value })
+            .collect(),
+        inputs: inputs
+            .into_iter()
+            .map(|n| proto::Node { node: Some(n) })
+            .collect(),
+        inputs_dir: nix_compat::store_path::STORE_DIR[1..].into(),
+        constraints,
+        working_dir: "build".into(),
+        scratch_paths: vec!["build".into(), "nix/store".into()],
+        additional_files: additional_files
+            .into_iter()
+            .map(|(path, contents)| AdditionalFile { path, contents })
+            .collect(),
+    };
+
+    debug_assert!(
+        build_request.validate().is_ok(),
+        "invalid BuildRequest: {}",
+        build_request.validate().unwrap_err()
+    );
+
+    Ok(build_request)
+}
+
+/// handle passAsFile, if set.
+/// For each env $x in that list, the original env is removed, and a $xPath
+/// environment var added instead, referring to a path inside the build with
+/// the contents from the original env var.
+fn handle_pass_as_file(
+    environment_vars: &mut BTreeMap<String, Bytes>,
+    additional_files: &mut BTreeMap<String, Bytes>,
+) -> std::io::Result<()> {
+    let pass_as_file = environment_vars.get("passAsFile").map(|v| {
+        // Convert pass_as_file to string.
+        // When it gets here, it contains a space-separated list of env var
+        // keys, which must be strings.
+        String::from_utf8(v.to_vec())
+    });
+
+    if let Some(pass_as_file) = pass_as_file {
+        let pass_as_file = pass_as_file.map_err(|_| {
+            std::io::Error::new(
+                std::io::ErrorKind::InvalidInput,
+                "passAsFile elements are no valid utf8 strings",
+            )
+        })?;
+
+        for x in pass_as_file.split(' ') {
+            match environment_vars.remove_entry(x) {
+                Some((k, contents)) => {
+                    let (new_k, path) = calculate_pass_as_file_env(&k);
+
+                    additional_files.insert(path[1..].to_string(), contents);
+                    environment_vars.insert(new_k, Bytes::from(path));
+                }
+                None => {
+                    return Err(std::io::Error::new(
+                        std::io::ErrorKind::InvalidData,
+                        "passAsFile refers to non-existent env key",
+                    ));
+                }
+            }
+        }
+    }
+
+    Ok(())
+}
+
+/// For a given key k in a derivation environment that's supposed to be passed as file,
+/// calculate the ${k}Path key and filepath value that it's being replaced with
+/// while preparing the build.
+/// The filepath is `/build/.attrs-${nixbase32(sha256(key))`.
+fn calculate_pass_as_file_env(k: &str) -> (String, String) {
+    (
+        format!("{}Path", k),
+        format!(
+            "/build/.attr-{}",
+            nixbase32::encode(&Sha256::new_with_prefix(k).finalize())
+        ),
+    )
+}
+
+#[cfg(test)]
+mod test {
+    use std::collections::BTreeSet;
+
+    use bytes::Bytes;
+    use nix_compat::derivation::Derivation;
+    use tvix_build::proto::{
+        build_request::{AdditionalFile, BuildConstraints, EnvVar},
+        BuildRequest,
+    };
+    use tvix_castore::{
+        fixtures::DUMMY_DIGEST,
+        proto::{self, node::Node, DirectoryNode},
+    };
+
+    use crate::tvix_build::NIX_ENVIRONMENT_VARS;
+
+    use super::derivation_to_build_request;
+    use lazy_static::lazy_static;
+
+    lazy_static! {
+        static ref INPUT_NODE_FOO: Node = Node::Directory(DirectoryNode {
+            name: Bytes::from("mp57d33657rf34lzvlbpfa1gjfv5gmpg-bar"),
+            digest: DUMMY_DIGEST.clone().into(),
+            size: 42,
+        });
+    }
+
+    #[test]
+    fn test_derivation_to_build_request() {
+        let aterm_bytes = include_bytes!("tests/ch49594n9avinrf8ip0aslidkc4lxkqv-foo.drv");
+
+        let derivation = Derivation::from_aterm_bytes(aterm_bytes).expect("must parse");
+
+        let build_request =
+            derivation_to_build_request(&derivation, BTreeSet::from([INPUT_NODE_FOO.clone()]))
+                .expect("must succeed");
+
+        let mut expected_environment_vars = vec![
+            EnvVar {
+                key: "bar".into(),
+                value: "/nix/store/mp57d33657rf34lzvlbpfa1gjfv5gmpg-bar".into(),
+            },
+            EnvVar {
+                key: "builder".into(),
+                value: ":".into(),
+            },
+            EnvVar {
+                key: "name".into(),
+                value: "foo".into(),
+            },
+            EnvVar {
+                key: "out".into(),
+                value: "/nix/store/fhaj6gmwns62s6ypkcldbaj2ybvkhx3p-foo".into(),
+            },
+            EnvVar {
+                key: "system".into(),
+                value: ":".into(),
+            },
+        ];
+
+        expected_environment_vars.extend(NIX_ENVIRONMENT_VARS.iter().map(|(k, v)| EnvVar {
+            key: k.to_string(),
+            value: Bytes::from_static(v.as_bytes()),
+        }));
+
+        expected_environment_vars.sort_unstable_by_key(|e| e.key.to_owned());
+
+        assert_eq!(
+            BuildRequest {
+                command_args: vec![":".into()],
+                outputs: vec!["nix/store/fhaj6gmwns62s6ypkcldbaj2ybvkhx3p-foo".into()],
+                environment_vars: expected_environment_vars,
+                inputs: vec![proto::Node {
+                    node: Some(INPUT_NODE_FOO.clone())
+                }],
+                inputs_dir: "nix/store".into(),
+                constraints: Some(BuildConstraints {
+                    system: derivation.system.clone(),
+                    min_memory: 0,
+                    network_access: false,
+                    available_ro_paths: vec![],
+                    provide_bin_sh: true,
+                }),
+                additional_files: vec![],
+                working_dir: "build".into(),
+                scratch_paths: vec!["build".into(), "nix/store".into()],
+            },
+            build_request
+        );
+    }
+
+    #[test]
+    fn test_fod_to_build_request() {
+        let aterm_bytes = include_bytes!("tests/0hm2f1psjpcwg8fijsmr4wwxrx59s092-bar.drv");
+
+        let derivation = Derivation::from_aterm_bytes(aterm_bytes).expect("must parse");
+
+        let build_request =
+            derivation_to_build_request(&derivation, BTreeSet::from([])).expect("must succeed");
+
+        let mut expected_environment_vars = vec![
+            EnvVar {
+                key: "builder".into(),
+                value: ":".into(),
+            },
+            EnvVar {
+                key: "name".into(),
+                value: "bar".into(),
+            },
+            EnvVar {
+                key: "out".into(),
+                value: "/nix/store/4q0pg5zpfmznxscq3avycvf9xdvx50n3-bar".into(),
+            },
+            EnvVar {
+                key: "outputHash".into(),
+                value: "08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba".into(),
+            },
+            EnvVar {
+                key: "outputHashAlgo".into(),
+                value: "sha256".into(),
+            },
+            EnvVar {
+                key: "outputHashMode".into(),
+                value: "recursive".into(),
+            },
+            EnvVar {
+                key: "system".into(),
+                value: ":".into(),
+            },
+        ];
+
+        expected_environment_vars.extend(NIX_ENVIRONMENT_VARS.iter().map(|(k, v)| EnvVar {
+            key: k.to_string(),
+            value: Bytes::from_static(v.as_bytes()),
+        }));
+
+        expected_environment_vars.sort_unstable_by_key(|e| e.key.to_owned());
+
+        assert_eq!(
+            BuildRequest {
+                command_args: vec![":".to_string()],
+                outputs: vec!["nix/store/4q0pg5zpfmznxscq3avycvf9xdvx50n3-bar".into()],
+                environment_vars: expected_environment_vars,
+                inputs: vec![],
+                inputs_dir: "nix/store".into(),
+                constraints: Some(BuildConstraints {
+                    system: derivation.system.clone(),
+                    min_memory: 0,
+                    network_access: true,
+                    available_ro_paths: vec![],
+                    provide_bin_sh: true,
+                }),
+                additional_files: vec![],
+                working_dir: "build".into(),
+                scratch_paths: vec!["build".into(), "nix/store".into()],
+            },
+            build_request
+        );
+    }
+
+    #[test]
+    fn test_pass_as_file() {
+        // (builtins.derivation { "name" = "foo"; passAsFile = ["bar" "baz"]; bar = "baz"; baz = "bar"; system = ":"; builder = ":";}).drvPath
+        let aterm_bytes = r#"Derive([("out","/nix/store/pp17lwra2jkx8rha15qabg2q3wij72lj-foo","","")],[],[],":",":",[],[("bar","baz"),("baz","bar"),("builder",":"),("name","foo"),("out","/nix/store/pp17lwra2jkx8rha15qabg2q3wij72lj-foo"),("passAsFile","bar baz"),("system",":")])"#.as_bytes();
+
+        let derivation = Derivation::from_aterm_bytes(aterm_bytes).expect("must parse");
+
+        let build_request =
+            derivation_to_build_request(&derivation, BTreeSet::from([])).expect("must succeed");
+
+        let mut expected_environment_vars = vec![
+            // Note how bar and baz are not present in the env anymore,
+            // but replaced with barPath, bazPath respectively.
+            EnvVar {
+                key: "barPath".into(),
+                value: "/build/.attr-1fcgpy7vc4ammr7s17j2xq88scswkgz23dqzc04g8sx5vcp2pppw".into(),
+            },
+            EnvVar {
+                key: "bazPath".into(),
+                value: "/build/.attr-15l04iksj1280dvhbzdq9ai3wlf8ac2188m9qv0gn81k9nba19ds".into(),
+            },
+            EnvVar {
+                key: "builder".into(),
+                value: ":".into(),
+            },
+            EnvVar {
+                key: "name".into(),
+                value: "foo".into(),
+            },
+            EnvVar {
+                key: "out".into(),
+                value: "/nix/store/pp17lwra2jkx8rha15qabg2q3wij72lj-foo".into(),
+            },
+            // passAsFile stays around
+            EnvVar {
+                key: "passAsFile".into(),
+                value: "bar baz".into(),
+            },
+            EnvVar {
+                key: "system".into(),
+                value: ":".into(),
+            },
+        ];
+
+        expected_environment_vars.extend(NIX_ENVIRONMENT_VARS.iter().map(|(k, v)| EnvVar {
+            key: k.to_string(),
+            value: Bytes::from_static(v.as_bytes()),
+        }));
+
+        expected_environment_vars.sort_unstable_by_key(|e| e.key.to_owned());
+
+        assert_eq!(
+            BuildRequest {
+                command_args: vec![":".to_string()],
+                outputs: vec!["nix/store/pp17lwra2jkx8rha15qabg2q3wij72lj-foo".into()],
+                environment_vars: expected_environment_vars,
+                inputs: vec![],
+                inputs_dir: "nix/store".into(),
+                constraints: Some(BuildConstraints {
+                    system: derivation.system.clone(),
+                    min_memory: 0,
+                    network_access: false,
+                    available_ro_paths: vec![],
+                    provide_bin_sh: true,
+                }),
+                additional_files: vec![
+                    // baz env
+                    AdditionalFile {
+                        path: "build/.attr-15l04iksj1280dvhbzdq9ai3wlf8ac2188m9qv0gn81k9nba19ds"
+                            .into(),
+                        contents: "bar".into()
+                    },
+                    // bar env
+                    AdditionalFile {
+                        path: "build/.attr-1fcgpy7vc4ammr7s17j2xq88scswkgz23dqzc04g8sx5vcp2pppw"
+                            .into(),
+                        contents: "baz".into(),
+                    },
+                ],
+                working_dir: "build".into(),
+                scratch_paths: vec!["build".into(), "nix/store".into()],
+            },
+            build_request
+        );
+    }
+}
diff --git a/tvix/glue/src/tvix_io.rs b/tvix/glue/src/tvix_io.rs
new file mode 100644
index 0000000000..0e5f23b990
--- /dev/null
+++ b/tvix/glue/src/tvix_io.rs
@@ -0,0 +1,66 @@
+//! This module implements a wrapper around tvix-eval's [EvalIO] type,
+//! adding functionality which is required by tvix-cli:
+//!
+//! 1. Marking plain paths known to the reference scanner.
+//! 2. Handling the C++ Nix `__corepkgs__`-hack for nixpkgs bootstrapping.
+//!
+//! All uses of [EvalIO] in tvix-cli must make use of this wrapper,
+//! otherwise fundamental features like nixpkgs bootstrapping and hash
+//! calculation will not work.
+
+use std::io::{self, Cursor};
+use std::path::{Path, PathBuf};
+use tvix_eval::{EvalIO, FileType};
+
+// TODO: Merge this together with TvixStoreIO?
+pub struct TvixIO<T> {
+    // Actual underlying [EvalIO] implementation.
+    actual: T,
+}
+
+impl<T> TvixIO<T> {
+    pub fn new(actual: T) -> Self {
+        Self { actual }
+    }
+}
+
+impl<T> EvalIO for TvixIO<T>
+where
+    T: AsRef<dyn EvalIO>,
+{
+    fn store_dir(&self) -> Option<String> {
+        self.actual.as_ref().store_dir()
+    }
+
+    fn import_path(&self, path: &Path) -> io::Result<PathBuf> {
+        self.actual.as_ref().import_path(path)
+    }
+
+    fn path_exists(&self, path: &Path) -> io::Result<bool> {
+        if path.starts_with("/__corepkgs__") {
+            return Ok(true);
+        }
+
+        self.actual.as_ref().path_exists(path)
+    }
+
+    fn open(&self, path: &Path) -> io::Result<Box<dyn io::Read>> {
+        // Bundled version of corepkgs/fetchurl.nix. The counterpart
+        // of this happens in [crate::configure_nix_path], where the `nix_path`
+        // of the evaluation has `nix=/__corepkgs__` added to it.
+        //
+        // This workaround is similar to what cppnix does for passing
+        // the path through.
+        //
+        // TODO: this comparison is bad we should use the sane path library.
+        if path.starts_with("/__corepkgs__/fetchurl.nix") {
+            return Ok(Box::new(Cursor::new(include_bytes!("fetchurl.nix"))));
+        }
+
+        self.actual.as_ref().open(path)
+    }
+
+    fn read_dir(&self, path: &Path) -> io::Result<Vec<(bytes::Bytes, FileType)>> {
+        self.actual.as_ref().read_dir(path)
+    }
+}
diff --git a/tvix/glue/src/tvix_store_io.rs b/tvix/glue/src/tvix_store_io.rs
new file mode 100644
index 0000000000..7478fac9d2
--- /dev/null
+++ b/tvix/glue/src/tvix_store_io.rs
@@ -0,0 +1,715 @@
+//! This module provides an implementation of EvalIO talking to tvix-store.
+
+use bytes::Bytes;
+use futures::{StreamExt, TryStreamExt};
+use nix_compat::nixhash::NixHash;
+use nix_compat::store_path::StorePathRef;
+use nix_compat::{nixhash::CAHash, store_path::StorePath};
+use sha2::{Digest, Sha256};
+use std::{
+    cell::RefCell,
+    collections::BTreeSet,
+    io,
+    path::{Path, PathBuf},
+    sync::Arc,
+};
+use tokio_util::io::SyncIoBridge;
+use tracing::{error, info, instrument, warn, Level};
+use tvix_build::buildservice::BuildService;
+use tvix_castore::proto::node::Node;
+use tvix_eval::{EvalIO, FileType, StdIO};
+use tvix_store::utils::AsyncIoBridge;
+
+use tvix_castore::{
+    blobservice::BlobService,
+    directoryservice::{self, DirectoryService},
+    proto::NamedNode,
+    B3Digest,
+};
+use tvix_store::{pathinfoservice::PathInfoService, proto::PathInfo};
+
+use crate::fetchers::Fetcher;
+use crate::known_paths::KnownPaths;
+use crate::tvix_build::derivation_to_build_request;
+
+/// Implements [EvalIO], asking given [PathInfoService], [DirectoryService]
+/// and [BlobService].
+///
+/// In case the given path does not exist in these stores, we ask StdIO.
+/// This is to both cover cases of syntactically valid store paths, that exist
+/// on the filesystem (still managed by Nix), as well as being able to read
+/// files outside store paths.
+///
+/// This structure is also directly used by the derivation builtins
+/// and tightly coupled to it.
+///
+/// In the future, we may revisit that coupling and figure out how to generalize this interface and
+/// hide this implementation detail of the glue itself so that glue can be used with more than one
+/// implementation of "Tvix Store IO" which does not necessarily bring the concept of blob service,
+/// directory service or path info service.
+pub struct TvixStoreIO {
+    // This is public so helper functions can interact with the stores directly.
+    pub(crate) blob_service: Arc<dyn BlobService>,
+    pub(crate) directory_service: Arc<dyn DirectoryService>,
+    pub(crate) path_info_service: Arc<dyn PathInfoService>,
+    std_io: StdIO,
+    #[allow(dead_code)]
+    build_service: Arc<dyn BuildService>,
+    pub(crate) tokio_handle: tokio::runtime::Handle,
+
+    pub(crate) fetcher:
+        Fetcher<Arc<dyn BlobService>, Arc<dyn DirectoryService>, Arc<dyn PathInfoService>>,
+
+    // Paths known how to produce, by building or fetching.
+    pub(crate) known_paths: RefCell<KnownPaths>,
+}
+
+impl TvixStoreIO {
+    pub fn new(
+        blob_service: Arc<dyn BlobService>,
+        directory_service: Arc<dyn DirectoryService>,
+        path_info_service: Arc<dyn PathInfoService>,
+        build_service: Arc<dyn BuildService>,
+        tokio_handle: tokio::runtime::Handle,
+    ) -> Self {
+        Self {
+            blob_service: blob_service.clone(),
+            directory_service: directory_service.clone(),
+            path_info_service: path_info_service.clone(),
+            std_io: StdIO {},
+            build_service,
+            tokio_handle,
+            fetcher: Fetcher::new(blob_service, directory_service, path_info_service),
+            known_paths: Default::default(),
+        }
+    }
+
+    /// for a given [StorePath] and additional [Path] inside the store path,
+    /// look up the [PathInfo], and if it exists, and then use
+    /// [directoryservice::descend_to] to return the
+    /// [Node] specified by `sub_path`.
+    ///
+    /// In case there is no PathInfo yet, this means we need to build it
+    /// (which currently is stubbed out still).
+    #[instrument(skip(self, store_path), fields(store_path=%store_path), ret(level = Level::TRACE), err)]
+    async fn store_path_to_node(
+        &self,
+        store_path: &StorePath,
+        sub_path: &Path,
+    ) -> io::Result<Option<Node>> {
+        // Find the root node for the store_path.
+        // It asks the PathInfoService first, but in case there was a Derivation
+        // produced that would build it, fall back to triggering the build.
+        // To populate the input nodes, it might recursively trigger builds of
+        // its dependencies too.
+        let root_node = match self
+            .path_info_service
+            .as_ref()
+            .get(*store_path.digest())
+            .await?
+        {
+            // if we have a PathInfo, we know there will be a root_node (due to validation)
+            Some(path_info) => path_info.node.expect("no node").node.expect("no node"),
+            // If there's no PathInfo found, this normally means we have to
+            // trigger the build (and insert into PathInfoService, after
+            // reference scanning).
+            // However, as Tvix is (currently) not managing /nix/store itself,
+            // we return Ok(None) to let std_io take over.
+            // While reading from store paths that are not known to Tvix during
+            // that evaluation clearly is an impurity, we still need to support
+            // it for things like <nixpkgs> pointing to a store path.
+            // In the future, these things will (need to) have PathInfo.
+            None => {
+                // The store path doesn't exist yet, so we need to fetch or build it.
+                // We check for fetches first, as we might have both native
+                // fetchers and FODs in KnownPaths, and prefer the former.
+
+                let maybe_fetch = self
+                    .known_paths
+                    .borrow()
+                    .get_fetch_for_output_path(store_path);
+
+                match maybe_fetch {
+                    Some((name, fetch)) => {
+                        info!(?fetch, "triggering lazy fetch");
+                        let (sp, root_node) = self
+                            .fetcher
+                            .ingest_and_persist(&name, fetch)
+                            .await
+                            .map_err(|e| {
+                            std::io::Error::new(std::io::ErrorKind::InvalidData, e)
+                        })?;
+
+                        debug_assert_eq!(
+                            sp.to_string(),
+                            store_path.to_string(),
+                            "store path returned from fetcher should match"
+                        );
+
+                        root_node
+                    }
+                    None => {
+                        // Look up the derivation for this output path.
+                        let (drv_path, drv) = {
+                            let known_paths = self.known_paths.borrow();
+                            match known_paths.get_drv_path_for_output_path(store_path) {
+                                Some(drv_path) => (
+                                    drv_path.to_owned(),
+                                    known_paths.get_drv_by_drvpath(drv_path).unwrap().to_owned(),
+                                ),
+                                None => {
+                                    warn!(store_path=%store_path, "no drv found");
+                                    // let StdIO take over
+                                    return Ok(None);
+                                }
+                            }
+                        };
+
+                        warn!("triggering build");
+
+                        // derivation_to_build_request needs castore nodes for all inputs.
+                        // Provide them, which means, here is where we recursively build
+                        // all dependencies.
+                        #[allow(clippy::mutable_key_type)]
+                        let input_nodes: BTreeSet<Node> =
+                            futures::stream::iter(drv.input_derivations.iter())
+                                .map(|(input_drv_path, output_names)| {
+                                    // look up the derivation object
+                                    let input_drv = {
+                                        let known_paths = self.known_paths.borrow();
+                                        known_paths
+                                            .get_drv_by_drvpath(input_drv_path)
+                                            .unwrap_or_else(|| {
+                                                panic!("{} not found", input_drv_path)
+                                            })
+                                            .to_owned()
+                                    };
+
+                                    // convert output names to actual paths
+                                    let output_paths: Vec<StorePath> = output_names
+                                        .iter()
+                                        .map(|output_name| {
+                                            input_drv
+                                                .outputs
+                                                .get(output_name)
+                                                .expect("missing output_name")
+                                                .path
+                                                .as_ref()
+                                                .expect("missing output path")
+                                                .clone()
+                                        })
+                                        .collect();
+                                    // For each output, ask for the castore node.
+                                    // We're in a per-derivation context, so if they're
+                                    // not built yet they'll all get built together.
+                                    // If they don't need to build, we can however still
+                                    // substitute all in parallel (if they don't need to
+                                    // be built) - so we turn this into a stream of streams.
+                                    // It's up to the builder to deduplicate same build requests.
+                                    futures::stream::iter(output_paths.into_iter()).map(
+                                        |output_path| async move {
+                                            let node = self
+                                                .store_path_to_node(&output_path, Path::new(""))
+                                                .await?;
+
+                                            if let Some(node) = node {
+                                                Ok(node)
+                                            } else {
+                                                Err(io::Error::other("no node produced"))
+                                            }
+                                        },
+                                    )
+                                })
+                                .flatten()
+                                .buffer_unordered(10) // TODO: make configurable
+                                .try_collect()
+                                .await?;
+
+                        // TODO: check if input sources are sufficiently dealth with,
+                        // I think yes, they must be imported into the store by other
+                        // operations, so dealt with in the Some(…) match arm
+
+                        // synthesize the build request.
+                        let build_request = derivation_to_build_request(&drv, input_nodes)?;
+
+                        // create a build
+                        let build_result = self
+                            .build_service
+                            .as_ref()
+                            .do_build(build_request)
+                            .await
+                            .map_err(|e| std::io::Error::new(io::ErrorKind::Other, e))?;
+
+                        // TODO: refscan?
+
+                        // For each output, insert a PathInfo.
+                        for output in &build_result.outputs {
+                            let root_node = output.node.as_ref().expect("invalid root node");
+
+                            // calculate the nar representation
+                            let (nar_size, nar_sha256) =
+                                self.path_info_service.calculate_nar(root_node).await?;
+
+                            // assemble the PathInfo to persist
+                            let path_info = PathInfo {
+                                node: Some(tvix_castore::proto::Node {
+                                    node: Some(root_node.clone()),
+                                }),
+                                references: vec![], // TODO: refscan
+                                narinfo: Some(tvix_store::proto::NarInfo {
+                                    nar_size,
+                                    nar_sha256: Bytes::from(nar_sha256.to_vec()),
+                                    signatures: vec![],
+                                    reference_names: vec![], // TODO: refscan
+                                    deriver: Some(tvix_store::proto::StorePath {
+                                        name: drv_path
+                                            .name()
+                                            .strip_suffix(".drv")
+                                            .expect("missing .drv suffix")
+                                            .to_string(),
+                                        digest: drv_path.digest().to_vec().into(),
+                                    }),
+                                    ca: drv.fod_digest().map(
+                                        |fod_digest| -> tvix_store::proto::nar_info::Ca {
+                                            (&CAHash::Nar(nix_compat::nixhash::NixHash::Sha256(
+                                                fod_digest,
+                                            )))
+                                                .into()
+                                        },
+                                    ),
+                                }),
+                            };
+
+                            self.path_info_service
+                                .put(path_info)
+                                .await
+                                .map_err(|e| std::io::Error::new(io::ErrorKind::Other, e))?;
+                        }
+
+                        // find the output for the store path requested
+                        build_result
+                            .outputs
+                            .into_iter()
+                            .find(|output_node| {
+                                output_node.node.as_ref().expect("invalid node").get_name()
+                                    == store_path.to_string().as_bytes()
+                            })
+                            .expect("build didn't produce the store path")
+                            .node
+                            .expect("invalid node")
+                    }
+                }
+            }
+        };
+
+        // now with the root_node and sub_path, descend to the node requested.
+        // We convert sub_path to the castore model here.
+        let sub_path = tvix_castore::PathBuf::from_host_path(sub_path, true)?;
+
+        directoryservice::descend_to(&self.directory_service, root_node, sub_path)
+            .await
+            .map_err(|e| std::io::Error::new(io::ErrorKind::Other, e))
+    }
+
+    pub(crate) async fn node_to_path_info(
+        &self,
+        name: &str,
+        path: &Path,
+        ca: CAHash,
+        root_node: Node,
+    ) -> io::Result<(PathInfo, NixHash, StorePath)> {
+        // Ask the PathInfoService for the NAR size and sha256
+        // We always need it no matter what is the actual hash mode
+        // because the path info construct a narinfo which *always*
+        // require a SHA256 of the NAR representation and the NAR size.
+        let (nar_size, nar_sha256) = self
+            .path_info_service
+            .as_ref()
+            .calculate_nar(&root_node)
+            .await?;
+
+        // Calculate the output path. This might still fail, as some names are illegal.
+        let output_path =
+            nix_compat::store_path::build_ca_path(name, &ca, Vec::<String>::new(), false).map_err(
+                |_| {
+                    std::io::Error::new(
+                        std::io::ErrorKind::InvalidData,
+                        format!("invalid name: {}", name),
+                    )
+                },
+            )?;
+
+        // assemble a new root_node with a name that is derived from the nar hash.
+        let root_node = root_node.rename(output_path.to_string().into_bytes().into());
+        tvix_store::import::log_node(&root_node, path);
+
+        let path_info =
+            tvix_store::import::derive_nar_ca_path_info(nar_size, nar_sha256, Some(ca), root_node);
+
+        Ok((
+            path_info,
+            NixHash::Sha256(nar_sha256),
+            output_path.to_owned(),
+        ))
+    }
+
+    pub(crate) async fn register_node_in_path_info_service(
+        &self,
+        name: &str,
+        path: &Path,
+        ca: CAHash,
+        root_node: Node,
+    ) -> io::Result<StorePath> {
+        let (path_info, _, output_path) = self.node_to_path_info(name, path, ca, root_node).await?;
+        let _path_info = self.path_info_service.as_ref().put(path_info).await?;
+
+        Ok(output_path)
+    }
+
+    /// Transforms a BLAKE-3 digest into a SHA256 digest
+    /// by re-hashing the whole file.
+    pub(crate) async fn blob_to_sha256_hash(&self, blob_digest: B3Digest) -> io::Result<[u8; 32]> {
+        let mut reader = self
+            .blob_service
+            .open_read(&blob_digest)
+            .await?
+            .ok_or_else(|| {
+                io::Error::new(
+                    io::ErrorKind::NotFound,
+                    format!("blob represented by digest: '{}' not found", blob_digest),
+                )
+            })?;
+        // It is fine to use `AsyncIoBridge` here because hashing is not actually I/O.
+        let mut hasher = AsyncIoBridge(Sha256::new());
+
+        tokio::io::copy(&mut reader, &mut hasher).await?;
+        Ok(hasher.0.finalize().into())
+    }
+
+    pub async fn store_path_exists<'a>(&'a self, store_path: StorePathRef<'a>) -> io::Result<bool> {
+        Ok(self
+            .path_info_service
+            .as_ref()
+            .get(*store_path.digest())
+            .await?
+            .is_some())
+    }
+}
+
+impl EvalIO for TvixStoreIO {
+    #[instrument(skip(self), ret(level = Level::TRACE), err)]
+    fn path_exists(&self, path: &Path) -> io::Result<bool> {
+        if let Ok((store_path, sub_path)) =
+            StorePath::from_absolute_path_full(&path.to_string_lossy())
+        {
+            if self
+                .tokio_handle
+                .block_on(async { self.store_path_to_node(&store_path, &sub_path).await })?
+                .is_some()
+            {
+                Ok(true)
+            } else {
+                // As tvix-store doesn't manage /nix/store on the filesystem,
+                // we still need to also ask self.std_io here.
+                self.std_io.path_exists(path)
+            }
+        } else {
+            // The store path is no store path, so do regular StdIO.
+            self.std_io.path_exists(path)
+        }
+    }
+
+    #[instrument(skip(self), err)]
+    fn open(&self, path: &Path) -> io::Result<Box<dyn io::Read>> {
+        if let Ok((store_path, sub_path)) =
+            StorePath::from_absolute_path_full(&path.to_string_lossy())
+        {
+            if let Some(node) = self
+                .tokio_handle
+                .block_on(async { self.store_path_to_node(&store_path, &sub_path).await })?
+            {
+                // depending on the node type, treat open differently
+                match node {
+                    Node::Directory(_) => {
+                        // This would normally be a io::ErrorKind::IsADirectory (still unstable)
+                        Err(io::Error::new(
+                            io::ErrorKind::Unsupported,
+                            format!("tried to open directory at {:?}", path),
+                        ))
+                    }
+                    Node::File(file_node) => {
+                        let digest: B3Digest =
+                            file_node.digest.clone().try_into().map_err(|_e| {
+                                error!(
+                                    file_node = ?file_node,
+                                    "invalid digest"
+                                );
+                                io::Error::new(
+                                    io::ErrorKind::InvalidData,
+                                    format!("invalid digest length in file node: {:?}", file_node),
+                                )
+                            })?;
+
+                        self.tokio_handle.block_on(async {
+                            let resp = self.blob_service.as_ref().open_read(&digest).await?;
+                            match resp {
+                                Some(blob_reader) => {
+                                    // The VM Response needs a sync [std::io::Reader].
+                                    Ok(Box::new(SyncIoBridge::new(blob_reader))
+                                        as Box<dyn io::Read>)
+                                }
+                                None => {
+                                    error!(
+                                        blob.digest = %digest,
+                                        "blob not found",
+                                    );
+                                    Err(io::Error::new(
+                                        io::ErrorKind::NotFound,
+                                        format!("blob {} not found", &digest),
+                                    ))
+                                }
+                            }
+                        })
+                    }
+                    Node::Symlink(_symlink_node) => Err(io::Error::new(
+                        io::ErrorKind::Unsupported,
+                        "open for symlinks is unsupported",
+                    ))?,
+                }
+            } else {
+                // As tvix-store doesn't manage /nix/store on the filesystem,
+                // we still need to also ask self.std_io here.
+                self.std_io.open(path)
+            }
+        } else {
+            // The store path is no store path, so do regular StdIO.
+            self.std_io.open(path)
+        }
+    }
+
+    #[instrument(skip(self), ret(level = Level::TRACE), err)]
+    fn read_dir(&self, path: &Path) -> io::Result<Vec<(bytes::Bytes, FileType)>> {
+        if let Ok((store_path, sub_path)) =
+            StorePath::from_absolute_path_full(&path.to_string_lossy())
+        {
+            if let Some(node) = self
+                .tokio_handle
+                .block_on(async { self.store_path_to_node(&store_path, &sub_path).await })?
+            {
+                match node {
+                    Node::Directory(directory_node) => {
+                        // fetch the Directory itself.
+                        let digest: B3Digest =
+                            directory_node.digest.clone().try_into().map_err(|_e| {
+                                io::Error::new(
+                                    io::ErrorKind::InvalidData,
+                                    format!(
+                                        "invalid digest length in directory node: {:?}",
+                                        directory_node
+                                    ),
+                                )
+                            })?;
+
+                        if let Some(directory) = self.tokio_handle.block_on(async {
+                            self.directory_service.as_ref().get(&digest).await
+                        })? {
+                            let mut children: Vec<(bytes::Bytes, FileType)> = Vec::new();
+                            for node in directory.nodes() {
+                                children.push(match node {
+                                    Node::Directory(e) => (e.name, FileType::Directory),
+                                    Node::File(e) => (e.name, FileType::Regular),
+                                    Node::Symlink(e) => (e.name, FileType::Symlink),
+                                })
+                            }
+                            Ok(children)
+                        } else {
+                            // If we didn't get the directory node that's linked, that's a store inconsistency!
+                            error!(
+                                directory.digest = %digest,
+                                path = ?path,
+                                "directory not found",
+                            );
+                            Err(io::Error::new(
+                                io::ErrorKind::NotFound,
+                                format!("directory {digest} does not exist"),
+                            ))?
+                        }
+                    }
+                    Node::File(_file_node) => {
+                        // This would normally be a io::ErrorKind::NotADirectory (still unstable)
+                        Err(io::Error::new(
+                            io::ErrorKind::Unsupported,
+                            "tried to readdir path {:?}, which is a file",
+                        ))?
+                    }
+                    Node::Symlink(_symlink_node) => Err(io::Error::new(
+                        io::ErrorKind::Unsupported,
+                        "read_dir for symlinks is unsupported",
+                    ))?,
+                }
+            } else {
+                self.std_io.read_dir(path)
+            }
+        } else {
+            self.std_io.read_dir(path)
+        }
+    }
+
+    #[instrument(skip(self), ret(level = Level::TRACE), err)]
+    fn import_path(&self, path: &Path) -> io::Result<PathBuf> {
+        let output_path = self.tokio_handle.block_on(async {
+            tvix_store::import::import_path_as_nar_ca(
+                path,
+                tvix_store::import::path_to_name(path)?,
+                &self.blob_service,
+                &self.directory_service,
+                &self.path_info_service,
+            )
+            .await
+        })?;
+
+        Ok(output_path.to_absolute_path().into())
+    }
+
+    #[instrument(skip(self), ret(level = Level::TRACE))]
+    fn store_dir(&self) -> Option<String> {
+        Some("/nix/store".to_string())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use std::{path::Path, rc::Rc, sync::Arc};
+
+    use bstr::ByteSlice;
+    use tempfile::TempDir;
+    use tvix_build::buildservice::DummyBuildService;
+    use tvix_castore::{
+        blobservice::{BlobService, MemoryBlobService},
+        directoryservice::{DirectoryService, MemoryDirectoryService},
+    };
+    use tvix_eval::{EvalIO, EvaluationResult};
+    use tvix_store::pathinfoservice::MemoryPathInfoService;
+
+    use super::TvixStoreIO;
+    use crate::builtins::{add_derivation_builtins, add_fetcher_builtins, add_import_builtins};
+
+    /// evaluates a given nix expression and returns the result.
+    /// Takes care of setting up the evaluator so it knows about the
+    // `derivation` builtin.
+    fn eval(str: &str) -> EvaluationResult {
+        let blob_service = Arc::new(MemoryBlobService::default()) as Arc<dyn BlobService>;
+        let directory_service =
+            Arc::new(MemoryDirectoryService::default()) as Arc<dyn DirectoryService>;
+        let path_info_service = Arc::new(MemoryPathInfoService::new(
+            blob_service.clone(),
+            directory_service.clone(),
+        ));
+
+        let runtime = tokio::runtime::Runtime::new().unwrap();
+
+        let io = Rc::new(TvixStoreIO::new(
+            blob_service.clone(),
+            directory_service.clone(),
+            path_info_service,
+            Arc::<DummyBuildService>::default(),
+            runtime.handle().clone(),
+        ));
+        let mut eval = tvix_eval::Evaluation::new(io.clone() as Rc<dyn EvalIO>, true);
+
+        add_derivation_builtins(&mut eval, io.clone());
+        add_fetcher_builtins(&mut eval, io.clone());
+        add_import_builtins(&mut eval, io);
+
+        // run the evaluation itself.
+        eval.evaluate(str, None)
+    }
+
+    /// Helper function that takes a &Path, and invokes a tvix evaluator coercing that path to a string
+    /// (via "${/this/path}"). The path can be both absolute or not.
+    /// It returns Option<String>, depending on whether the evaluation succeeded or not.
+    fn import_path_and_compare<P: AsRef<Path>>(p: P) -> Option<String> {
+        // Try to import the path using "${/tmp/path/to/test}".
+        // The format string looks funny, the {} passed to Nix needs to be
+        // escaped.
+        let code = format!(r#""${{{}}}""#, p.as_ref().display());
+        let result = eval(&code);
+
+        if !result.errors.is_empty() {
+            return None;
+        }
+
+        let value = result.value.expect("must be some");
+        match value {
+            tvix_eval::Value::String(s) => Some(s.to_str_lossy().into_owned()),
+            _ => panic!("unexpected value type: {:?}", value),
+        }
+    }
+
+    /// Import a directory with a zero-sized ".keep" regular file.
+    /// Ensure it matches the (pre-recorded) store path that Nix would produce.
+    #[test]
+    fn import_directory() {
+        let tmpdir = TempDir::new().unwrap();
+
+        // create a directory named "test"
+        let src_path = tmpdir.path().join("test");
+        std::fs::create_dir(&src_path).unwrap();
+
+        // write a regular file `.keep`.
+        std::fs::write(src_path.join(".keep"), vec![]).unwrap();
+
+        // importing the path with .../test at the end.
+        assert_eq!(
+            Some("/nix/store/gq3xcv4xrj4yr64dflyr38acbibv3rm9-test".to_string()),
+            import_path_and_compare(&src_path)
+        );
+
+        // importing the path with .../test/. at the end.
+        assert_eq!(
+            Some("/nix/store/gq3xcv4xrj4yr64dflyr38acbibv3rm9-test".to_string()),
+            import_path_and_compare(src_path.join("."))
+        );
+    }
+
+    /// Import a file into the store. Nix uses the "recursive"/NAR-based hashing
+    /// scheme for these.
+    #[test]
+    fn import_file() {
+        let tmpdir = TempDir::new().unwrap();
+
+        // write a regular file `empty`.
+        std::fs::write(tmpdir.path().join("empty"), vec![]).unwrap();
+
+        assert_eq!(
+            Some("/nix/store/lx5i78a4izwk2qj1nq8rdc07y8zrwy90-empty".to_string()),
+            import_path_and_compare(tmpdir.path().join("empty"))
+        );
+
+        // write a regular file `hello.txt`.
+        std::fs::write(tmpdir.path().join("hello.txt"), b"Hello World!").unwrap();
+
+        assert_eq!(
+            Some("/nix/store/925f1jb1ajrypjbyq7rylwryqwizvhp0-hello.txt".to_string()),
+            import_path_and_compare(tmpdir.path().join("hello.txt"))
+        );
+    }
+
+    /// Invoke toString on a nonexisting file, and access the .file attribute.
+    /// This should not cause an error, because it shouldn't trigger an import,
+    /// and leave the path as-is.
+    #[test]
+    fn nonexisting_path_without_import() {
+        let result = eval("toString ({ line = 42; col = 42; file = /deep/thought; }.file)");
+
+        assert!(result.errors.is_empty(), "expect evaluation to succeed");
+        let value = result.value.expect("must be some");
+
+        match value {
+            tvix_eval::Value::String(s) => {
+                assert_eq!(*s, "/deep/thought");
+            }
+            _ => panic!("unexpected value type: {:?}", value),
+        }
+    }
+}
diff --git a/tvix/logo.webp b/tvix/logo.webp
new file mode 100644
index 0000000000..07bffc18b7
--- /dev/null
+++ b/tvix/logo.webp
Binary files differdiff --git a/tvix/nar-bridge/.gitignore b/tvix/nar-bridge/.gitignore
new file mode 100644
index 0000000000..d70e1f8120
--- /dev/null
+++ b/tvix/nar-bridge/.gitignore
@@ -0,0 +1,2 @@
+/nar-bridge-http
+/nar-bridge-pathinfo
diff --git a/tvix/nar-bridge/README.md b/tvix/nar-bridge/README.md
new file mode 100644
index 0000000000..b14ee7af7b
--- /dev/null
+++ b/tvix/nar-bridge/README.md
@@ -0,0 +1,7 @@
+# //tvix/nar-bridge
+
+This exposes a HTTP Binary cache interface (GET/HEAD/PUT requests) for a `tvix-
+store`.
+
+It can be used to configure a tvix-store as a substitutor for Nix, or to upload
+store paths from Nix via `nix copy` into a `tvix-store`.
diff --git a/tvix/nar-bridge/cmd/nar-bridge-http/main.go b/tvix/nar-bridge/cmd/nar-bridge-http/main.go
new file mode 100644
index 0000000000..171ea7f5bd
--- /dev/null
+++ b/tvix/nar-bridge/cmd/nar-bridge-http/main.go
@@ -0,0 +1,93 @@
+package main
+
+import (
+	"context"
+	"os"
+	"os/signal"
+	"runtime/debug"
+	"time"
+
+	"github.com/alecthomas/kong"
+
+	"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
+	"google.golang.org/grpc"
+	"google.golang.org/grpc/credentials/insecure"
+
+	castorev1pb "code.tvl.fyi/tvix/castore-go"
+	narBridgeHttp "code.tvl.fyi/tvix/nar-bridge/pkg/http"
+	storev1pb "code.tvl.fyi/tvix/store-go"
+	log "github.com/sirupsen/logrus"
+)
+
+// `help:"Expose a tvix-store gRPC Interface as HTTP NAR/NARinfo"`
+var cli struct {
+	LogLevel        string `enum:"trace,debug,info,warn,error,fatal,panic" help:"The log level to log with" default:"info"`
+	ListenAddr      string `name:"listen-addr" help:"The address this service listens on" type:"string" default:"[::]:9000"`                    //nolint:lll
+	EnableAccessLog bool   `name:"access-log" help:"Enable access logging" type:"bool" default:"true" negatable:""`                             //nolint:lll
+	StoreAddr       string `name:"store-addr" help:"The address to the tvix-store RPC interface this will connect to" default:"localhost:8000"` //nolint:lll
+	EnableOtlp      bool   `name:"otlp" help:"Enable OpenTelemetry for logs, spans, and metrics" default:"true"`                                //nolint:lll
+}
+
+func main() {
+	_ = kong.Parse(&cli)
+
+	logLevel, err := log.ParseLevel(cli.LogLevel)
+	if err != nil {
+		log.Panic("invalid log level")
+		return
+	}
+	log.SetLevel(logLevel)
+
+	ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
+	defer stop()
+
+	if cli.EnableOtlp {
+		buildInfo, ok := debug.ReadBuildInfo()
+		if !ok {
+			log.Fatal("failed to read build info")
+		}
+
+		shutdown, err := setupOpenTelemetry(ctx, "nar-bridge", buildInfo.Main.Version)
+		if err != nil {
+			log.WithError(err).Fatal("failed to setup OpenTelemetry")
+		}
+		defer shutdown(context.Background())
+	}
+
+	// connect to tvix-store
+	log.Debugf("Dialing to %v", cli.StoreAddr)
+	conn, err := grpc.DialContext(ctx, cli.StoreAddr,
+		grpc.WithTransportCredentials(insecure.NewCredentials()),
+		grpc.WithStatsHandler(otelgrpc.NewClientHandler()),
+	)
+	if err != nil {
+		log.Fatalf("did not connect: %v", err)
+	}
+	defer conn.Close()
+
+	s := narBridgeHttp.New(
+		castorev1pb.NewDirectoryServiceClient(conn),
+		castorev1pb.NewBlobServiceClient(conn),
+		storev1pb.NewPathInfoServiceClient(conn),
+		cli.EnableAccessLog,
+		30,
+	)
+
+	log.Printf("Starting nar-bridge-http at %v", cli.ListenAddr)
+	go s.ListenAndServe(cli.ListenAddr)
+
+	// listen for the interrupt signal.
+	<-ctx.Done()
+
+	// Restore default behaviour on the interrupt signal
+	stop()
+	log.Info("Received Signal, shutting down, press Ctl+C again to force.")
+
+	timeoutCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
+	defer cancel()
+
+	if err := s.Shutdown(timeoutCtx); err != nil {
+		log.WithError(err).Warn("failed to shutdown")
+		os.Exit(1)
+	}
+}
diff --git a/tvix/nar-bridge/cmd/nar-bridge-http/otel.go b/tvix/nar-bridge/cmd/nar-bridge-http/otel.go
new file mode 100644
index 0000000000..c446c6ec1a
--- /dev/null
+++ b/tvix/nar-bridge/cmd/nar-bridge-http/otel.go
@@ -0,0 +1,87 @@
+package main
+
+import (
+	"context"
+	"errors"
+
+	"go.opentelemetry.io/otel"
+	"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc"
+	"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
+	"go.opentelemetry.io/otel/propagation"
+	"go.opentelemetry.io/otel/sdk/metric"
+	"go.opentelemetry.io/otel/sdk/resource"
+	"go.opentelemetry.io/otel/sdk/trace"
+	semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
+)
+
+func setupOpenTelemetry(ctx context.Context, serviceName, serviceVersion string) (func(context.Context) error, error) {
+	var shutdownFuncs []func(context.Context) error
+	shutdown := func(ctx context.Context) error {
+		var err error
+		for _, fn := range shutdownFuncs {
+			err = errors.Join(err, fn(ctx))
+		}
+		shutdownFuncs = nil
+		return err
+	}
+
+	res, err := resource.Merge(
+		resource.Default(),
+		resource.NewWithAttributes(
+			semconv.SchemaURL,
+			semconv.ServiceName(serviceName),
+			semconv.ServiceVersion(serviceVersion),
+		),
+	)
+	if err != nil {
+		return nil, errors.Join(err, shutdown(ctx))
+	}
+
+	prop := propagation.NewCompositeTextMapPropagator(
+		propagation.TraceContext{},
+		propagation.Baggage{},
+	)
+	otel.SetTextMapPropagator(prop)
+
+	tracerProvider, err := newTraceProvider(ctx, res)
+	if err != nil {
+		return nil, errors.Join(err, shutdown(ctx))
+	}
+	shutdownFuncs = append(shutdownFuncs, tracerProvider.Shutdown)
+	otel.SetTracerProvider(tracerProvider)
+
+	meterProvider, err := newMeterProvider(ctx, res)
+	if err != nil {
+		return nil, errors.Join(err, shutdown(ctx))
+	}
+	shutdownFuncs = append(shutdownFuncs, meterProvider.Shutdown)
+	otel.SetMeterProvider(meterProvider)
+
+	return shutdown, nil
+}
+
+func newTraceProvider(ctx context.Context, res *resource.Resource) (*trace.TracerProvider, error) {
+	traceExporter, err := otlptracegrpc.New(ctx)
+	if err != nil {
+		return nil, err
+	}
+
+	traceProvider := trace.NewTracerProvider(
+		trace.WithBatcher(traceExporter),
+		trace.WithResource(res),
+	)
+	return traceProvider, nil
+}
+
+func newMeterProvider(ctx context.Context, res *resource.Resource) (*metric.MeterProvider, error) {
+	metricExporter, err := otlpmetricgrpc.New(ctx)
+	if err != nil {
+		return nil, err
+	}
+
+	meterProvider := metric.NewMeterProvider(
+		metric.WithResource(res),
+		metric.WithReader(metric.NewPeriodicReader(metricExporter)),
+	)
+	return meterProvider, nil
+}
diff --git a/tvix/nar-bridge/default.nix b/tvix/nar-bridge/default.nix
new file mode 100644
index 0000000000..c0247f279f
--- /dev/null
+++ b/tvix/nar-bridge/default.nix
@@ -0,0 +1,10 @@
+# Target containing just the proto files.
+
+{ depot, pkgs, lib, ... }:
+
+pkgs.buildGoModule {
+  name = "nar-bridge";
+  src = depot.third_party.gitignoreSource ./.;
+
+  vendorHash = "sha256-7jugbC5sEGhppjiZgnoLP5A6kQSaHK9vE6cXVZBG22s=";
+}
diff --git a/tvix/nar-bridge/go.mod b/tvix/nar-bridge/go.mod
new file mode 100644
index 0000000000..deb6943e23
--- /dev/null
+++ b/tvix/nar-bridge/go.mod
@@ -0,0 +1,54 @@
+module code.tvl.fyi/tvix/nar-bridge
+
+require (
+	code.tvl.fyi/tvix/castore-go v0.0.0-20231105151352-990d6ba2175e
+	code.tvl.fyi/tvix/store-go v0.0.0-20231105203234-f2baad42494f
+	github.com/alecthomas/kong v0.7.1
+	github.com/go-chi/chi v1.5.4
+	github.com/go-chi/chi/v5 v5.0.7
+	github.com/google/go-cmp v0.6.0
+	github.com/multiformats/go-multihash v0.2.1
+	github.com/nix-community/go-nix v0.0.0-20231012070617-9b176785e54d
+	github.com/sirupsen/logrus v1.9.0
+	github.com/stretchr/testify v1.8.4
+	go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0
+	go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0
+	go.opentelemetry.io/otel v1.22.0
+	go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.45.0
+	go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.22.0
+	go.opentelemetry.io/otel/sdk v1.22.0
+	go.opentelemetry.io/otel/sdk/metric v1.22.0
+	golang.org/x/sync v0.4.0
+	google.golang.org/grpc v1.60.1
+	google.golang.org/protobuf v1.32.0
+	lukechampine.com/blake3 v1.2.1
+)
+
+require (
+	github.com/cenkalti/backoff/v4 v4.2.1 // indirect
+	github.com/davecgh/go-spew v1.1.1 // indirect
+	github.com/felixge/httpsnoop v1.0.4 // indirect
+	github.com/go-logr/logr v1.4.1 // indirect
+	github.com/go-logr/stdr v1.2.2 // indirect
+	github.com/golang/protobuf v1.5.3 // indirect
+	github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect
+	github.com/klauspost/cpuid/v2 v2.2.5 // indirect
+	github.com/minio/sha256-simd v1.0.0 // indirect
+	github.com/mr-tron/base58 v1.2.0 // indirect
+	github.com/multiformats/go-varint v0.0.6 // indirect
+	github.com/pmezard/go-difflib v1.0.0 // indirect
+	github.com/spaolacci/murmur3 v1.1.0 // indirect
+	go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0 // indirect
+	go.opentelemetry.io/otel/metric v1.22.0 // indirect
+	go.opentelemetry.io/otel/trace v1.22.0 // indirect
+	go.opentelemetry.io/proto/otlp v1.0.0 // indirect
+	golang.org/x/crypto v0.18.0 // indirect
+	golang.org/x/net v0.20.0 // indirect
+	golang.org/x/sys v0.16.0 // indirect
+	golang.org/x/text v0.14.0 // indirect
+	google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97 // indirect
+	google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405 // indirect
+	gopkg.in/yaml.v3 v3.0.1 // indirect
+)
+
+go 1.19
diff --git a/tvix/nar-bridge/go.sum b/tvix/nar-bridge/go.sum
new file mode 100644
index 0000000000..39f77b9061
--- /dev/null
+++ b/tvix/nar-bridge/go.sum
@@ -0,0 +1,120 @@
+cloud.google.com/go/compute v1.23.0 h1:tP41Zoavr8ptEqaW6j+LQOnyBBhO7OkOMAGrgLopTwY=
+cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
+code.tvl.fyi/tvix/castore-go v0.0.0-20231105151352-990d6ba2175e h1:Nj+anfyEYeEdhnIo2BG/N1ZwQl1IvI7AH3TbNDLwUOA=
+code.tvl.fyi/tvix/castore-go v0.0.0-20231105151352-990d6ba2175e/go.mod h1:+vKbozsa04yy2TWh3kUVU568jaza3Hf0p1jAEoMoCwA=
+code.tvl.fyi/tvix/store-go v0.0.0-20231105203234-f2baad42494f h1:bN3K7oSu3IAHXqS3ETHUgpBPHF9+awKKBRLiM8/1tmI=
+code.tvl.fyi/tvix/store-go v0.0.0-20231105203234-f2baad42494f/go.mod h1:8jpfSC2rGi6VKaKOqqgmflPVSEpUawuRQFwQpQYCMiA=
+github.com/alecthomas/assert/v2 v2.1.0 h1:tbredtNcQnoSd3QBhQWI7QZ3XHOVkw1Moklp2ojoH/0=
+github.com/alecthomas/kong v0.7.1 h1:azoTh0IOfwlAX3qN9sHWTxACE2oV8Bg2gAwBsMwDQY4=
+github.com/alecthomas/kong v0.7.1/go.mod h1:n1iCIO2xS46oE8ZfYCNDqdR0b0wZNrXAIAqro/2132U=
+github.com/alecthomas/repr v0.1.0 h1:ENn2e1+J3k09gyj2shc0dHr/yjaWSHRlrJ4DPMevDqE=
+github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
+github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
+github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA=
+github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
+github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
+github.com/go-chi/chi v1.5.4 h1:QHdzF2szwjqVV4wmByUnTcsbIg7UGaQ0tPF2t5GcAIs=
+github.com/go-chi/chi v1.5.4/go.mod h1:uaf8YgoFazUOkPBG7fxPftUylNumIev9awIWOENIuEg=
+github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8=
+github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
+github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg=
+github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
+github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
+github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
+github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
+github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g=
+github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM=
+github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
+github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
+github.com/multiformats/go-multihash v0.2.1 h1:aem8ZT0VA2nCHHk7bPJ1BjUbHNciqZC/d16Vve9l108=
+github.com/multiformats/go-multihash v0.2.1/go.mod h1:WxoMcYG85AZVQUyRyo9s4wULvW5qrI9vb2Lt6evduFc=
+github.com/multiformats/go-varint v0.0.6 h1:gk85QWKxh3TazbLxED/NlDVv8+q+ReFJk7Y2W/KhfNY=
+github.com/multiformats/go-varint v0.0.6/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE=
+github.com/nix-community/go-nix v0.0.0-20231012070617-9b176785e54d h1:kwc1ivTuStqa3iBC2M/ojWPor88+YeIbZGeD2SlMYZ0=
+github.com/nix-community/go-nix v0.0.0-20231012070617-9b176785e54d/go.mod h1:4ZJah5sYrUSsWXIOJIsQ6iVOQyLO+ffhWXU3gblcO+E=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
+github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
+github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
+github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
+github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 h1:UNQQKPfTDe1J81ViolILjTKPr9WetKW6uei2hFgJmFs=
+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0/go.mod h1:r9vWsPS/3AQItv3OSlEJ/E4mbrhUbbw18meOjArPtKQ=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 h1:sv9kVfal0MK0wBMCOGr+HeJm9v803BkJxGrk2au7j08=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0/go.mod h1:SK2UL73Zy1quvRPonmOmRDiWk1KBV3LyIeeIxcEApWw=
+go.opentelemetry.io/otel v1.22.0 h1:xS7Ku+7yTFvDfDraDIJVpw7XPyuHlB9MCiqqX5mcJ6Y=
+go.opentelemetry.io/otel v1.22.0/go.mod h1:eoV4iAi3Ea8LkAEI9+GFT44O6T/D0GWAVFyZVCC6pMI=
+go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.45.0 h1:tfil6di0PoNV7FZdsCS7A5izZoVVQ7AuXtyekbOpG/I=
+go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.45.0/go.mod h1:AKFZIEPOnqB00P63bTjOiah4ZTaRzl1TKwUWpZdYUHI=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0 h1:9M3+rhx7kZCIQQhQRYaZCdNu1V73tm4TvXs2ntl98C4=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0/go.mod h1:noq80iT8rrHP1SfybmPiRGc9dc5M8RPmGvtwo7Oo7tc=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.22.0 h1:H2JFgRcGiyHg7H7bwcwaQJYrNFqCqrbTQ8K4p1OvDu8=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.22.0/go.mod h1:WfCWp1bGoYK8MeULtI15MmQVczfR+bFkk0DF3h06QmQ=
+go.opentelemetry.io/otel/metric v1.22.0 h1:lypMQnGyJYeuYPhOM/bgjbFM6WE44W1/T45er4d8Hhg=
+go.opentelemetry.io/otel/metric v1.22.0/go.mod h1:evJGjVpZv0mQ5QBRJoBF64yMuOf4xCWdXjK8pzFvliY=
+go.opentelemetry.io/otel/sdk v1.22.0 h1:6coWHw9xw7EfClIC/+O31R8IY3/+EiRFHevmHafB2Gw=
+go.opentelemetry.io/otel/sdk v1.22.0/go.mod h1:iu7luyVGYovrRpe2fmj3CVKouQNdTOkxtLzPvPz1DOc=
+go.opentelemetry.io/otel/sdk/metric v1.22.0 h1:ARrRetm1HCVxq0cbnaZQlfwODYJHo3gFL8Z3tSmHBcI=
+go.opentelemetry.io/otel/sdk/metric v1.22.0/go.mod h1:KjQGeMIDlBNEOo6HvjhxIec1p/69/kULDcp4gr0oLQQ=
+go.opentelemetry.io/otel/trace v1.22.0 h1:Hg6pPujv0XG9QaVbGOBVHunyuLcCC3jN7WEhPx83XD0=
+go.opentelemetry.io/otel/trace v1.22.0/go.mod h1:RbbHXVqKES9QhzZq/fE5UnOSILqRt40a21sPw2He1xo=
+go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
+go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
+go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
+golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
+golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
+golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
+golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
+golang.org/x/oauth2 v0.13.0 h1:jDDenyj+WgFtmV3zYVoi8aE2BwtXFLWOA67ZfNWftiY=
+golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
+golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
+golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
+golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
+golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
+google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b h1:+YaDE2r2OG8t/z5qmsh7Y+XXwCbvadxxZ0YY6mTdrVA=
+google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97 h1:W18sezcAYs+3tDZX4F80yctqa12jcP1PUS2gQu1zTPU=
+google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97/go.mod h1:iargEX0SFPm3xcfMI0d1domjg0ZF4Aa0p2awqyxhvF0=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405 h1:AB/lmRny7e2pLhFEYIbl5qkDAUt2h0ZRO4wGPhZf+ik=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405/go.mod h1:67X1fPuzjcrkymZzZV1vvkFeTn2Rvc6lYF9MYFGCcwE=
+google.golang.org/grpc v1.60.1 h1:26+wFr+cNqSGFcOXcabYC0lUVJVRa2Sb2ortSK7VrEU=
+google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
+google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI=
+lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
diff --git a/tvix/nar-bridge/pkg/http/nar_get.go b/tvix/nar-bridge/pkg/http/nar_get.go
new file mode 100644
index 0000000000..75797f8da9
--- /dev/null
+++ b/tvix/nar-bridge/pkg/http/nar_get.go
@@ -0,0 +1,197 @@
+package http
+
+import (
+	"bytes"
+	"context"
+	"encoding/base64"
+	"encoding/hex"
+	"errors"
+	"fmt"
+	"io"
+	"io/fs"
+	"net/http"
+	"sync"
+
+	castorev1pb "code.tvl.fyi/tvix/castore-go"
+	storev1pb "code.tvl.fyi/tvix/store-go"
+	"github.com/go-chi/chi/v5"
+	nixhash "github.com/nix-community/go-nix/pkg/hash"
+	"github.com/nix-community/go-nix/pkg/nixbase32"
+	log "github.com/sirupsen/logrus"
+)
+
+const (
+	narUrl = "/nar/{narhash:^([" + nixbase32.Alphabet + "]{52})$}.nar"
+)
+
+func renderNar(
+	ctx context.Context,
+	log *log.Entry,
+	directoryServiceClient castorev1pb.DirectoryServiceClient,
+	blobServiceClient castorev1pb.BlobServiceClient,
+	narHashDbMu *sync.Mutex,
+	narHashDb map[string]*narData,
+	w io.Writer,
+	narHash *nixhash.Hash,
+	headOnly bool,
+) error {
+	// look in the lookup table
+	narHashDbMu.Lock()
+	narData, found := narHashDb[narHash.SRIString()]
+	narHashDbMu.Unlock()
+
+	rootNode := narData.rootNode
+
+	// if we didn't find anything, return 404.
+	if !found {
+		return fmt.Errorf("narHash not found: %w", fs.ErrNotExist)
+	}
+
+	// if this was only a head request, we're done.
+	if headOnly {
+		return nil
+	}
+
+	directories := make(map[string]*castorev1pb.Directory)
+
+	// If the root node is a directory, ask the directory service for all directories
+	if pathInfoDirectory := rootNode.GetDirectory(); pathInfoDirectory != nil {
+		rootDirectoryDigest := pathInfoDirectory.GetDigest()
+		log = log.WithField("root_directory", base64.StdEncoding.EncodeToString(rootDirectoryDigest))
+
+		directoryStream, err := directoryServiceClient.Get(ctx, &castorev1pb.GetDirectoryRequest{
+			ByWhat: &castorev1pb.GetDirectoryRequest_Digest{
+				Digest: rootDirectoryDigest,
+			},
+			Recursive: true,
+		})
+		if err != nil {
+			return fmt.Errorf("unable to query directory stream: %w", err)
+		}
+
+		// For now, we just stream all of these locally and put them into a hashmap,
+		// which is used in the lookup function below.
+		for {
+			directory, err := directoryStream.Recv()
+			if err != nil {
+				if err == io.EOF {
+					break
+				}
+				return fmt.Errorf("unable to receive from directory stream: %w", err)
+			}
+
+			// calculate directory digest
+			// TODO: do we need to do any more validation?
+			directoryDgst, err := directory.Digest()
+			if err != nil {
+				return fmt.Errorf("unable to calculate directory digest: %w", err)
+			}
+
+			log.WithField("directory", base64.StdEncoding.EncodeToString(directoryDgst)).Debug("received directory node")
+
+			directories[hex.EncodeToString(directoryDgst)] = directory
+		}
+
+	}
+
+	// render the NAR file
+	err := storev1pb.Export(
+		w,
+		rootNode,
+		func(directoryDigest []byte) (*castorev1pb.Directory, error) {
+			log.WithField("directory", base64.StdEncoding.EncodeToString(directoryDigest)).Debug("Get directory")
+			directoryRefStr := hex.EncodeToString(directoryDigest)
+			directory, found := directories[directoryRefStr]
+			if !found {
+				return nil, fmt.Errorf(
+					"directory with hash %v does not exist: %w",
+					directoryDigest,
+					fs.ErrNotExist,
+				)
+			}
+
+			return directory, nil
+		},
+		func(blobDigest []byte) (io.ReadCloser, error) {
+			log.WithField("blob", base64.StdEncoding.EncodeToString(blobDigest)).Debug("Get blob")
+			resp, err := blobServiceClient.Read(ctx, &castorev1pb.ReadBlobRequest{
+				Digest: blobDigest,
+			})
+			if err != nil {
+				return nil, fmt.Errorf("unable to get blob: %w", err)
+			}
+
+			// set up a pipe, let a goroutine write, return the reader.
+			pR, pW := io.Pipe()
+
+			go func() {
+				for {
+					chunk, err := resp.Recv()
+					if errors.Is(err, io.EOF) {
+						break
+					}
+					if err != nil {
+						pW.CloseWithError(fmt.Errorf("receiving chunk: %w", err))
+						return
+					}
+
+					// write the received chunk to the writer part of the pipe
+					if _, err := io.Copy(pW, bytes.NewReader(chunk.GetData())); err != nil {
+						log.WithError(err).Error("writing chunk to pipe")
+						pW.CloseWithError(fmt.Errorf("writing chunk to pipe: %w", err))
+						return
+					}
+				}
+				pW.Close()
+
+			}()
+
+			return io.NopCloser(pR), nil
+		},
+	)
+	if err != nil {
+		return fmt.Errorf("unable to export nar: %w", err)
+	}
+	return nil
+}
+
+func registerNarGet(s *Server) {
+	// produce a handler for rendering NAR files.
+	genNarHandler := func(isHead bool) func(w http.ResponseWriter, r *http.Request) {
+		return func(w http.ResponseWriter, r *http.Request) {
+			defer r.Body.Close()
+
+			ctx := r.Context()
+
+			// parse the narhash sent in the request URL
+			narHash, err := parseNarHashFromUrl(chi.URLParamFromCtx(ctx, "narhash"))
+			if err != nil {
+				log.WithError(err).WithField("url", r.URL).Error("unable to decode nar hash from url")
+				w.WriteHeader(http.StatusBadRequest)
+				_, err := w.Write([]byte("unable to decode nar hash from url"))
+				if err != nil {
+					log.WithError(err).Errorf("unable to write error message to client")
+				}
+
+				return
+			}
+
+			log := log.WithField("narhash_url", narHash.SRIString())
+
+			// TODO: inline more of that function here?
+			err = renderNar(ctx, log, s.directoryServiceClient, s.blobServiceClient, &s.narDbMu, s.narDb, w, narHash, isHead)
+			if err != nil {
+				if errors.Is(err, fs.ErrNotExist) {
+					w.WriteHeader(http.StatusNotFound)
+				} else {
+					log.WithError(err).Warn("unable to render nar")
+					w.WriteHeader(http.StatusInternalServerError)
+				}
+			}
+
+		}
+	}
+
+	s.handler.Head(narUrl, genNarHandler(true))
+	s.handler.Get(narUrl, genNarHandler(false))
+}
diff --git a/tvix/nar-bridge/pkg/http/nar_put.go b/tvix/nar-bridge/pkg/http/nar_put.go
new file mode 100644
index 0000000000..fdfa20f9c3
--- /dev/null
+++ b/tvix/nar-bridge/pkg/http/nar_put.go
@@ -0,0 +1,141 @@
+package http
+
+import (
+	"bufio"
+	"bytes"
+	"fmt"
+	"net/http"
+
+	castorev1pb "code.tvl.fyi/tvix/castore-go"
+	"code.tvl.fyi/tvix/nar-bridge/pkg/importer"
+	"github.com/go-chi/chi/v5"
+	mh "github.com/multiformats/go-multihash/core"
+	nixhash "github.com/nix-community/go-nix/pkg/hash"
+	"github.com/sirupsen/logrus"
+	log "github.com/sirupsen/logrus"
+)
+
+func registerNarPut(s *Server) {
+	s.handler.Put(narUrl, func(w http.ResponseWriter, r *http.Request) {
+		defer r.Body.Close()
+
+		ctx := r.Context()
+
+		// parse the narhash sent in the request URL
+		narHashFromUrl, err := parseNarHashFromUrl(chi.URLParamFromCtx(ctx, "narhash"))
+		if err != nil {
+			log.WithError(err).WithField("url", r.URL).Error("unable to decode nar hash from url")
+			w.WriteHeader(http.StatusBadRequest)
+			_, err := w.Write([]byte("unable to decode nar hash from url"))
+			if err != nil {
+				log.WithError(err).Error("unable to write error message to client")
+			}
+
+			return
+		}
+
+		log := log.WithField("narhash_url", narHashFromUrl.SRIString())
+
+		directoriesUploader := importer.NewDirectoriesUploader(ctx, s.directoryServiceClient)
+		defer directoriesUploader.Done() //nolint:errcheck
+
+		rootNode, narSize, narSha256, err := importer.Import(
+			ctx,
+			// buffer the body by 10MiB
+			bufio.NewReaderSize(r.Body, 10*1024*1024),
+			importer.GenBlobUploaderCb(ctx, s.blobServiceClient),
+			func(directory *castorev1pb.Directory) ([]byte, error) {
+				return directoriesUploader.Put(directory)
+			},
+		)
+
+		if err != nil {
+			log.Errorf("error during NAR import: %v", err)
+			w.WriteHeader(http.StatusInternalServerError)
+			_, err := w.Write([]byte(fmt.Sprintf("error during NAR import: %v", err)))
+			if err != nil {
+				log.WithError(err).Errorf("unable to write error message to client")
+			}
+
+			return
+		}
+
+		log.Debug("closing the stream")
+
+		// Close the directories uploader
+		directoriesPutResponse, err := directoriesUploader.Done()
+		if err != nil {
+			log.WithError(err).Error("error during directory upload")
+			w.WriteHeader(http.StatusBadRequest)
+			_, err := w.Write([]byte("error during directory upload"))
+			if err != nil {
+				log.WithError(err).Errorf("unable to write error message to client")
+			}
+
+			return
+		}
+		// If we uploaded directories (so directoriesPutResponse doesn't return null),
+		// the RootDigest field in directoriesPutResponse should match the digest
+		// returned in the PathInfo struct returned by the `Import` call.
+		// This check ensures the server-side came up with the same root hash.
+
+		if directoriesPutResponse != nil {
+			rootDigestPathInfo := rootNode.GetDirectory().GetDigest()
+			rootDigestDirectoriesPutResponse := directoriesPutResponse.GetRootDigest()
+
+			log := log.WithFields(logrus.Fields{
+				"root_digest_pathinfo":             rootDigestPathInfo,
+				"root_digest_directories_put_resp": rootDigestDirectoriesPutResponse,
+			})
+			if !bytes.Equal(rootDigestPathInfo, rootDigestDirectoriesPutResponse) {
+				log.Errorf("returned root digest doesn't match what's calculated")
+
+				w.WriteHeader(http.StatusBadRequest)
+				_, err := w.Write([]byte("error in root digest calculation"))
+				if err != nil {
+					log.WithError(err).Error("unable to write error message to client")
+				}
+
+				return
+			}
+		}
+
+		// Compare the nar hash specified in the URL with the one that has been
+		// calculated while processing the NAR file.
+		narHash, err := nixhash.FromHashTypeAndDigest(mh.SHA2_256, narSha256)
+		if err != nil {
+			panic("must parse nixbase32")
+		}
+
+		if !bytes.Equal(narHashFromUrl.Digest(), narHash.Digest()) {
+			log := log.WithFields(logrus.Fields{
+				"narhash_received_sha256": narHash.SRIString(),
+				"narsize":                 narSize,
+			})
+			log.Error("received bytes don't match narhash from URL")
+
+			w.WriteHeader(http.StatusBadRequest)
+			_, err := w.Write([]byte("received bytes don't match narHash specified in URL"))
+			if err != nil {
+				log.WithError(err).Errorf("unable to write error message to client")
+			}
+
+			return
+		}
+
+		// Insert the partial pathinfo structs into our lookup map,
+		// so requesting the NAR file will be possible.
+		// The same  might exist already, but it'll have the same contents (so
+		// replacing will be a no-op), except maybe the root node Name field value, which
+		// is safe to ignore (as not part of the NAR).
+		s.narDbMu.Lock()
+		s.narDb[narHash.SRIString()] = &narData{
+			rootNode: rootNode,
+			narSize:  narSize,
+		}
+		s.narDbMu.Unlock()
+
+		// Done!
+	})
+
+}
diff --git a/tvix/nar-bridge/pkg/http/narinfo.go b/tvix/nar-bridge/pkg/http/narinfo.go
new file mode 100644
index 0000000000..e5b99a9505
--- /dev/null
+++ b/tvix/nar-bridge/pkg/http/narinfo.go
@@ -0,0 +1,51 @@
+package http
+
+import (
+	"fmt"
+
+	storev1pb "code.tvl.fyi/tvix/store-go"
+	mh "github.com/multiformats/go-multihash/core"
+	nixhash "github.com/nix-community/go-nix/pkg/hash"
+
+	"github.com/nix-community/go-nix/pkg/narinfo"
+	"github.com/nix-community/go-nix/pkg/narinfo/signature"
+	"github.com/nix-community/go-nix/pkg/nixbase32"
+)
+
+// ToNixNarInfo converts the PathInfo to a narinfo.NarInfo.
+func ToNixNarInfo(p *storev1pb.PathInfo) (*narinfo.NarInfo, error) {
+	// ensure the PathInfo is valid, and extract the StorePath from the node in
+	// there.
+	storePath, err := p.Validate()
+	if err != nil {
+		return nil, fmt.Errorf("failed to validate PathInfo: %w", err)
+	}
+
+	// convert the signatures from storev1pb signatures to narinfo signatures
+	narinfoSignatures := make([]signature.Signature, len(p.GetNarinfo().GetSignatures()))
+	for i, pathInfoSignature := range p.GetNarinfo().GetSignatures() {
+		narinfoSignatures[i] = signature.Signature{
+			Name: pathInfoSignature.GetName(),
+			Data: pathInfoSignature.GetData(),
+		}
+	}
+
+	// produce nixhash for the narsha256.
+	narHash, err := nixhash.FromHashTypeAndDigest(
+		mh.SHA2_256,
+		p.GetNarinfo().GetNarSha256(),
+	)
+	if err != nil {
+		return nil, fmt.Errorf("invalid narsha256: %w", err)
+	}
+
+	return &narinfo.NarInfo{
+		StorePath:   storePath.Absolute(),
+		URL:         "nar/" + nixbase32.EncodeToString(narHash.Digest()) + ".nar",
+		Compression: "none",
+		NarHash:     narHash,
+		NarSize:     uint64(p.GetNarinfo().GetNarSize()),
+		References:  p.GetNarinfo().GetReferenceNames(),
+		Signatures:  narinfoSignatures,
+	}, nil
+}
diff --git a/tvix/nar-bridge/pkg/http/narinfo_get.go b/tvix/nar-bridge/pkg/http/narinfo_get.go
new file mode 100644
index 0000000000..d43cb58078
--- /dev/null
+++ b/tvix/nar-bridge/pkg/http/narinfo_get.go
@@ -0,0 +1,137 @@
+package http
+
+import (
+	"context"
+	"encoding/base64"
+	"errors"
+	"fmt"
+	"io"
+	"io/fs"
+	"net/http"
+	"strings"
+	"sync"
+
+	storev1pb "code.tvl.fyi/tvix/store-go"
+	"github.com/go-chi/chi/v5"
+	nixhash "github.com/nix-community/go-nix/pkg/hash"
+	"github.com/nix-community/go-nix/pkg/nixbase32"
+	log "github.com/sirupsen/logrus"
+	"google.golang.org/grpc/codes"
+	"google.golang.org/grpc/status"
+)
+
+// renderNarinfo writes narinfo contents to a passed io.Writer, or a returns a
+// (wrapped) io.ErrNoExist error if something doesn't exist.
+// if headOnly is set to true, only the existence is checked, but no content is
+// actually written.
+func renderNarinfo(
+	ctx context.Context,
+	log *log.Entry,
+	pathInfoServiceClient storev1pb.PathInfoServiceClient,
+	narHashToPathInfoMu *sync.Mutex,
+	narHashToPathInfo map[string]*narData,
+	outputHash []byte,
+	w io.Writer,
+	headOnly bool,
+) error {
+	pathInfo, err := pathInfoServiceClient.Get(ctx, &storev1pb.GetPathInfoRequest{
+		ByWhat: &storev1pb.GetPathInfoRequest_ByOutputHash{
+			ByOutputHash: outputHash,
+		},
+	})
+	if err != nil {
+		st, ok := status.FromError(err)
+		if ok {
+			if st.Code() == codes.NotFound {
+				return fmt.Errorf("output hash %v not found: %w", base64.StdEncoding.EncodeToString(outputHash), fs.ErrNotExist)
+			}
+			return fmt.Errorf("unable to get pathinfo, code %v: %w", st.Code(), err)
+		}
+
+		return fmt.Errorf("unable to get pathinfo: %w", err)
+	}
+
+	log = log.WithField("pathInfo", pathInfo)
+
+	if _, err := pathInfo.Validate(); err != nil {
+		log.WithError(err).Error("unable to validate PathInfo")
+
+		return fmt.Errorf("unable to validate PathInfo: %w", err)
+	}
+
+	if pathInfo.GetNarinfo() == nil {
+		log.Error("PathInfo doesn't contain Narinfo field")
+
+		return fmt.Errorf("PathInfo doesn't contain Narinfo field")
+	}
+
+	// extract the NARHash. This must succeed, as Validate() did succeed.
+	narHash, err := nixhash.FromHashTypeAndDigest(0x12, pathInfo.GetNarinfo().GetNarSha256())
+	if err != nil {
+		panic("must parse NarHash")
+	}
+
+	// add things to the lookup table, in case the same process didn't handle the NAR hash yet.
+	narHashToPathInfoMu.Lock()
+	narHashToPathInfo[narHash.SRIString()] = &narData{
+		rootNode: pathInfo.GetNode(),
+		narSize:  pathInfo.GetNarinfo().GetNarSize(),
+	}
+	narHashToPathInfoMu.Unlock()
+
+	if headOnly {
+		return nil
+	}
+
+	// convert the PathInfo to NARInfo.
+	narInfo, err := ToNixNarInfo(pathInfo)
+
+	// Write it out to the client.
+	_, err = io.Copy(w, strings.NewReader(narInfo.String()))
+	if err != nil {
+		return fmt.Errorf("unable to write narinfo to client: %w", err)
+	}
+
+	return nil
+}
+
+func registerNarinfoGet(s *Server) {
+	// GET/HEAD $outHash.narinfo looks up the PathInfo from the tvix-store,
+	// and, if it's a GET request, render a .narinfo file to the client.
+	// In both cases it will keep the PathInfo in the lookup map,
+	// so a subsequent GET/HEAD /nar/ $narhash.nar request can find it.
+	genNarinfoHandler := func(isHead bool) func(w http.ResponseWriter, r *http.Request) {
+		return func(w http.ResponseWriter, r *http.Request) {
+			defer r.Body.Close()
+
+			ctx := r.Context()
+			log := log.WithField("outputhash", chi.URLParamFromCtx(ctx, "outputhash"))
+
+			// parse the output hash sent in the request URL
+			outputHash, err := nixbase32.DecodeString(chi.URLParamFromCtx(ctx, "outputhash"))
+			if err != nil {
+				log.WithError(err).Error("unable to decode output hash from url")
+				w.WriteHeader(http.StatusBadRequest)
+				_, err := w.Write([]byte("unable to decode output hash from url"))
+				if err != nil {
+					log.WithError(err).Errorf("unable to write error message to client")
+				}
+
+				return
+			}
+
+			err = renderNarinfo(ctx, log, s.pathInfoServiceClient, &s.narDbMu, s.narDb, outputHash, w, isHead)
+			if err != nil {
+				if errors.Is(err, fs.ErrNotExist) {
+					w.WriteHeader(http.StatusNotFound)
+				} else {
+					log.WithError(err).Warn("unable to render narinfo")
+					w.WriteHeader(http.StatusInternalServerError)
+				}
+			}
+		}
+	}
+
+	s.handler.Get("/{outputhash:^["+nixbase32.Alphabet+"]{32}}.narinfo", genNarinfoHandler(false))
+	s.handler.Head("/{outputhash:^["+nixbase32.Alphabet+"]{32}}.narinfo", genNarinfoHandler(true))
+}
diff --git a/tvix/nar-bridge/pkg/http/narinfo_put.go b/tvix/nar-bridge/pkg/http/narinfo_put.go
new file mode 100644
index 0000000000..fd588bec86
--- /dev/null
+++ b/tvix/nar-bridge/pkg/http/narinfo_put.go
@@ -0,0 +1,103 @@
+package http
+
+import (
+	"net/http"
+
+	"code.tvl.fyi/tvix/nar-bridge/pkg/importer"
+	"github.com/go-chi/chi/v5"
+	"github.com/nix-community/go-nix/pkg/narinfo"
+	"github.com/nix-community/go-nix/pkg/nixbase32"
+	"github.com/sirupsen/logrus"
+	log "github.com/sirupsen/logrus"
+)
+
+func registerNarinfoPut(s *Server) {
+	s.handler.Put("/{outputhash:^["+nixbase32.Alphabet+"]{32}}.narinfo", func(w http.ResponseWriter, r *http.Request) {
+		defer r.Body.Close()
+
+		ctx := r.Context()
+		log := log.WithField("outputhash", chi.URLParamFromCtx(ctx, "outputhash"))
+
+		// TODO: decide on merging behaviour.
+		// Maybe it's fine to add if contents are the same, but more sigs can be added?
+		// Right now, just replace a .narinfo for a path that already exists.
+
+		// read and parse the .narinfo file
+		narInfo, err := narinfo.Parse(r.Body)
+		if err != nil {
+			log.WithError(err).Error("unable to parse narinfo")
+			w.WriteHeader(http.StatusBadRequest)
+			_, err := w.Write([]byte("unable to parse narinfo"))
+			if err != nil {
+				log.WithError(err).Errorf("unable to write error message to client")
+			}
+
+			return
+		}
+
+		log = log.WithFields(logrus.Fields{
+			"narhash":     narInfo.NarHash.SRIString(),
+			"output_path": narInfo.StorePath,
+		})
+
+		// look up the narHash in our temporary map
+		s.narDbMu.Lock()
+		narData, found := s.narDb[narInfo.NarHash.SRIString()]
+		s.narDbMu.Unlock()
+		if !found {
+			log.Error("unable to find referred NAR")
+			w.WriteHeader(http.StatusBadRequest)
+			_, err := w.Write([]byte("unable to find referred NAR"))
+			if err != nil {
+				log.WithError(err).Errorf("unable to write error message to client")
+			}
+
+			return
+		}
+
+		rootNode := narData.rootNode
+
+		// compare fields with what we computed while receiving the NAR file
+
+		// NarSize needs to match
+		if narData.narSize != narInfo.NarSize {
+			log.Error("narsize mismatch")
+			w.WriteHeader(http.StatusBadRequest)
+			_, err := w.Write([]byte("unable to parse narinfo"))
+			if err != nil {
+				log.WithError(err).Errorf("unable to write error message to client")
+			}
+
+			return
+		}
+
+		pathInfo, err := importer.GenPathInfo(rootNode, narInfo)
+		if err != nil {
+			log.WithError(err).Error("unable to generate PathInfo")
+
+			w.WriteHeader(http.StatusInternalServerError)
+			_, err := w.Write([]byte("unable to generate PathInfo"))
+			if err != nil {
+				log.WithError(err).Errorf("unable to write error message to client")
+			}
+
+			return
+		}
+
+		log.WithField("pathInfo", pathInfo).Debug("inserted new pathInfo")
+
+		receivedPathInfo, err := s.pathInfoServiceClient.Put(ctx, pathInfo)
+		if err != nil {
+			log.WithError(err).Error("unable to upload pathinfo to service")
+			w.WriteHeader(http.StatusInternalServerError)
+			_, err := w.Write([]byte("unable to upload pathinfo to server"))
+			if err != nil {
+				log.WithError(err).Errorf("unable to write error message to client")
+			}
+
+			return
+		}
+
+		log.WithField("pathInfo", receivedPathInfo).Debug("got back PathInfo")
+	})
+}
diff --git a/tvix/nar-bridge/pkg/http/server.go b/tvix/nar-bridge/pkg/http/server.go
new file mode 100644
index 0000000000..fbcb20be18
--- /dev/null
+++ b/tvix/nar-bridge/pkg/http/server.go
@@ -0,0 +1,119 @@
+package http
+
+import (
+	"context"
+	"fmt"
+	"net"
+	"net/http"
+	"strings"
+	"sync"
+	"time"
+
+	castorev1pb "code.tvl.fyi/tvix/castore-go"
+	storev1pb "code.tvl.fyi/tvix/store-go"
+	"github.com/go-chi/chi/middleware"
+	"github.com/go-chi/chi/v5"
+	log "github.com/sirupsen/logrus"
+	"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
+)
+
+type Server struct {
+	srv     *http.Server
+	handler chi.Router
+
+	directoryServiceClient castorev1pb.DirectoryServiceClient
+	blobServiceClient      castorev1pb.BlobServiceClient
+	pathInfoServiceClient  storev1pb.PathInfoServiceClient
+
+	// When uploading NAR files to a HTTP binary cache, the .nar
+	// files are uploaded before the .narinfo files.
+	// We need *both* to be able to fully construct a PathInfo object.
+	// Keep a in-memory map of narhash(es) (in SRI) to (unnamed) root node and nar
+	// size.
+	// This is necessary until we can ask a PathInfoService for a node with a given
+	// narSha256.
+	narDbMu sync.Mutex
+	narDb   map[string]*narData
+}
+
+type narData struct {
+	rootNode *castorev1pb.Node
+	narSize  uint64
+}
+
+func New(
+	directoryServiceClient castorev1pb.DirectoryServiceClient,
+	blobServiceClient castorev1pb.BlobServiceClient,
+	pathInfoServiceClient storev1pb.PathInfoServiceClient,
+	enableAccessLog bool,
+	priority int,
+) *Server {
+	r := chi.NewRouter()
+	r.Use(func(h http.Handler) http.Handler {
+		return otelhttp.NewHandler(h, "http.request")
+	})
+
+	if enableAccessLog {
+		r.Use(middleware.Logger)
+	}
+
+	r.Get("/", func(w http.ResponseWriter, r *http.Request) {
+		_, err := w.Write([]byte("nar-bridge"))
+		if err != nil {
+			log.Errorf("Unable to write response: %v", err)
+		}
+	})
+
+	r.Get("/nix-cache-info", func(w http.ResponseWriter, r *http.Request) {
+		_, err := w.Write([]byte(fmt.Sprintf("StoreDir: /nix/store\nWantMassQuery: 1\nPriority: %d\n", priority)))
+		if err != nil {
+			log.Errorf("Unable to write response: %v", err)
+		}
+	})
+
+	s := &Server{
+		handler:                r,
+		directoryServiceClient: directoryServiceClient,
+		blobServiceClient:      blobServiceClient,
+		pathInfoServiceClient:  pathInfoServiceClient,
+		narDb:                  make(map[string]*narData),
+	}
+
+	registerNarPut(s)
+	registerNarinfoPut(s)
+
+	registerNarinfoGet(s)
+	registerNarGet(s)
+
+	return s
+}
+
+func (s *Server) Shutdown(ctx context.Context) error {
+	return s.srv.Shutdown(ctx)
+}
+
+// ListenAndServer starts the webserver, and waits for it being closed or
+// shutdown, after which it'll return ErrServerClosed.
+func (s *Server) ListenAndServe(addr string) error {
+	s.srv = &http.Server{
+		Handler:      s.handler,
+		ReadTimeout:  500 * time.Second,
+		WriteTimeout: 500 * time.Second,
+		IdleTimeout:  500 * time.Second,
+	}
+
+	var listener net.Listener
+	var err error
+
+	// check addr. If it contains slashes, assume it's a unix domain socket.
+	if strings.Contains(addr, "/") {
+		listener, err = net.Listen("unix", addr)
+	} else {
+		listener, err = net.Listen("tcp", addr)
+	}
+	if err != nil {
+		return fmt.Errorf("unable to listen on %v: %w", addr, err)
+	}
+
+	return s.srv.Serve(listener)
+}
diff --git a/tvix/nar-bridge/pkg/http/util.go b/tvix/nar-bridge/pkg/http/util.go
new file mode 100644
index 0000000000..60febea1f4
--- /dev/null
+++ b/tvix/nar-bridge/pkg/http/util.go
@@ -0,0 +1,24 @@
+package http
+
+import (
+	"fmt"
+	nixhash "github.com/nix-community/go-nix/pkg/hash"
+)
+
+// parseNarHashFromUrl parses a nixbase32 string representing a sha256 NarHash
+// and returns a nixhash.Hash when it was able to parse, or an error.
+func parseNarHashFromUrl(narHashFromUrl string) (*nixhash.Hash, error) {
+	// peek at the length. If it's 52 characters, assume sha256,
+	// if it's something else, this is an error.
+	l := len(narHashFromUrl)
+	if l != 52 {
+		return nil, fmt.Errorf("invalid length of narHash: %v", l)
+	}
+
+	nixHash, err := nixhash.ParseNixBase32("sha256:" + narHashFromUrl)
+	if err != nil {
+		return nil, fmt.Errorf("unable to parse nixbase32 hash: %w", err)
+	}
+
+	return nixHash, nil
+}
diff --git a/tvix/nar-bridge/pkg/importer/blob_upload.go b/tvix/nar-bridge/pkg/importer/blob_upload.go
new file mode 100644
index 0000000000..c1255dd3ad
--- /dev/null
+++ b/tvix/nar-bridge/pkg/importer/blob_upload.go
@@ -0,0 +1,71 @@
+package importer
+
+import (
+	"bufio"
+	"context"
+	"encoding/base64"
+	"errors"
+	"fmt"
+	"io"
+
+	castorev1pb "code.tvl.fyi/tvix/castore-go"
+	log "github.com/sirupsen/logrus"
+)
+
+// the size of individual BlobChunk we send when uploading to BlobService.
+const chunkSize = 1024 * 1024
+
+// this produces a callback function that can be used as blobCb for the
+// importer.Import function call.
+func GenBlobUploaderCb(ctx context.Context, blobServiceClient castorev1pb.BlobServiceClient) func(io.Reader) ([]byte, error) {
+	return func(blobReader io.Reader) ([]byte, error) {
+		// Ensure the blobReader is buffered to at least the chunk size.
+		blobReader = bufio.NewReaderSize(blobReader, chunkSize)
+
+		putter, err := blobServiceClient.Put(ctx)
+		if err != nil {
+			// return error to the importer
+			return nil, fmt.Errorf("error from blob service: %w", err)
+		}
+
+		blobSize := 0
+		chunk := make([]byte, chunkSize)
+
+		for {
+			n, err := blobReader.Read(chunk)
+			if err != nil && !errors.Is(err, io.EOF) {
+				return nil, fmt.Errorf("unable to read from blobreader: %w", err)
+			}
+
+			if n != 0 {
+				log.WithField("chunk_size", n).Debug("sending chunk")
+				blobSize += n
+
+				// send the blob chunk to the server. The err is only valid in the inner scope
+				if err := putter.Send(&castorev1pb.BlobChunk{
+					Data: chunk[:n],
+				}); err != nil {
+					return nil, fmt.Errorf("sending blob chunk: %w", err)
+				}
+			}
+
+			// if our read from blobReader returned an EOF, we're done reading
+			if errors.Is(err, io.EOF) {
+				break
+			}
+
+		}
+
+		resp, err := putter.CloseAndRecv()
+		if err != nil {
+			return nil, fmt.Errorf("close blob putter: %w", err)
+		}
+
+		log.WithFields(log.Fields{
+			"blob_digest": base64.StdEncoding.EncodeToString(resp.GetDigest()),
+			"blob_size":   blobSize,
+		}).Debug("uploaded blob")
+
+		return resp.GetDigest(), nil
+	}
+}
diff --git a/tvix/nar-bridge/pkg/importer/counting_writer.go b/tvix/nar-bridge/pkg/importer/counting_writer.go
new file mode 100644
index 0000000000..d003a4b11b
--- /dev/null
+++ b/tvix/nar-bridge/pkg/importer/counting_writer.go
@@ -0,0 +1,21 @@
+package importer
+
+import (
+	"io"
+)
+
+// CountingWriter implements io.Writer.
+var _ io.Writer = &CountingWriter{}
+
+type CountingWriter struct {
+	bytesWritten uint64
+}
+
+func (cw *CountingWriter) Write(p []byte) (n int, err error) {
+	cw.bytesWritten += uint64(len(p))
+	return len(p), nil
+}
+
+func (cw *CountingWriter) BytesWritten() uint64 {
+	return cw.bytesWritten
+}
diff --git a/tvix/nar-bridge/pkg/importer/directory_upload.go b/tvix/nar-bridge/pkg/importer/directory_upload.go
new file mode 100644
index 0000000000..117f442fa5
--- /dev/null
+++ b/tvix/nar-bridge/pkg/importer/directory_upload.go
@@ -0,0 +1,88 @@
+package importer
+
+import (
+	"bytes"
+	"context"
+	"encoding/base64"
+	"fmt"
+
+	castorev1pb "code.tvl.fyi/tvix/castore-go"
+	log "github.com/sirupsen/logrus"
+)
+
+// DirectoriesUploader opens a Put stream when it receives the first Put() call,
+// and then uses the opened stream for subsequent Put() calls.
+// When the uploading is finished, a call to Done() will close the stream and
+// return the root digest returned from the directoryServiceClient.
+type DirectoriesUploader struct {
+	ctx                       context.Context
+	directoryServiceClient    castorev1pb.DirectoryServiceClient
+	directoryServicePutStream castorev1pb.DirectoryService_PutClient
+	lastDirectoryDigest       []byte
+}
+
+func NewDirectoriesUploader(ctx context.Context, directoryServiceClient castorev1pb.DirectoryServiceClient) *DirectoriesUploader {
+	return &DirectoriesUploader{
+		ctx:                       ctx,
+		directoryServiceClient:    directoryServiceClient,
+		directoryServicePutStream: nil,
+	}
+}
+
+func (du *DirectoriesUploader) Put(directory *castorev1pb.Directory) ([]byte, error) {
+	directoryDigest, err := directory.Digest()
+	if err != nil {
+		return nil, fmt.Errorf("failed calculating directory digest: %w", err)
+	}
+
+	// Send the directory to the directory service
+	// If the stream hasn't been initialized yet, do it first
+	if du.directoryServicePutStream == nil {
+		directoryServicePutStream, err := du.directoryServiceClient.Put(du.ctx)
+		if err != nil {
+			return nil, fmt.Errorf("unable to initialize directory service put stream: %v", err)
+		}
+		du.directoryServicePutStream = directoryServicePutStream
+	}
+
+	// send the directory out
+	err = du.directoryServicePutStream.Send(directory)
+	if err != nil {
+		return nil, fmt.Errorf("error sending directory: %w", err)
+	}
+	log.WithField("digest", base64.StdEncoding.EncodeToString(directoryDigest)).Debug("uploaded directory")
+
+	// update lastDirectoryDigest
+	du.lastDirectoryDigest = directoryDigest
+
+	return directoryDigest, nil
+}
+
+// Done closes the stream and returns the response.
+// It returns null if closed for a second time.
+func (du *DirectoriesUploader) Done() (*castorev1pb.PutDirectoryResponse, error) {
+	// only close once, and only if we opened.
+	if du.directoryServicePutStream == nil {
+		return nil, nil
+	}
+
+	putDirectoryResponse, err := du.directoryServicePutStream.CloseAndRecv()
+	if err != nil {
+		return nil, fmt.Errorf("unable to close directory service put stream: %v", err)
+	}
+
+	// ensure the response contains the same digest as the one we have in lastDirectoryDigest.
+	// Otherwise, the backend came up with another digest than we, in which we return an error.
+	if !bytes.Equal(du.lastDirectoryDigest, putDirectoryResponse.RootDigest) {
+		return nil, fmt.Errorf(
+			"backend calculated different root digest as we, expected %s, actual %s",
+			base64.StdEncoding.EncodeToString(du.lastDirectoryDigest),
+			base64.StdEncoding.EncodeToString(putDirectoryResponse.RootDigest),
+		)
+	}
+
+	// clear directoryServicePutStream.
+	du.directoryServicePutStream = nil
+
+	return putDirectoryResponse, nil
+}
diff --git a/tvix/nar-bridge/pkg/importer/gen_pathinfo.go b/tvix/nar-bridge/pkg/importer/gen_pathinfo.go
new file mode 100644
index 0000000000..bdc298a9a3
--- /dev/null
+++ b/tvix/nar-bridge/pkg/importer/gen_pathinfo.go
@@ -0,0 +1,62 @@
+package importer
+
+import (
+	castorev1pb "code.tvl.fyi/tvix/castore-go"
+	storev1pb "code.tvl.fyi/tvix/store-go"
+	"fmt"
+	"github.com/nix-community/go-nix/pkg/narinfo"
+	"github.com/nix-community/go-nix/pkg/storepath"
+)
+
+// GenPathInfo takes a rootNode and narInfo and assembles a PathInfo.
+// The rootNode is renamed to match the StorePath in the narInfo.
+func GenPathInfo(rootNode *castorev1pb.Node, narInfo *narinfo.NarInfo) (*storev1pb.PathInfo, error) {
+	// parse the storePath from the .narinfo
+	storePath, err := storepath.FromAbsolutePath(narInfo.StorePath)
+	if err != nil {
+		return nil, fmt.Errorf("unable to parse StorePath: %w", err)
+	}
+
+	// construct the references, by parsing ReferenceNames and extracting the digest
+	references := make([][]byte, len(narInfo.References))
+	for i, referenceStr := range narInfo.References {
+		// parse reference as store path
+		referenceStorePath, err := storepath.FromString(referenceStr)
+		if err != nil {
+			return nil, fmt.Errorf("unable to parse reference %s as storepath: %w", referenceStr, err)
+		}
+		references[i] = referenceStorePath.Digest
+	}
+
+	// construct the narInfo.Signatures[*] from pathInfo.Narinfo.Signatures[*]
+	narinfoSignatures := make([]*storev1pb.NARInfo_Signature, len(narInfo.Signatures))
+	for i, narinfoSig := range narInfo.Signatures {
+		narinfoSignatures[i] = &storev1pb.NARInfo_Signature{
+			Name: narinfoSig.Name,
+			Data: narinfoSig.Data,
+		}
+	}
+
+	// assemble the PathInfo.
+	pathInfo := &storev1pb.PathInfo{
+		// embed a new root node with the name set to the store path basename.
+		Node:       castorev1pb.RenamedNode(rootNode, storePath.String()),
+		References: references,
+		Narinfo: &storev1pb.NARInfo{
+			NarSize:        narInfo.NarSize,
+			NarSha256:      narInfo.FileHash.Digest(),
+			Signatures:     narinfoSignatures,
+			ReferenceNames: narInfo.References,
+		},
+	}
+
+	// run Validate on the PathInfo, more as an additional sanity check our code is sound,
+	// to make sure we populated everything properly, before returning it.
+	// Fail hard if we fail validation, this is a code error.
+	if _, err = pathInfo.Validate(); err != nil {
+		panic(fmt.Sprintf("PathInfo failed validation: %v", err))
+	}
+
+	return pathInfo, nil
+
+}
diff --git a/tvix/nar-bridge/pkg/importer/importer.go b/tvix/nar-bridge/pkg/importer/importer.go
new file mode 100644
index 0000000000..fce6c5f293
--- /dev/null
+++ b/tvix/nar-bridge/pkg/importer/importer.go
@@ -0,0 +1,303 @@
+package importer
+
+import (
+	"bytes"
+	"context"
+	"crypto/sha256"
+	"errors"
+	"fmt"
+	"io"
+	"path"
+	"strings"
+
+	castorev1pb "code.tvl.fyi/tvix/castore-go"
+	"github.com/nix-community/go-nix/pkg/nar"
+	"golang.org/x/sync/errgroup"
+	"lukechampine.com/blake3"
+)
+
+const (
+	// asyncUploadThreshold controls when a file is buffered into memory and uploaded
+	// asynchronously. Files must be smaller than the threshold to be uploaded asynchronously.
+	asyncUploadThreshold = 1024 * 1024 // 1 MiB
+	// maxAsyncUploadBufferBytes is the maximum number of async blob uploads allowed to be
+	// running concurrently at any given time for a simple import operation.
+	maxConcurrentAsyncUploads = 128
+)
+
+// An item on the directories stack
+type stackItem struct {
+	path      string
+	directory *castorev1pb.Directory
+}
+
+// Import reads a NAR from a reader, and returns a the root node,
+// NAR size and NAR sha256 digest.
+func Import(
+	// a context, to support cancellation
+	ctx context.Context,
+	// The reader the data is read from
+	r io.Reader,
+	// callback function called with each regular file content
+	blobCb func(fileReader io.Reader) ([]byte, error),
+	// callback function called with each finalized directory node
+	directoryCb func(directory *castorev1pb.Directory) ([]byte, error),
+) (*castorev1pb.Node, uint64, []byte, error) {
+	// We need to wrap the underlying reader a bit.
+	// - we want to keep track of the number of bytes read in total
+	// - we calculate the sha256 digest over all data read
+	// Express these two things in a MultiWriter, and give the NAR reader a
+	// TeeReader that writes to it.
+	narCountW := &CountingWriter{}
+	sha256W := sha256.New()
+	multiW := io.MultiWriter(narCountW, sha256W)
+	narReader, err := nar.NewReader(io.TeeReader(r, multiW))
+	if err != nil {
+		return nil, 0, nil, fmt.Errorf("failed to instantiate nar reader: %w", err)
+	}
+	defer narReader.Close()
+
+	// If we store a symlink or regular file at the root, these are not nil.
+	// If they are nil, we instead have a stackDirectory.
+	var rootSymlink *castorev1pb.SymlinkNode
+	var rootFile *castorev1pb.FileNode
+	var stackDirectory *castorev1pb.Directory
+
+	// Keep track of all asynch blob uploads so we can make sure they all succeed
+	// before returning.
+	var asyncBlobWg errgroup.Group
+	asyncBlobWg.SetLimit(maxConcurrentAsyncUploads)
+
+	var stack = []stackItem{}
+
+	// popFromStack is used when we transition to a different directory or
+	// drain the stack when we reach the end of the NAR.
+	// It adds the popped element to the element underneath if any,
+	// and passes it to the directoryCb callback.
+	// This function may only be called if the stack is not already empty.
+	popFromStack := func() error {
+		// Keep the top item, and "resize" the stack slice.
+		// This will only make the last element unaccessible, but chances are high
+		// we're re-using that space anyways.
+		toPop := stack[len(stack)-1]
+		stack = stack[:len(stack)-1]
+
+		// call the directoryCb
+		directoryDigest, err := directoryCb(toPop.directory)
+		if err != nil {
+			return fmt.Errorf("failed calling directoryCb: %w", err)
+		}
+
+		// if there's still a parent left on the stack, refer to it from there.
+		if len(stack) > 0 {
+			topOfStack := stack[len(stack)-1].directory
+			topOfStack.Directories = append(topOfStack.Directories, &castorev1pb.DirectoryNode{
+				Name:   []byte(path.Base(toPop.path)),
+				Digest: directoryDigest,
+				Size:   toPop.directory.Size(),
+			})
+		}
+		// Keep track that we have encounter at least one directory
+		stackDirectory = toPop.directory
+		return nil
+	}
+
+	getBasename := func(p string) string {
+		// extract the basename. In case of "/", replace with empty string.
+		basename := path.Base(p)
+		if basename == "/" {
+			basename = ""
+		}
+		return basename
+	}
+
+	for {
+		select {
+		case <-ctx.Done():
+			return nil, 0, nil, ctx.Err()
+		default:
+			// call narReader.Next() to get the next element
+			hdr, err := narReader.Next()
+
+			// If this returns an error, it's either EOF (when we're done reading from the NAR),
+			// or another error.
+			if err != nil {
+				// if this returns no EOF, bail out
+				if !errors.Is(err, io.EOF) {
+					return nil, 0, nil, fmt.Errorf("failed getting next nar element: %w", err)
+				}
+
+				// The NAR has been read all the way to the end…
+				// Make sure we close the nar reader, which might read some final trailers.
+				if err := narReader.Close(); err != nil {
+					return nil, 0, nil, fmt.Errorf("unable to close nar reader: %w", err)
+				}
+
+				// Check the stack. While it's not empty, we need to pop things off the stack.
+				for len(stack) > 0 {
+					err := popFromStack()
+					if err != nil {
+						return nil, 0, nil, fmt.Errorf("unable to pop from stack: %w", err)
+					}
+				}
+
+				// Wait for any pending blob uploads.
+				err := asyncBlobWg.Wait()
+				if err != nil {
+					return nil, 0, nil, fmt.Errorf("async blob upload: %w", err)
+				}
+
+				// Stack is empty.
+				// Now either root{File,Symlink,Directory} is not nil,
+				// and we can return the root node.
+				narSize := narCountW.BytesWritten()
+				narSha256 := sha256W.Sum(nil)
+
+				if rootFile != nil {
+					return &castorev1pb.Node{
+						Node: &castorev1pb.Node_File{
+							File: rootFile,
+						},
+					}, narSize, narSha256, nil
+				} else if rootSymlink != nil {
+					return &castorev1pb.Node{
+						Node: &castorev1pb.Node_Symlink{
+							Symlink: rootSymlink,
+						},
+					}, narSize, narSha256, nil
+				} else if stackDirectory != nil {
+					// calculate directory digest (i.e. after we received all its contents)
+					dgst, err := stackDirectory.Digest()
+					if err != nil {
+						return nil, 0, nil, fmt.Errorf("unable to calculate root directory digest: %w", err)
+					}
+
+					return &castorev1pb.Node{
+						Node: &castorev1pb.Node_Directory{
+							Directory: &castorev1pb.DirectoryNode{
+								Name:   []byte{},
+								Digest: dgst,
+								Size:   stackDirectory.Size(),
+							},
+						},
+					}, narSize, narSha256, nil
+				} else {
+					return nil, 0, nil, fmt.Errorf("no root set")
+				}
+			}
+
+			// Check for valid path transitions, pop from stack if needed
+			// The nar reader already gives us some guarantees about ordering and illegal transitions,
+			// So we really only need to check if the top-of-stack path is a prefix of the path,
+			// and if it's not, pop from the stack. We do this repeatedly until the top of the stack is
+			// the subdirectory the new entry is in, or we hit the root directory.
+
+			// We don't need to worry about the root node case, because we can only finish the root "/"
+			// If we're at the end of the NAR reader (covered by the EOF check)
+			for len(stack) > 1 && !strings.HasPrefix(hdr.Path, stack[len(stack)-1].path+"/") {
+				err := popFromStack()
+				if err != nil {
+					return nil, 0, nil, fmt.Errorf("unable to pop from stack: %w", err)
+				}
+			}
+
+			if hdr.Type == nar.TypeSymlink {
+				symlinkNode := &castorev1pb.SymlinkNode{
+					Name:   []byte(getBasename(hdr.Path)),
+					Target: []byte(hdr.LinkTarget),
+				}
+				if len(stack) > 0 {
+					topOfStack := stack[len(stack)-1].directory
+					topOfStack.Symlinks = append(topOfStack.Symlinks, symlinkNode)
+				} else {
+					rootSymlink = symlinkNode
+				}
+
+			}
+			if hdr.Type == nar.TypeRegular {
+				uploadBlob := func(r io.Reader) ([]byte, error) {
+					// wrap reader with a reader counting the number of bytes read
+					blobCountW := &CountingWriter{}
+					blobReader := io.TeeReader(r, blobCountW)
+
+					blobDigest, err := blobCb(blobReader)
+					if err != nil {
+						return nil, fmt.Errorf("failure from blobCb: %w", err)
+					}
+
+					// ensure blobCb did read all the way to the end.
+					// If it didn't, the blobCb function is wrong and we should bail out.
+					if blobCountW.BytesWritten() != uint64(hdr.Size) {
+						return nil, fmt.Errorf("blobCb did not read all: %d/%d bytes", blobCountW.BytesWritten(), hdr.Size)
+					}
+
+					return blobDigest, nil
+				}
+
+				h := blake3.New(32, nil)
+				blobReader := io.TeeReader(narReader, io.MultiWriter(h))
+				var blobDigest []byte
+
+				// If this file is small enough, read it off the wire immediately and
+				// upload to the blob service asynchronously. This helps reduce the
+				// RTT on blob uploads for NARs with many small files.
+				doAsync := hdr.Size < asyncUploadThreshold
+				if doAsync {
+					blobContents, err := io.ReadAll(blobReader)
+					if err != nil {
+						return nil, 0, nil, fmt.Errorf("read blob: %w", err)
+					}
+
+					blobDigest = h.Sum(nil)
+
+					asyncBlobWg.Go(func() error {
+						blobDigestFromCb, err := uploadBlob(bytes.NewReader(blobContents))
+						if err != nil {
+							return err
+						}
+
+						if !bytes.Equal(blobDigest, blobDigestFromCb) {
+							return fmt.Errorf("unexpected digest (got %x, expected %x)", blobDigestFromCb, blobDigest)
+						}
+
+						return nil
+					})
+				} else {
+					blobDigestFromCb, err := uploadBlob(blobReader)
+					if err != nil {
+						return nil, 0, nil, fmt.Errorf("upload blob: %w", err)
+					}
+
+					blobDigest = h.Sum(nil)
+					if !bytes.Equal(blobDigest, blobDigestFromCb) {
+						return nil, 0, nil, fmt.Errorf("unexpected digest (got %x, expected %x)", blobDigestFromCb, blobDigest)
+					}
+				}
+
+				fileNode := &castorev1pb.FileNode{
+					Name:       []byte(getBasename(hdr.Path)),
+					Digest:     blobDigest,
+					Size:       uint64(hdr.Size),
+					Executable: hdr.Executable,
+				}
+				if len(stack) > 0 {
+					topOfStack := stack[len(stack)-1].directory
+					topOfStack.Files = append(topOfStack.Files, fileNode)
+				} else {
+					rootFile = fileNode
+				}
+			}
+			if hdr.Type == nar.TypeDirectory {
+				directory := &castorev1pb.Directory{
+					Directories: []*castorev1pb.DirectoryNode{},
+					Files:       []*castorev1pb.FileNode{},
+					Symlinks:    []*castorev1pb.SymlinkNode{},
+				}
+				stack = append(stack, stackItem{
+					directory: directory,
+					path:      hdr.Path,
+				})
+			}
+		}
+	}
+}
diff --git a/tvix/nar-bridge/pkg/importer/importer_test.go b/tvix/nar-bridge/pkg/importer/importer_test.go
new file mode 100644
index 0000000000..8ff63b9257
--- /dev/null
+++ b/tvix/nar-bridge/pkg/importer/importer_test.go
@@ -0,0 +1,537 @@
+package importer_test
+
+import (
+	"bytes"
+	"context"
+	"errors"
+	"io"
+	"os"
+	"testing"
+
+	castorev1pb "code.tvl.fyi/tvix/castore-go"
+	"code.tvl.fyi/tvix/nar-bridge/pkg/importer"
+	"github.com/stretchr/testify/require"
+)
+
+func TestSymlink(t *testing.T) {
+	f, err := os.Open("../../testdata/symlink.nar")
+	require.NoError(t, err)
+
+	rootNode, narSize, narSha256, err := importer.Import(
+		context.Background(),
+		f,
+		func(blobReader io.Reader) ([]byte, error) {
+			panic("no file contents expected!")
+		}, func(directory *castorev1pb.Directory) ([]byte, error) {
+			panic("no directories expected!")
+		},
+	)
+	require.NoError(t, err)
+	require.Equal(t, &castorev1pb.Node{
+		Node: &castorev1pb.Node_Symlink{
+			Symlink: &castorev1pb.SymlinkNode{
+				Name:   []byte(""),
+				Target: []byte("/nix/store/somewhereelse"),
+			},
+		},
+	}, rootNode)
+	require.Equal(t, []byte{
+		0x09, 0x7d, 0x39, 0x7e, 0x9b, 0x58, 0x26, 0x38, 0x4e, 0xaa, 0x16, 0xc4, 0x57, 0x71, 0x5d, 0x1c, 0x1a, 0x51, 0x67, 0x03, 0x13, 0xea, 0xd0, 0xf5, 0x85, 0x66, 0xe0, 0xb2, 0x32, 0x53, 0x9c, 0xf1,
+	}, narSha256)
+	require.Equal(t, uint64(136), narSize)
+}
+
+func TestRegular(t *testing.T) {
+	f, err := os.Open("../../testdata/onebyteregular.nar")
+	require.NoError(t, err)
+
+	rootNode, narSize, narSha256, err := importer.Import(
+		context.Background(),
+		f,
+		func(blobReader io.Reader) ([]byte, error) {
+			contents, err := io.ReadAll(blobReader)
+			require.NoError(t, err, "reading blobReader should not error")
+			require.Equal(t, []byte{0x01}, contents, "contents read from blobReader should match expectations")
+			return mustBlobDigest(bytes.NewBuffer(contents)), nil
+		}, func(directory *castorev1pb.Directory) ([]byte, error) {
+			panic("no directories expected!")
+		},
+	)
+
+	// The blake3 digest of the 0x01 byte.
+	BLAKE3_DIGEST_0X01 := []byte{
+		0x48, 0xfc, 0x72, 0x1f, 0xbb, 0xc1, 0x72, 0xe0, 0x92, 0x5f, 0xa2, 0x7a, 0xf1, 0x67, 0x1d,
+		0xe2, 0x25, 0xba, 0x92, 0x71, 0x34, 0x80, 0x29, 0x98, 0xb1, 0x0a, 0x15, 0x68, 0xa1, 0x88,
+		0x65, 0x2b,
+	}
+
+	require.NoError(t, err)
+	require.Equal(t, &castorev1pb.Node{
+		Node: &castorev1pb.Node_File{
+			File: &castorev1pb.FileNode{
+				Name:       []byte(""),
+				Digest:     BLAKE3_DIGEST_0X01,
+				Size:       1,
+				Executable: false,
+			},
+		},
+	}, rootNode)
+	require.Equal(t, []byte{
+		0x73, 0x08, 0x50, 0xa8, 0x11, 0x25, 0x9d, 0xbf, 0x3a, 0x68, 0xdc, 0x2e, 0xe8, 0x7a, 0x79, 0xaa, 0x6c, 0xae, 0x9f, 0x71, 0x37, 0x5e, 0xdf, 0x39, 0x6f, 0x9d, 0x7a, 0x91, 0xfb, 0xe9, 0x13, 0x4d,
+	}, narSha256)
+	require.Equal(t, uint64(120), narSize)
+}
+
+func TestEmptyDirectory(t *testing.T) {
+	f, err := os.Open("../../testdata/emptydirectory.nar")
+	require.NoError(t, err)
+
+	expectedDirectory := &castorev1pb.Directory{
+		Directories: []*castorev1pb.DirectoryNode{},
+		Files:       []*castorev1pb.FileNode{},
+		Symlinks:    []*castorev1pb.SymlinkNode{},
+	}
+	rootNode, narSize, narSha256, err := importer.Import(
+		context.Background(),
+		f,
+		func(blobReader io.Reader) ([]byte, error) {
+			panic("no file contents expected!")
+		}, func(directory *castorev1pb.Directory) ([]byte, error) {
+			requireProtoEq(t, expectedDirectory, directory)
+			return mustDirectoryDigest(directory), nil
+		},
+	)
+	require.NoError(t, err)
+	require.Equal(t, &castorev1pb.Node{
+		Node: &castorev1pb.Node_Directory{
+			Directory: &castorev1pb.DirectoryNode{
+				Name:   []byte(""),
+				Digest: mustDirectoryDigest(expectedDirectory),
+				Size:   expectedDirectory.Size(),
+			},
+		},
+	}, rootNode)
+	require.Equal(t, []byte{
+		0xa5, 0x0a, 0x5a, 0xb6, 0xd9, 0x92, 0xf5, 0x59, 0x8e, 0xdd, 0x92, 0x10, 0x50, 0x59, 0xfa, 0xe9, 0xac, 0xfc, 0x19, 0x29, 0x81, 0xe0, 0x8b, 0xd8, 0x85, 0x34, 0xc2, 0x16, 0x7e, 0x92, 0x52, 0x6a,
+	}, narSha256)
+	require.Equal(t, uint64(96), narSize)
+}
+
+func TestFull(t *testing.T) {
+	f, err := os.Open("../../testdata/nar_1094wph9z4nwlgvsd53abfz8i117ykiv5dwnq9nnhz846s7xqd7d.nar")
+	require.NoError(t, err)
+
+	expectedDirectoryPaths := []string{
+		"/bin",
+		"/share/man/man1",
+		"/share/man/man5",
+		"/share/man/man8",
+		"/share/man",
+		"/share",
+		"/",
+	}
+	expectedDirectories := make(map[string]*castorev1pb.Directory, len(expectedDirectoryPaths))
+
+	// /bin is a leaf directory
+	expectedDirectories["/bin"] = &castorev1pb.Directory{
+		Directories: []*castorev1pb.DirectoryNode{},
+		Files: []*castorev1pb.FileNode{
+			{
+				Name: []byte("arp"),
+				Digest: []byte{
+					0xfb, 0xc4, 0x61, 0x4a, 0x29, 0x27, 0x11, 0xcb, 0xcc, 0xe4, 0x99, 0x81, 0x9c, 0xf0, 0xa9, 0x17, 0xf7, 0xd0, 0x91, 0xbe, 0xea, 0x08, 0xcb, 0x5b, 0xaa, 0x76, 0x76, 0xf5, 0xee, 0x4f, 0x82, 0xbb,
+				},
+				Size:       55288,
+				Executable: true,
+			},
+			{
+				Name: []byte("hostname"),
+				Digest: []byte{
+					0x9c, 0x6a, 0xe4, 0xb5, 0xe4, 0x6c, 0xb5, 0x67, 0x45, 0x0e, 0xaa, 0x2a, 0xd8, 0xdd, 0x9b, 0x38, 0xd7, 0xed, 0x01, 0x02, 0x84, 0xf7, 0x26, 0xe1, 0xc7, 0xf3, 0x1c, 0xeb, 0xaa, 0x8a, 0x01, 0x30,
+				},
+				Size:       17704,
+				Executable: true,
+			},
+			{
+				Name: []byte("ifconfig"),
+				Digest: []byte{
+					0x25, 0xbe, 0x3b, 0x1d, 0xf4, 0x1a, 0x45, 0x42, 0x79, 0x09, 0x2c, 0x2a, 0x83, 0xf0, 0x0b, 0xff, 0xe8, 0xc0, 0x9c, 0x26, 0x98, 0x70, 0x15, 0x4d, 0xa8, 0xca, 0x05, 0xfe, 0x92, 0x68, 0x35, 0x2e,
+				},
+				Size:       72576,
+				Executable: true,
+			},
+			{
+				Name: []byte("nameif"),
+				Digest: []byte{
+					0x8e, 0xaa, 0xc5, 0xdb, 0x71, 0x08, 0x8e, 0xe5, 0xe6, 0x30, 0x1f, 0x2c, 0x3a, 0xf2, 0x42, 0x39, 0x0c, 0x57, 0x15, 0xaf, 0x50, 0xaa, 0x1c, 0xdf, 0x84, 0x22, 0x08, 0x77, 0x03, 0x54, 0x62, 0xb1,
+				},
+				Size:       18776,
+				Executable: true,
+			},
+			{
+				Name: []byte("netstat"),
+				Digest: []byte{
+					0x13, 0x34, 0x7e, 0xdd, 0x2a, 0x9a, 0x17, 0x0b, 0x3f, 0xc7, 0x0a, 0xe4, 0x92, 0x89, 0x25, 0x9f, 0xaa, 0xb5, 0x05, 0x6b, 0x24, 0xa7, 0x91, 0xeb, 0xaf, 0xf9, 0xe9, 0x35, 0x56, 0xaa, 0x2f, 0xb2,
+				},
+				Size:       131784,
+				Executable: true,
+			},
+			{
+				Name: []byte("plipconfig"),
+				Digest: []byte{
+					0x19, 0x7c, 0x80, 0xdc, 0x81, 0xdc, 0xb4, 0xc0, 0x45, 0xe1, 0xf9, 0x76, 0x51, 0x4f, 0x50, 0xbf, 0xa4, 0x69, 0x51, 0x9a, 0xd4, 0xa9, 0xe7, 0xaa, 0xe7, 0x0d, 0x53, 0x32, 0xff, 0x28, 0x40, 0x60,
+				},
+				Size:       13160,
+				Executable: true,
+			},
+			{
+				Name: []byte("rarp"),
+				Digest: []byte{
+					0x08, 0x85, 0xb4, 0x85, 0x03, 0x2b, 0x3c, 0x7a, 0x3e, 0x24, 0x4c, 0xf8, 0xcc, 0x45, 0x01, 0x9e, 0x79, 0x43, 0x8c, 0x6f, 0x5e, 0x32, 0x46, 0x54, 0xb6, 0x68, 0x91, 0x8e, 0xa0, 0xcb, 0x6e, 0x0d,
+				},
+				Size:       30384,
+				Executable: true,
+			},
+			{
+				Name: []byte("route"),
+				Digest: []byte{
+					0x4d, 0x14, 0x20, 0x89, 0x9e, 0x76, 0xf4, 0xe2, 0x92, 0x53, 0xee, 0x9b, 0x78, 0x7d, 0x23, 0x80, 0x6c, 0xff, 0xe6, 0x33, 0xdc, 0x4a, 0x10, 0x29, 0x39, 0x02, 0xa0, 0x60, 0xff, 0xe2, 0xbb, 0xd7,
+				},
+				Size:       61928,
+				Executable: true,
+			},
+			{
+				Name: []byte("slattach"),
+				Digest: []byte{
+					0xfb, 0x25, 0xc3, 0x73, 0xb7, 0xb1, 0x0b, 0x25, 0xcd, 0x7b, 0x62, 0xf6, 0x71, 0x83, 0xfe, 0x36, 0x80, 0xf6, 0x48, 0xc3, 0xdb, 0xd8, 0x0c, 0xfe, 0xb8, 0xd3, 0xda, 0x32, 0x9b, 0x47, 0x4b, 0x05,
+				},
+				Size:       35672,
+				Executable: true,
+			},
+		},
+		Symlinks: []*castorev1pb.SymlinkNode{
+			{
+				Name:   []byte("dnsdomainname"),
+				Target: []byte("hostname"),
+			},
+			{
+				Name:   []byte("domainname"),
+				Target: []byte("hostname"),
+			},
+			{
+				Name:   []byte("nisdomainname"),
+				Target: []byte("hostname"),
+			},
+			{
+				Name:   []byte("ypdomainname"),
+				Target: []byte("hostname"),
+			},
+		},
+	}
+
+	// /share/man/man1 is a leaf directory.
+	// The parser traversed over /sbin, but only added it to / which is still on the stack.
+	expectedDirectories["/share/man/man1"] = &castorev1pb.Directory{
+		Directories: []*castorev1pb.DirectoryNode{},
+		Files: []*castorev1pb.FileNode{
+			{
+				Name: []byte("dnsdomainname.1.gz"),
+				Digest: []byte{
+					0x98, 0x8a, 0xbd, 0xfa, 0x64, 0xd5, 0xb9, 0x27, 0xfe, 0x37, 0x43, 0x56, 0xb3, 0x18, 0xc7, 0x2b, 0xcb, 0xe3, 0x17, 0x1c, 0x17, 0xf4, 0x17, 0xeb, 0x4a, 0xa4, 0x99, 0x64, 0x39, 0xca, 0x2d, 0xee,
+				},
+				Size:       40,
+				Executable: false,
+			},
+			{
+				Name: []byte("domainname.1.gz"),
+				Digest: []byte{
+					0x98, 0x8a, 0xbd, 0xfa, 0x64, 0xd5, 0xb9, 0x27, 0xfe, 0x37, 0x43, 0x56, 0xb3, 0x18, 0xc7, 0x2b, 0xcb, 0xe3, 0x17, 0x1c, 0x17, 0xf4, 0x17, 0xeb, 0x4a, 0xa4, 0x99, 0x64, 0x39, 0xca, 0x2d, 0xee,
+				},
+				Size:       40,
+				Executable: false,
+			},
+			{
+				Name: []byte("hostname.1.gz"),
+				Digest: []byte{
+					0xbf, 0x89, 0xe6, 0x28, 0x00, 0x24, 0x66, 0x79, 0x70, 0x04, 0x38, 0xd6, 0xdd, 0x9d, 0xf6, 0x0e, 0x0d, 0xee, 0x00, 0xf7, 0x64, 0x4f, 0x05, 0x08, 0x9d, 0xf0, 0x36, 0xde, 0x85, 0xf4, 0x75, 0xdb,
+				},
+				Size:       1660,
+				Executable: false,
+			},
+			{
+				Name: []byte("nisdomainname.1.gz"),
+				Digest: []byte{
+					0x98, 0x8a, 0xbd, 0xfa, 0x64, 0xd5, 0xb9, 0x27, 0xfe, 0x37, 0x43, 0x56, 0xb3, 0x18, 0xc7, 0x2b, 0xcb, 0xe3, 0x17, 0x1c, 0x17, 0xf4, 0x17, 0xeb, 0x4a, 0xa4, 0x99, 0x64, 0x39, 0xca, 0x2d, 0xee,
+				},
+				Size:       40,
+				Executable: false,
+			},
+			{
+				Name: []byte("ypdomainname.1.gz"),
+				Digest: []byte{
+					0x98, 0x8a, 0xbd, 0xfa, 0x64, 0xd5, 0xb9, 0x27, 0xfe, 0x37, 0x43, 0x56, 0xb3, 0x18, 0xc7, 0x2b, 0xcb, 0xe3, 0x17, 0x1c, 0x17, 0xf4, 0x17, 0xeb, 0x4a, 0xa4, 0x99, 0x64, 0x39, 0xca, 0x2d, 0xee,
+				},
+				Size:       40,
+				Executable: false,
+			},
+		},
+		Symlinks: []*castorev1pb.SymlinkNode{},
+	}
+
+	// /share/man/man5 is a leaf directory
+	expectedDirectories["/share/man/man5"] = &castorev1pb.Directory{
+		Directories: []*castorev1pb.DirectoryNode{},
+		Files: []*castorev1pb.FileNode{
+			{
+				Name: []byte("ethers.5.gz"),
+				Digest: []byte{
+					0x42, 0x63, 0x8c, 0xc4, 0x18, 0x93, 0xcf, 0x60, 0xd6, 0xff, 0x43, 0xbc, 0x16, 0xb4, 0xfd, 0x22, 0xd2, 0xf2, 0x05, 0x0b, 0x52, 0xdc, 0x6a, 0x6b, 0xff, 0x34, 0xe2, 0x6a, 0x38, 0x3a, 0x07, 0xe3,
+				},
+				Size:       563,
+				Executable: false,
+			},
+		},
+		Symlinks: []*castorev1pb.SymlinkNode{},
+	}
+
+	// /share/man/man8 is a leaf directory
+	expectedDirectories["/share/man/man8"] = &castorev1pb.Directory{
+		Directories: []*castorev1pb.DirectoryNode{},
+		Files: []*castorev1pb.FileNode{
+			{
+				Name: []byte("arp.8.gz"),
+				Digest: []byte{
+					0xf5, 0x35, 0x4e, 0xf5, 0xf6, 0x44, 0xf7, 0x52, 0x0f, 0x42, 0xa0, 0x26, 0x51, 0xd9, 0x89, 0xf9, 0x68, 0xf2, 0xef, 0xeb, 0xba, 0xe1, 0xf4, 0x55, 0x01, 0x57, 0x77, 0xb7, 0x68, 0x55, 0x92, 0xef,
+				},
+				Size:       2464,
+				Executable: false,
+			},
+			{
+				Name: []byte("ifconfig.8.gz"),
+				Digest: []byte{
+					0x18, 0x65, 0x25, 0x11, 0x32, 0xee, 0x77, 0x91, 0x35, 0x4c, 0x3c, 0x24, 0xdb, 0xaf, 0x66, 0xdb, 0xfc, 0x17, 0x7b, 0xba, 0xe1, 0x3d, 0x05, 0xd2, 0xca, 0x6e, 0x2c, 0xe4, 0xef, 0xb8, 0xa8, 0xbe,
+				},
+				Size:       3382,
+				Executable: false,
+			},
+			{
+				Name: []byte("nameif.8.gz"),
+				Digest: []byte{
+					0x73, 0xc1, 0x27, 0xe8, 0x3b, 0xa8, 0x49, 0xdc, 0x0e, 0xdf, 0x70, 0x5f, 0xaf, 0x06, 0x01, 0x2c, 0x62, 0xe9, 0x18, 0x67, 0x01, 0x94, 0x64, 0x26, 0xca, 0x95, 0x22, 0xc0, 0xdc, 0xe4, 0x42, 0xb6,
+				},
+				Size:       523,
+				Executable: false,
+			},
+			{
+				Name: []byte("netstat.8.gz"),
+				Digest: []byte{
+					0xc0, 0x86, 0x43, 0x4a, 0x43, 0x57, 0xaa, 0x84, 0xa7, 0x24, 0xa0, 0x7c, 0x65, 0x38, 0x46, 0x1c, 0xf2, 0x45, 0xa2, 0xef, 0x12, 0x44, 0x18, 0xba, 0x52, 0x56, 0xe9, 0x8e, 0x6a, 0x0f, 0x70, 0x63,
+				},
+				Size:       4284,
+				Executable: false,
+			},
+			{
+				Name: []byte("plipconfig.8.gz"),
+				Digest: []byte{
+					0x2a, 0xd9, 0x1d, 0xa8, 0x9e, 0x0d, 0x05, 0xd0, 0xb0, 0x49, 0xaa, 0x64, 0xba, 0x29, 0x28, 0xc6, 0x45, 0xe1, 0xbb, 0x5e, 0x72, 0x8d, 0x48, 0x7b, 0x09, 0x4f, 0x0a, 0x82, 0x1e, 0x26, 0x83, 0xab,
+				},
+				Size:       889,
+				Executable: false,
+			},
+			{
+				Name: []byte("rarp.8.gz"),
+				Digest: []byte{
+					0x3d, 0x51, 0xc1, 0xd0, 0x6a, 0x59, 0x1e, 0x6d, 0x9a, 0xf5, 0x06, 0xd2, 0xe7, 0x7d, 0x7d, 0xd0, 0x70, 0x3d, 0x84, 0x64, 0xc3, 0x7d, 0xfb, 0x10, 0x84, 0x3b, 0xe1, 0xa9, 0xdf, 0x46, 0xee, 0x9f,
+				},
+				Size:       1198,
+				Executable: false,
+			},
+			{
+				Name: []byte("route.8.gz"),
+				Digest: []byte{
+					0x2a, 0x5a, 0x4b, 0x4f, 0x91, 0xf2, 0x78, 0xe4, 0xa9, 0x25, 0xb2, 0x7f, 0xa7, 0x2a, 0xc0, 0x8a, 0x4a, 0x65, 0xc9, 0x5f, 0x07, 0xa0, 0x48, 0x44, 0xeb, 0x46, 0xf9, 0xc9, 0xe1, 0x17, 0x96, 0x21,
+				},
+				Size:       3525,
+				Executable: false,
+			},
+			{
+				Name: []byte("slattach.8.gz"),
+				Digest: []byte{
+					0x3f, 0x05, 0x6b, 0x20, 0xe1, 0xe4, 0xf0, 0xba, 0x16, 0x15, 0x66, 0x6b, 0x57, 0x96, 0xe9, 0x9d, 0x83, 0xa8, 0x20, 0xaf, 0x8a, 0xca, 0x16, 0x4d, 0xa2, 0x6d, 0x94, 0x8e, 0xca, 0x91, 0x8f, 0xd4,
+				},
+				Size:       1441,
+				Executable: false,
+			},
+		},
+		Symlinks: []*castorev1pb.SymlinkNode{},
+	}
+
+	// /share/man holds /share/man/man{1,5,8}.
+	expectedDirectories["/share/man"] = &castorev1pb.Directory{
+		Directories: []*castorev1pb.DirectoryNode{
+			{
+				Name:   []byte("man1"),
+				Digest: mustDirectoryDigest(expectedDirectories["/share/man/man1"]),
+				Size:   expectedDirectories["/share/man/man1"].Size(),
+			},
+			{
+				Name:   []byte("man5"),
+				Digest: mustDirectoryDigest(expectedDirectories["/share/man/man5"]),
+				Size:   expectedDirectories["/share/man/man5"].Size(),
+			},
+			{
+				Name:   []byte("man8"),
+				Digest: mustDirectoryDigest(expectedDirectories["/share/man/man8"]),
+				Size:   expectedDirectories["/share/man/man8"].Size(),
+			},
+		},
+		Files:    []*castorev1pb.FileNode{},
+		Symlinks: []*castorev1pb.SymlinkNode{},
+	}
+
+	// /share holds /share/man.
+	expectedDirectories["/share"] = &castorev1pb.Directory{
+		Directories: []*castorev1pb.DirectoryNode{
+			{
+				Name:   []byte("man"),
+				Digest: mustDirectoryDigest(expectedDirectories["/share/man"]),
+				Size:   expectedDirectories["/share/man"].Size(),
+			},
+		},
+		Files:    []*castorev1pb.FileNode{},
+		Symlinks: []*castorev1pb.SymlinkNode{},
+	}
+
+	// / holds /bin, /share, and a /sbin symlink.
+	expectedDirectories["/"] = &castorev1pb.Directory{
+		Directories: []*castorev1pb.DirectoryNode{
+			{
+				Name:   []byte("bin"),
+				Digest: mustDirectoryDigest(expectedDirectories["/bin"]),
+				Size:   expectedDirectories["/bin"].Size(),
+			},
+			{
+				Name:   []byte("share"),
+				Digest: mustDirectoryDigest(expectedDirectories["/share"]),
+				Size:   expectedDirectories["/share"].Size(),
+			},
+		},
+		Files: []*castorev1pb.FileNode{},
+		Symlinks: []*castorev1pb.SymlinkNode{
+			{
+				Name:   []byte("sbin"),
+				Target: []byte("bin"),
+			},
+		},
+	}
+	// assert we populated the two fixtures properly
+	require.Equal(t, len(expectedDirectoryPaths), len(expectedDirectories))
+
+	numDirectoriesReceived := 0
+
+	rootNode, narSize, narSha256, err := importer.Import(
+		context.Background(),
+		f,
+		func(blobReader io.Reader) ([]byte, error) {
+			// Don't really bother reading and comparing the contents here,
+			// We already verify the right digests are produced by comparing the
+			// directoryCb calls, and TestRegular ensures the reader works.
+			return mustBlobDigest(blobReader), nil
+		}, func(directory *castorev1pb.Directory) ([]byte, error) {
+			// use actualDirectoryOrder to look up the Directory object we expect at this specific invocation.
+			currentDirectoryPath := expectedDirectoryPaths[numDirectoriesReceived]
+
+			expectedDirectory, found := expectedDirectories[currentDirectoryPath]
+			require.True(t, found, "must find the current directory")
+
+			requireProtoEq(t, expectedDirectory, directory)
+
+			numDirectoriesReceived += 1
+			return mustDirectoryDigest(directory), nil
+		},
+	)
+	require.NoError(t, err)
+	require.Equal(t, &castorev1pb.Node{
+		Node: &castorev1pb.Node_Directory{
+			Directory: &castorev1pb.DirectoryNode{
+				Name:   []byte(""),
+				Digest: mustDirectoryDigest(expectedDirectories["/"]),
+				Size:   expectedDirectories["/"].Size(),
+			},
+		},
+	}, rootNode)
+	require.Equal(t, []byte{
+		0xc6, 0xe1, 0x55, 0xb3, 0x45, 0x6e, 0x30, 0xb7, 0x61, 0x22, 0x63, 0xec, 0x09, 0x50, 0x70, 0x81, 0x1c, 0xaf, 0x8a, 0xbf, 0xd5, 0x9f, 0xaa, 0x72, 0xab, 0x82, 0xa5, 0x92, 0xef, 0xde, 0xb2, 0x53,
+	}, narSha256)
+	require.Equal(t, uint64(464152), narSize)
+}
+
+// TestCallbackErrors ensures that errors returned from the callback function
+// bubble up to the importer process, and are not ignored.
+func TestCallbackErrors(t *testing.T) {
+	t.Run("callback blob", func(t *testing.T) {
+		// Pick an example NAR with a regular file.
+		f, err := os.Open("../../testdata/onebyteregular.nar")
+		require.NoError(t, err)
+
+		targetErr := errors.New("expected error")
+
+		_, _, _, err = importer.Import(
+			context.Background(),
+			f,
+			func(blobReader io.Reader) ([]byte, error) {
+				return nil, targetErr
+			}, func(directory *castorev1pb.Directory) ([]byte, error) {
+				panic("no directories expected!")
+			},
+		)
+		require.ErrorIs(t, err, targetErr)
+	})
+	t.Run("callback directory", func(t *testing.T) {
+		// Pick an example NAR with a directory node
+		f, err := os.Open("../../testdata/emptydirectory.nar")
+		require.NoError(t, err)
+
+		targetErr := errors.New("expected error")
+
+		_, _, _, err = importer.Import(
+			context.Background(),
+			f,
+			func(blobReader io.Reader) ([]byte, error) {
+				panic("no file contents expected!")
+			}, func(directory *castorev1pb.Directory) ([]byte, error) {
+				return nil, targetErr
+			},
+		)
+		require.ErrorIs(t, err, targetErr)
+	})
+}
+
+// TestPopDirectories is a regression test that ensures we handle the directory
+// stack properly.
+//
+// This test case looks like:
+//
+// / (dir)
+// /test (dir)
+// /test/tested (file)
+// /tested (file)
+//
+// We used to have a bug where the second `tested` file would appear as if
+// it was in the `/test` dir because it has that dir as a string prefix.
+func TestPopDirectories(t *testing.T) {
+	f, err := os.Open("../../testdata/popdirectories.nar")
+	require.NoError(t, err)
+	defer f.Close()
+
+	_, _, _, err = importer.Import(
+		context.Background(),
+		f,
+		func(blobReader io.Reader) ([]byte, error) { return mustBlobDigest(blobReader), nil },
+		func(directory *castorev1pb.Directory) ([]byte, error) {
+			require.NoError(t, directory.Validate(), "directory validation shouldn't error")
+			return mustDirectoryDigest(directory), nil
+		},
+	)
+	require.NoError(t, err)
+}
diff --git a/tvix/nar-bridge/pkg/importer/roundtrip_test.go b/tvix/nar-bridge/pkg/importer/roundtrip_test.go
new file mode 100644
index 0000000000..6d6fcb9ee2
--- /dev/null
+++ b/tvix/nar-bridge/pkg/importer/roundtrip_test.go
@@ -0,0 +1,85 @@
+package importer_test
+
+import (
+	"bytes"
+	"context"
+	"encoding/base64"
+	"fmt"
+	"io"
+	"os"
+	"sync"
+	"testing"
+
+	castorev1pb "code.tvl.fyi/tvix/castore-go"
+	"code.tvl.fyi/tvix/nar-bridge/pkg/importer"
+	storev1pb "code.tvl.fyi/tvix/store-go"
+	"github.com/stretchr/testify/require"
+)
+
+func TestRoundtrip(t *testing.T) {
+	// We pipe nar_1094wph9z4nwlgvsd53abfz8i117ykiv5dwnq9nnhz846s7xqd7d.nar to
+	// storev1pb.Export, and store all the file contents and directory objects
+	// received in two hashmaps.
+	// We then feed it to the writer, and test we come up with the same NAR file.
+
+	f, err := os.Open("../../testdata/nar_1094wph9z4nwlgvsd53abfz8i117ykiv5dwnq9nnhz846s7xqd7d.nar")
+	require.NoError(t, err)
+
+	narContents, err := io.ReadAll(f)
+	require.NoError(t, err)
+
+	var mu sync.Mutex
+	blobsMap := make(map[string][]byte, 0)
+	directoriesMap := make(map[string]*castorev1pb.Directory)
+
+	rootNode, _, _, err := importer.Import(
+		context.Background(),
+		bytes.NewBuffer(narContents),
+		func(blobReader io.Reader) ([]byte, error) {
+			// read in contents, we need to put it into filesMap later.
+			contents, err := io.ReadAll(blobReader)
+			require.NoError(t, err)
+
+			dgst := mustBlobDigest(bytes.NewReader(contents))
+
+			// put it in filesMap
+			mu.Lock()
+			blobsMap[base64.StdEncoding.EncodeToString(dgst)] = contents
+			mu.Unlock()
+
+			return dgst, nil
+		},
+		func(directory *castorev1pb.Directory) ([]byte, error) {
+			dgst := mustDirectoryDigest(directory)
+
+			directoriesMap[base64.StdEncoding.EncodeToString(dgst)] = directory
+			return dgst, nil
+		},
+	)
+
+	require.NoError(t, err)
+
+	// done populating everything, now actually test the export :-)
+	var narBuf bytes.Buffer
+	err = storev1pb.Export(
+		&narBuf,
+		rootNode,
+		func(directoryDgst []byte) (*castorev1pb.Directory, error) {
+			d, found := directoriesMap[base64.StdEncoding.EncodeToString(directoryDgst)]
+			if !found {
+				panic(fmt.Sprintf("directory %v not found", base64.StdEncoding.EncodeToString(directoryDgst)))
+			}
+			return d, nil
+		},
+		func(blobDgst []byte) (io.ReadCloser, error) {
+			blobContents, found := blobsMap[base64.StdEncoding.EncodeToString(blobDgst)]
+			if !found {
+				panic(fmt.Sprintf("blob      %v not found", base64.StdEncoding.EncodeToString(blobDgst)))
+			}
+			return io.NopCloser(bytes.NewReader(blobContents)), nil
+		},
+	)
+
+	require.NoError(t, err, "exporter shouldn't fail")
+	require.Equal(t, narContents, narBuf.Bytes())
+}
diff --git a/tvix/nar-bridge/pkg/importer/util_test.go b/tvix/nar-bridge/pkg/importer/util_test.go
new file mode 100644
index 0000000000..06353cf582
--- /dev/null
+++ b/tvix/nar-bridge/pkg/importer/util_test.go
@@ -0,0 +1,34 @@
+package importer_test
+
+import (
+	"io"
+	"testing"
+
+	castorev1pb "code.tvl.fyi/tvix/castore-go"
+	"github.com/google/go-cmp/cmp"
+	"google.golang.org/protobuf/testing/protocmp"
+	"lukechampine.com/blake3"
+)
+
+func requireProtoEq(t *testing.T, expected interface{}, actual interface{}) {
+	if diff := cmp.Diff(expected, actual, protocmp.Transform()); diff != "" {
+		t.Errorf("unexpected difference:\n%v", diff)
+	}
+}
+
+func mustDirectoryDigest(d *castorev1pb.Directory) []byte {
+	dgst, err := d.Digest()
+	if err != nil {
+		panic(err)
+	}
+	return dgst
+}
+
+func mustBlobDigest(r io.Reader) []byte {
+	hasher := blake3.New(32, nil)
+	_, err := io.Copy(hasher, r)
+	if err != nil {
+		panic(err)
+	}
+	return hasher.Sum([]byte{})
+}
diff --git a/tvix/nar-bridge/testdata/emptydirectory.nar b/tvix/nar-bridge/testdata/emptydirectory.nar
new file mode 100644
index 0000000000..baba558622
--- /dev/null
+++ b/tvix/nar-bridge/testdata/emptydirectory.nar
Binary files differdiff --git a/tvix/nar-bridge/testdata/nar_1094wph9z4nwlgvsd53abfz8i117ykiv5dwnq9nnhz846s7xqd7d.nar b/tvix/nar-bridge/testdata/nar_1094wph9z4nwlgvsd53abfz8i117ykiv5dwnq9nnhz846s7xqd7d.nar
new file mode 100644
index 0000000000..6cb0b16e5d
--- /dev/null
+++ b/tvix/nar-bridge/testdata/nar_1094wph9z4nwlgvsd53abfz8i117ykiv5dwnq9nnhz846s7xqd7d.nar
Binary files differdiff --git a/tvix/nar-bridge/testdata/onebyteexecutable.nar b/tvix/nar-bridge/testdata/onebyteexecutable.nar
new file mode 100644
index 0000000000..6868219666
--- /dev/null
+++ b/tvix/nar-bridge/testdata/onebyteexecutable.nar
Binary files differdiff --git a/tvix/nar-bridge/testdata/onebyteregular.nar b/tvix/nar-bridge/testdata/onebyteregular.nar
new file mode 100644
index 0000000000..b8c94932bf
--- /dev/null
+++ b/tvix/nar-bridge/testdata/onebyteregular.nar
Binary files differdiff --git a/tvix/nar-bridge/testdata/popdirectories.nar b/tvix/nar-bridge/testdata/popdirectories.nar
new file mode 100644
index 0000000000..74313aca52
--- /dev/null
+++ b/tvix/nar-bridge/testdata/popdirectories.nar
Binary files differdiff --git a/tvix/nar-bridge/testdata/symlink.nar b/tvix/nar-bridge/testdata/symlink.nar
new file mode 100644
index 0000000000..7990e4ad5b
--- /dev/null
+++ b/tvix/nar-bridge/testdata/symlink.nar
Binary files differdiff --git a/tvix/nix-compat/Cargo.toml b/tvix/nix-compat/Cargo.toml
new file mode 100644
index 0000000000..876ac3ecad
--- /dev/null
+++ b/tvix/nix-compat/Cargo.toml
@@ -0,0 +1,56 @@
+[package]
+name = "nix-compat"
+version = "0.1.0"
+edition = "2021"
+
+[features]
+# async NAR writer
+async = ["tokio"]
+# code emitting low-level packets used in the daemon protocol.
+wire = ["tokio", "pin-project-lite"]
+
+# Enable all features by default.
+default = ["async", "wire"]
+
+[dependencies]
+bitflags = "2.4.1"
+bstr = { version = "1.6.0", features = ["alloc", "unicode", "serde"] }
+data-encoding = "2.3.3"
+ed25519 = "2.2.3"
+ed25519-dalek = "2.1.0"
+enum-primitive-derive = "0.3.0"
+glob = "0.3.0"
+nom = "7.1.3"
+num-traits = "0.2.18"
+serde = { version = "1.0", features = ["derive"] }
+serde_json = "1.0"
+sha2 = "0.10.6"
+thiserror = "1.0.38"
+
+[dependencies.tokio]
+optional = true
+version = "1.32.0"
+features = ["io-util", "macros"]
+
+[dependencies.pin-project-lite]
+optional = true
+version = "0.2.13"
+
+[dev-dependencies]
+criterion = { version = "0.5", features = ["html_reports"] }
+futures = { version = "0.3.30", default-features = false, features = ["executor"] }
+hex-literal = "0.4.1"
+lazy_static = "1.4.0"
+pretty_assertions = "1.4.0"
+rstest = "0.19.0"
+serde_json = "1.0"
+tokio-test = "0.4.3"
+zstd = "^0.13.0"
+
+[[bench]]
+name = "derivation_parse_aterm"
+harness = false
+
+[[bench]]
+name = "narinfo_parse"
+harness = false
diff --git a/tvix/nix-compat/benches/derivation_parse_aterm.rs b/tvix/nix-compat/benches/derivation_parse_aterm.rs
new file mode 100644
index 0000000000..4ace7d4480
--- /dev/null
+++ b/tvix/nix-compat/benches/derivation_parse_aterm.rs
@@ -0,0 +1,31 @@
+use std::path::Path;
+
+use criterion::{black_box, criterion_group, criterion_main, Criterion};
+use nix_compat::derivation::Derivation;
+
+const RESOURCES_PATHS: &str = "src/derivation/tests/derivation_tests/ok";
+
+fn bench_aterm_parser(c: &mut Criterion) {
+    for drv in [
+        "0hm2f1psjpcwg8fijsmr4wwxrx59s092-bar.drv",
+        "292w8yzv5nn7nhdpxcs8b7vby2p27s09-nested-json.drv",
+        "4wvvbi4jwn0prsdxb7vs673qa5h9gr7x-foo.drv",
+        "52a9id8hx688hvlnz4d1n25ml1jdykz0-unicode.drv",
+        "9lj1lkjm2ag622mh4h9rpy6j607an8g2-structured-attrs.drv",
+        "ch49594n9avinrf8ip0aslidkc4lxkqv-foo.drv",
+        "h32dahq0bx5rp1krcdx3a53asj21jvhk-has-multi-out.drv",
+        "m1vfixn8iprlf0v9abmlrz7mjw1xj8kp-cp1252.drv",
+        "ss2p4wmxijn652haqyd7dckxwl4c7hxx-bar.drv",
+        "x6p0hg79i3wg0kkv7699935f7rrj9jf3-latin1.drv",
+    ] {
+        let drv_path = Path::new(RESOURCES_PATHS).join(drv);
+        let drv_bytes = &std::fs::read(drv_path).unwrap();
+
+        c.bench_function(drv, |b| {
+            b.iter(|| Derivation::from_aterm_bytes(black_box(drv_bytes)))
+        });
+    }
+}
+
+criterion_group!(benches, bench_aterm_parser);
+criterion_main!(benches);
diff --git a/tvix/nix-compat/benches/narinfo_parse.rs b/tvix/nix-compat/benches/narinfo_parse.rs
new file mode 100644
index 0000000000..7ffd24d12b
--- /dev/null
+++ b/tvix/nix-compat/benches/narinfo_parse.rs
@@ -0,0 +1,69 @@
+use criterion::{black_box, criterion_group, criterion_main, Criterion, Throughput};
+use lazy_static::lazy_static;
+use nix_compat::narinfo::NarInfo;
+use std::{io, str};
+
+const SAMPLE: &str = r#"StorePath: /nix/store/1pajsq519irjy86vli20bgq1wr1q3pny-banking-0.3.0
+URL: nar/0rdn027rxqbl42bv9jxhsipgq2hwqdapvwmdzligmzdmz2p9vybs.nar.xz
+Compression: xz
+FileHash: sha256:0rdn027rxqbl42bv9jxhsipgq2hwqdapvwmdzligmzdmz2p9vybs
+FileSize: 92828
+NarHash: sha256:0cfnydzp132y69bh20dj76yfd6hc3qdyblbwr9hwn59vfmnb09m0
+NarSize: 173352
+References: 03d4ncyfh76mgs6sfayl8l6zzdhm219w-python3.9-mt-940-4.23.0 0rhbw783qcjxv3cqln1760i1lmz2yb67-gsettings-desktop-schemas-41.0 1dm9ndgg56ylawpcbdzkhl03fg6777rr-python3.9-six-1.16.0 1pajsq519irjy86vli20bgq1wr1q3pny-banking-0.3.0 2ccy5zc89zpc2aznqxgvzp4wm1bwj05n-bzip2-1.0.6.0.2-bin 32gy3pqk4n725lscdm622yzsg9np3xvs-python3.9-cryptography-36.0.0-dev 35chvqbr7vp9icdki0132fc6np09vrx5-python3.9-bleach-4.1.0 53abh5cz9zi4yh75lfzg99xqy0fdgj4i-python3.9-xmlschema-1.9.2 5p96sifyavb407mnharhyzlw6pn6km1b-glib-2.70.2-bin 6hil8z0zkqcgvaw1qwjyqa8qyaa1lm3k-python3.9-pycairo-1.20.1 803ffb21rv4af521pplb72zjm1ygm9kk-python3.9-pyparsing-2.4.7 al95l8psvmq5di3vdwa75n8w2m0sj2sy-gdk-pixbuf-2.42.6 b09371lq1jjrv43h8jpp82v23igndsn2-python3.9-fints-3.0.1 b53hk557pdk5mq4lv1zrh71a54qazbsm-python3.9-certifi-2021.10.08 bl0cwvwgch92cfsnli4dsah2gxgdickp-gtk+3-3.24.30 cfkq9wi7ypqk26c75dzic5v3nxlzyi58-python3.9-cryptography-36.0.0 cyhg57whqvrx7xf7fvn70dr5836y7zak-python3.9-sepaxml-2.4.1 d810g729g1c4lvp3nv1n3ah6cvpwg7by-cairo-1.16.0-dev dn4fwp0yx6nsa85cr20cwvdmg64xwmcy-python3-3.9.9 dzsj2n0nmq8nv6w0hvy5vb61kim3rzmd-pango-1.50.0 fs6rcnhbjvpxsyw5qiq0q7jx378fjrq7-python3.9-webencodings-0.5.1 g08sxarx191yh2dh0yk2j8icja54aksf-harfbuzz-3.1.2 glanz2lv7m6ak8pql0jcpr3izyp5cxm5-python3.9-pycparser-2.21 gpzx6h0dp5yhcvkfj68zs444ghll7dzm-python3.9-html5lib-1.1 gxyhqkpahahn4h8wbanzfhr1zkxbysid-expat-2.4.2-dev gy3pnc7bpff1h4ylhrivs4cjlvmxl0dk-python3.9-packaging-20.9 hhpqldw0552mf4mjdm2q7zqwy9hpfchd-libpng-apng-1.6.37-dev ig2bdwmplvs6dyg07fdyh006ha768jh1-python3.9-cffi-1.15.0 ij5rm5y6lmqzrwqd1zxckhbii3dg2nq5-glib-2.70.2-dev j5raylzz6fsafbgayyfaydadjl0x22s0-freetype-2.11.1-dev j6w2fbsl49jska4scyr860gz4df9biha-gobject-introspection-1.70.0 jfc99f1hrca6ih6h0n4ax431hjlx96j0-python3.9-brotli-1.0.9 kbazcxnki2qz514rl1plhsj3587hl8bb-python3.9-pysocks-1.7.1 kkljrrrj80fnz59qyfgnv6wvv0cbmpql-libhandy-1.5.0 l82il2lbp757c0smi81qmj4crlcmdz9s-python3.9-pygobject-3.42.0-dev m4zflhr10wz4frhgxqfi43rwvapki1pi-fontconfig-2.13.94-bin mbsc1c7mq15vgfzcdma9fglczih9ncfy-python3.9-chardet-4.0.0 mfvaaf4illpwrflg30cij5x4rncp9jin-python3.9-text-unidecode-1.3 msiv2nkdcaf4gvaf2cfnxcjm66j8mjxz-python3.9-elementpath-2.4.0 nmwapds8fcx22vd30d81va7a7a51ywwx-gettext-0.21 pbfraw351mksnkp2ni9c4rkc9cpp89iv-bash-5.1-p12 r8cbf18vrd54rb4psf3m4zlk5sd2jsv3-python3.9-pygobject-3.42.0 rig6npd9sd45ashf6fxcwgxzm7m4p0l3-python3.9-requests-2.26.0 ryj72ashr27gf4kh0ssgi3zpiv8fxw53-librsvg-2.52.4 s2jjq7rk5yrzlv9lyralzvpixg4p6jh3-atk-2.36.0 w1lsr2i37fr0mp1jya04nwa5nf5dxm2n-python3.9-setuptools-57.2.0 whfykra99ahs814l5hp3q5ps8rwzsf3s-python3.9-brotlicffi-1.0.9.2 wqdmghdvc4s95jgpp13fj5v3xar8mlks-python3.9-charset-normalizer-2.0.8 x1ha2nyji1px0iqknbyhdnvw4icw5h3i-python3.9-idna-3.3 z9560qb4ygbi0352m9pglwhi332cxb1f-python3.9-urllib3-1.26.7
+Deriver: 2ch8jx910qk6721mp4yqsmvdfgj5c8ir-banking-0.3.0.drv
+Sig: cache.nixos.org-1:xcL67rBZPcdVZudDLpLeddkBa0KaFTw5A0udnaa0axysjrQ6Nvd9p3BLZ4rhKgl52/cKiU3c6aq60L8+IcE5Dw==
+"#;
+
+lazy_static! {
+    static ref CASES: &'static [&'static str] = {
+        let data =
+            zstd::decode_all(io::Cursor::new(include_bytes!("../testdata/narinfo.zst"))).unwrap();
+        let data = str::from_utf8(Vec::leak(data)).unwrap();
+        Vec::leak(
+            data.split_inclusive("\n\n")
+                .map(|s| s.strip_suffix('\n').unwrap())
+                .collect::<Vec<_>>(),
+        )
+    };
+}
+
+pub fn parse(c: &mut Criterion) {
+    let mut g = c.benchmark_group("parse");
+
+    {
+        g.throughput(Throughput::Bytes(SAMPLE.len() as u64));
+        g.bench_with_input("single", SAMPLE, |b, data| {
+            b.iter(|| {
+                black_box(NarInfo::parse(black_box(data)).ok().unwrap());
+            });
+        });
+    }
+
+    {
+        for &case in *CASES {
+            NarInfo::parse(case).expect("should parse");
+        }
+
+        g.throughput(Throughput::Bytes(
+            CASES.iter().map(|s| s.len() as u64).sum(),
+        ));
+        g.bench_with_input("many", &*CASES, |b, data| {
+            let mut vec = vec![];
+            b.iter(|| {
+                vec.clear();
+                vec.extend(
+                    black_box(data)
+                        .iter()
+                        .map(|s| NarInfo::parse(s).ok().unwrap()),
+                );
+                black_box(&vec);
+            });
+        });
+    }
+
+    g.finish();
+}
+
+criterion_group!(benches, parse);
+criterion_main!(benches);
diff --git a/tvix/nix-compat/default.nix b/tvix/nix-compat/default.nix
new file mode 100644
index 0000000000..9df76e12fc
--- /dev/null
+++ b/tvix/nix-compat/default.nix
@@ -0,0 +1,7 @@
+{ depot, ... }:
+
+depot.tvix.crates.workspaceMembers.nix-compat.build.override {
+  runTests = true;
+  # make sure we also enable async here, so run the tests behind that feature flag.
+  features = [ "default" "async" "wire" ];
+}
diff --git a/tvix/nix-compat/src/aterm/escape.rs b/tvix/nix-compat/src/aterm/escape.rs
new file mode 100644
index 0000000000..80a85d2103
--- /dev/null
+++ b/tvix/nix-compat/src/aterm/escape.rs
@@ -0,0 +1,28 @@
+use bstr::ByteSlice;
+
+/// Escapes a byte sequence. Does not add surrounding quotes.
+pub fn escape_bytes<P: AsRef<[u8]>>(s: P) -> Vec<u8> {
+    let mut s: Vec<u8> = s.as_ref().to_vec();
+
+    s = s.replace(b"\\", b"\\\\");
+    s = s.replace(b"\n", b"\\n");
+    s = s.replace(b"\r", b"\\r");
+    s = s.replace(b"\t", b"\\t");
+    s = s.replace(b"\"", b"\\\"");
+
+    s
+}
+
+#[cfg(test)]
+mod tests {
+    use super::escape_bytes;
+    use rstest::rstest;
+
+    #[rstest]
+    #[case::empty(b"", b"")]
+    #[case::doublequote(b"\"", b"\\\"")]
+    #[case::colon(b":", b":")]
+    fn escape(#[case] input: &[u8], #[case] expected: &[u8]) {
+        assert_eq!(expected, escape_bytes(input))
+    }
+}
diff --git a/tvix/nix-compat/src/aterm/mod.rs b/tvix/nix-compat/src/aterm/mod.rs
new file mode 100644
index 0000000000..8806b6caf2
--- /dev/null
+++ b/tvix/nix-compat/src/aterm/mod.rs
@@ -0,0 +1,7 @@
+mod escape;
+mod parser;
+
+pub(crate) use escape::escape_bytes;
+pub(crate) use parser::parse_bstr_field;
+pub(crate) use parser::parse_str_list;
+pub(crate) use parser::parse_string_field;
diff --git a/tvix/nix-compat/src/aterm/parser.rs b/tvix/nix-compat/src/aterm/parser.rs
new file mode 100644
index 0000000000..a30cb40ab0
--- /dev/null
+++ b/tvix/nix-compat/src/aterm/parser.rs
@@ -0,0 +1,125 @@
+//! This module implements parsing code for some basic building blocks
+//! of the [ATerm][] format, which is used by C++ Nix to serialize Derivations.
+//!
+//! [ATerm]: http://program-transformation.org/Tools/ATermFormat.html
+use bstr::BString;
+use nom::branch::alt;
+use nom::bytes::complete::{escaped_transform, is_not, tag};
+use nom::character::complete::char as nomchar;
+use nom::combinator::{map, value};
+use nom::multi::separated_list0;
+use nom::sequence::delimited;
+use nom::IResult;
+
+/// Parse a bstr and undo any escaping.
+fn parse_escaped_bstr(i: &[u8]) -> IResult<&[u8], BString> {
+    escaped_transform(
+        is_not("\"\\"),
+        '\\',
+        alt((
+            value("\\".as_bytes(), nomchar('\\')),
+            value("\n".as_bytes(), nomchar('n')),
+            value("\t".as_bytes(), nomchar('t')),
+            value("\r".as_bytes(), nomchar('r')),
+            value("\"".as_bytes(), nomchar('\"')),
+        )),
+    )(i)
+    .map(|(i, v)| (i, BString::new(v)))
+}
+
+/// Parse a field in double quotes, undo any escaping, and return the unquoted
+/// and decoded `Vec<u8>`.
+pub(crate) fn parse_bstr_field(i: &[u8]) -> IResult<&[u8], BString> {
+    // inside double quotes…
+    delimited(
+        nomchar('\"'),
+        // There is
+        alt((
+            // …either is a bstr after unescaping
+            parse_escaped_bstr,
+            // …or an empty string.
+            map(tag(b""), |_| BString::default()),
+        )),
+        nomchar('\"'),
+    )(i)
+}
+
+/// Parse a field in double quotes, undo any escaping, and return the unquoted
+/// and decoded string, if it's a valid string. Or fail parsing if the bytes are
+/// no valid UTF-8.
+pub(crate) fn parse_string_field(i: &[u8]) -> IResult<&[u8], String> {
+    // inside double quotes…
+    delimited(
+        nomchar('\"'),
+        // There is
+        alt((
+            // either is a String after unescaping
+            nom::combinator::map_opt(parse_escaped_bstr, |escaped_bstr| {
+                String::from_utf8(escaped_bstr.into()).ok()
+            }),
+            // or an empty string.
+            map(tag(b""), |_| String::new()),
+        )),
+        nomchar('\"'),
+    )(i)
+}
+
+/// Parse a list of of string fields (enclosed in brackets)
+pub(crate) fn parse_str_list(i: &[u8]) -> IResult<&[u8], Vec<String>> {
+    // inside brackets
+    delimited(
+        nomchar('['),
+        separated_list0(nomchar(','), parse_string_field),
+        nomchar(']'),
+    )(i)
+}
+
+#[cfg(test)]
+mod tests {
+    use rstest::rstest;
+
+    #[rstest]
+    #[case::empty(br#""""#, b"", b"")]
+    #[case::hello_world(br#""Hello World""#, b"Hello World", b"")]
+    #[case::doublequote(br#""\"""#, br#"""#, b"")]
+    #[case::colon(br#"":""#, b":", b"")]
+    #[case::doublequote_rest(br#""\""Rest"#, br#"""#, b"Rest")]
+    fn test_parse_bstr_field(
+        #[case] input: &[u8],
+        #[case] expected: &[u8],
+        #[case] exp_rest: &[u8],
+    ) {
+        let (rest, parsed) = super::parse_bstr_field(input).expect("must parse");
+        assert_eq!(exp_rest, rest, "expected remainder");
+        assert_eq!(expected, parsed);
+    }
+
+    #[rstest]
+    #[case::empty(br#""""#, "", b"")]
+    #[case::hello_world(br#""Hello World""#, "Hello World", b"")]
+    #[case::doublequote(br#""\"""#, r#"""#, b"")]
+    #[case::colon(br#"":""#, ":", b"")]
+    #[case::doublequote_rest(br#""\""Rest"#, r#"""#, b"Rest")]
+    fn parse_string_field(#[case] input: &[u8], #[case] expected: &str, #[case] exp_rest: &[u8]) {
+        let (rest, parsed) = super::parse_string_field(input).expect("must parse");
+        assert_eq!(exp_rest, rest, "expected remainder");
+        assert_eq!(expected, &parsed);
+    }
+
+    #[test]
+    fn parse_string_field_invalid_encoding_fail() {
+        let input: Vec<_> = vec![b'"', 0xc5, 0xc4, 0xd6, b'"'];
+
+        super::parse_string_field(&input).expect_err("must fail");
+    }
+
+    #[rstest]
+    #[case::single_foo(br#"["foo"]"#, vec!["foo".to_string()], b"")]
+    #[case::empty_list(b"[]", vec![], b"")]
+    #[case::empty_list_with_rest(b"[]blub", vec![], b"blub")]
+    fn parse_list(#[case] input: &[u8], #[case] expected: Vec<String>, #[case] exp_rest: &[u8]) {
+        let (rest, parsed) = super::parse_str_list(input).expect("must parse");
+        assert_eq!(exp_rest, rest, "expected remainder");
+        assert_eq!(expected, parsed);
+    }
+}
diff --git a/tvix/nix-compat/src/bin/drvfmt.rs b/tvix/nix-compat/src/bin/drvfmt.rs
new file mode 100644
index 0000000000..ddc1f0389f
--- /dev/null
+++ b/tvix/nix-compat/src/bin/drvfmt.rs
@@ -0,0 +1,42 @@
+use std::{collections::BTreeMap, io::Read};
+
+use nix_compat::derivation::Derivation;
+use serde_json::json;
+
+/// construct a serde_json::Value from a Derivation.
+/// Some environment values can be non-valid UTF-8 strings.
+/// `serde_json` prints them out really unreadable.
+/// This is a tool to print A-Terms in a more readable fashion, so we brutally
+/// use the [std::string::ToString] implementation of [bstr::BString] to get
+/// a UTF-8 string (replacing invalid characters with the Unicode replacement
+/// codepoint).
+fn build_serde_json_value(drv: Derivation) -> serde_json::Value {
+    json!({
+        "args": drv.arguments,
+        "builder": drv.builder,
+        "env":   drv.environment.into_iter().map(|(k,v)| (k, v.to_string())).collect::<BTreeMap<String, String>>(),
+        "inputDrvs": drv.input_derivations,
+        "inputSrcs": drv.input_sources,
+        "outputs": drv.outputs,
+        "system": drv.system,
+    })
+}
+
+fn main() {
+    // read A-Term from stdin
+    let mut buf = Vec::new();
+    std::io::stdin()
+        .read_to_end(&mut buf)
+        .expect("failed to read from stdin");
+
+    match Derivation::from_aterm_bytes(&buf) {
+        Ok(drv) => {
+            println!(
+                "{}",
+                serde_json::to_string_pretty(&build_serde_json_value(drv))
+                    .expect("unable to serialize")
+            );
+        }
+        Err(e) => eprintln!("unable to parse derivation: {:#?}", e),
+    }
+}
diff --git a/tvix/nix-compat/src/derivation/errors.rs b/tvix/nix-compat/src/derivation/errors.rs
new file mode 100644
index 0000000000..452231f19d
--- /dev/null
+++ b/tvix/nix-compat/src/derivation/errors.rs
@@ -0,0 +1,60 @@
+//! Contains [DerivationError], exported as [crate::derivation::DerivationError]
+use crate::store_path;
+use thiserror::Error;
+
+use super::CAHash;
+
+/// Errors that can occur during the validation of Derivation structs.
+#[derive(Debug, Error, PartialEq)]
+pub enum DerivationError {
+    // outputs
+    #[error("no outputs defined")]
+    NoOutputs(),
+    #[error("invalid output name: {0}")]
+    InvalidOutputName(String),
+    #[error("encountered fixed-output derivation, but more than 1 output in total")]
+    MoreThanOneOutputButFixed(),
+    #[error("invalid output name for fixed-output derivation: {0}")]
+    InvalidOutputNameForFixed(String),
+    #[error("unable to validate output {0}: {1}")]
+    InvalidOutput(String, OutputError),
+    #[error("unable to validate output {0}: {1}")]
+    InvalidOutputDerivationPath(String, store_path::BuildStorePathError),
+    // input derivation
+    #[error("unable to parse input derivation path {0}: {1}")]
+    InvalidInputDerivationPath(String, store_path::Error),
+    #[error("input derivation {0} doesn't end with .drv")]
+    InvalidInputDerivationPrefix(String),
+    #[error("input derivation {0} output names are empty")]
+    EmptyInputDerivationOutputNames(String),
+    #[error("input derivation {0} output name {1} is invalid")]
+    InvalidInputDerivationOutputName(String, String),
+
+    // input sources
+    #[error("unable to parse input sources path {0}: {1}")]
+    InvalidInputSourcesPath(String, store_path::Error),
+
+    // platform
+    #[error("invalid platform field: {0}")]
+    InvalidPlatform(String),
+
+    // builder
+    #[error("invalid builder field: {0}")]
+    InvalidBuilder(String),
+
+    // environment
+    #[error("invalid environment key {0}")]
+    InvalidEnvironmentKey(String),
+}
+
+/// Errors that can occur during the validation of a specific
+// [crate::derivation::Output] of a [crate::derivation::Derviation].
+#[derive(Debug, Error, PartialEq)]
+pub enum OutputError {
+    #[error("Invalid output path {0}: {1}")]
+    InvalidOutputPath(String, store_path::Error),
+    #[error("Missing output path")]
+    MissingOutputPath,
+    #[error("Invalid CAHash: {:?}", .0)]
+    InvalidCAHash(CAHash),
+}
diff --git a/tvix/nix-compat/src/derivation/mod.rs b/tvix/nix-compat/src/derivation/mod.rs
new file mode 100644
index 0000000000..6e12e3ea86
--- /dev/null
+++ b/tvix/nix-compat/src/derivation/mod.rs
@@ -0,0 +1,305 @@
+use crate::store_path::{
+    self, build_ca_path, build_output_path, build_text_path, StorePath, StorePathRef,
+};
+use bstr::BString;
+use serde::{Deserialize, Serialize};
+use sha2::{Digest, Sha256};
+use std::collections::{BTreeMap, BTreeSet};
+use std::io;
+
+mod errors;
+mod output;
+mod parse_error;
+mod parser;
+mod validate;
+mod write;
+
+#[cfg(test)]
+mod tests;
+
+// Public API of the crate.
+pub use crate::nixhash::{CAHash, NixHash};
+pub use errors::{DerivationError, OutputError};
+pub use output::Output;
+
+use self::write::AtermWriteable;
+
+#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
+pub struct Derivation {
+    #[serde(rename = "args")]
+    pub arguments: Vec<String>,
+
+    pub builder: String,
+
+    #[serde(rename = "env")]
+    pub environment: BTreeMap<String, BString>,
+
+    /// Map from drv path to output names used from this derivation.
+    #[serde(rename = "inputDrvs")]
+    pub input_derivations: BTreeMap<StorePath, BTreeSet<String>>,
+
+    /// Plain store paths of additional inputs.
+    #[serde(rename = "inputSrcs")]
+    pub input_sources: BTreeSet<StorePath>,
+
+    /// Maps output names to Output.
+    pub outputs: BTreeMap<String, Output>,
+
+    pub system: String,
+}
+
+impl Derivation {
+    /// write the Derivation to the given [std::io::Write], in ATerm format.
+    ///
+    /// The only errors returns are these when writing to the passed writer.
+    pub fn serialize(&self, writer: &mut impl std::io::Write) -> Result<(), io::Error> {
+        self.serialize_with_replacements(writer, &self.input_derivations)
+    }
+
+    /// Like `serialize` but allow replacing the input_derivations for hash calculations.
+    fn serialize_with_replacements(
+        &self,
+        writer: &mut impl std::io::Write,
+        input_derivations: &BTreeMap<impl AtermWriteable, BTreeSet<String>>,
+    ) -> Result<(), io::Error> {
+        use write::*;
+
+        writer.write_all(write::DERIVATION_PREFIX.as_bytes())?;
+        write_char(writer, write::PAREN_OPEN)?;
+
+        write_outputs(writer, &self.outputs)?;
+        write_char(writer, COMMA)?;
+
+        write_input_derivations(writer, input_derivations)?;
+        write_char(writer, COMMA)?;
+
+        write_input_sources(writer, &self.input_sources)?;
+        write_char(writer, COMMA)?;
+
+        write_system(writer, &self.system)?;
+        write_char(writer, COMMA)?;
+
+        write_builder(writer, &self.builder)?;
+        write_char(writer, COMMA)?;
+
+        write_arguments(writer, &self.arguments)?;
+        write_char(writer, COMMA)?;
+
+        write_environment(writer, &self.environment)?;
+
+        write_char(writer, PAREN_CLOSE)?;
+
+        Ok(())
+    }
+
+    /// return the ATerm serialization.
+    pub fn to_aterm_bytes(&self) -> Vec<u8> {
+        self.to_aterm_bytes_with_replacements(&self.input_derivations)
+    }
+
+    /// Like `to_aterm_bytes`, but accept a different BTreeMap for input_derivations.
+    /// This is used to render the ATerm representation of a Derivation "modulo
+    /// fixed-output derivations".
+    fn to_aterm_bytes_with_replacements(
+        &self,
+        input_derivations: &BTreeMap<impl AtermWriteable, BTreeSet<String>>,
+    ) -> Vec<u8> {
+        let mut buffer: Vec<u8> = Vec::new();
+
+        // invoke serialize and write to the buffer.
+        // Note we only propagate errors writing to the writer in serialize,
+        // which won't panic for the string we write to.
+        self.serialize_with_replacements(&mut buffer, input_derivations)
+            .unwrap();
+
+        buffer
+    }
+
+    /// Parse an Derivation in ATerm serialization, and validate it passes our
+    /// set of validations.
+    pub fn from_aterm_bytes(b: &[u8]) -> Result<Derivation, parser::Error<&[u8]>> {
+        parser::parse(b)
+    }
+
+    /// Returns the drv path of a [Derivation] struct.
+    ///
+    /// The drv path is calculated by invoking [build_text_path], using
+    /// the `name` with a `.drv` suffix as name, all [Derivation::input_sources] and
+    /// keys of [Derivation::input_derivations] as references, and the ATerm string of
+    /// the [Derivation] as content.
+    pub fn calculate_derivation_path(&self, name: &str) -> Result<StorePath, DerivationError> {
+        // append .drv to the name
+        let name = &format!("{}.drv", name);
+
+        // collect the list of paths from input_sources and input_derivations
+        // into a (sorted, guaranteed by BTreeSet) list of references
+        let references: BTreeSet<String> = self
+            .input_sources
+            .iter()
+            .chain(self.input_derivations.keys())
+            .map(StorePath::to_absolute_path)
+            .collect();
+
+        build_text_path(name, self.to_aterm_bytes(), references)
+            .map(|s| s.to_owned())
+            .map_err(|_e| DerivationError::InvalidOutputName(name.to_string()))
+    }
+
+    /// Returns the FOD digest, if the derivation is fixed-output, or None if
+    /// it's not.
+    /// TODO: this is kinda the string from [build_ca_path] with a
+    /// [CAHash::Flat], what's fed to `build_store_path_from_fingerprint_parts`
+    /// (except the out_output.path being an empty string)
+    pub fn fod_digest(&self) -> Option<[u8; 32]> {
+        if self.outputs.len() != 1 {
+            return None;
+        }
+
+        let out_output = self.outputs.get("out")?;
+        let ca_hash = &out_output.ca_hash.as_ref()?;
+
+        Some(
+            Sha256::new_with_prefix(format!(
+                "fixed:out:{}{}:{}",
+                ca_kind_prefix(ca_hash),
+                ca_hash.hash().to_nix_hex_string(),
+                out_output
+                    .path
+                    .as_ref()
+                    .map(StorePath::to_absolute_path)
+                    .as_ref()
+                    .map(|s| s as &str)
+                    .unwrap_or(""),
+            ))
+            .finalize()
+            .into(),
+        )
+    }
+
+    /// Calculates the hash of a derivation modulo fixed-output subderivations.
+    ///
+    /// This is called `hashDerivationModulo` in nixcpp.
+    ///
+    /// It returns the sha256 digest of the derivation ATerm representation,
+    /// except that:
+    ///  -  any input derivation paths have beed replaced "by the result of a
+    ///     recursive call to this function" and that
+    ///  - for fixed-output derivations the special
+    ///    `fixed:out:${algo}:${digest}:${fodPath}` string is hashed instead of
+    ///    the A-Term.
+    ///
+    /// It's up to the caller of this function to provide a (infallible) lookup
+    /// function to query [hash_derivation_modulo] of direct input derivations,
+    /// by their [StorePathRef].
+    /// It will only be called in case the derivation is not a fixed-output
+    /// derivation.
+    pub fn hash_derivation_modulo<F>(&self, fn_lookup_hash_derivation_modulo: F) -> [u8; 32]
+    where
+        F: Fn(&StorePathRef) -> [u8; 32],
+    {
+        // Fixed-output derivations return a fixed hash.
+        // Non-Fixed-output derivations return the sha256 digest of the ATerm
+        // notation, but with all input_derivation paths replaced by a recursive
+        // call to this function.
+        // We call [fn_lookup_hash_derivation_modulo] rather than recursing
+        // ourselves, so callers can precompute this.
+        self.fod_digest().unwrap_or({
+            // For each input_derivation, look up the hash derivation modulo,
+            // and replace the derivation path in the aterm with it's HEXLOWER digest.
+            let aterm_bytes = self.to_aterm_bytes_with_replacements(&BTreeMap::from_iter(
+                self.input_derivations
+                    .iter()
+                    .map(|(drv_path, output_names)| {
+                        let hash = fn_lookup_hash_derivation_modulo(&drv_path.into());
+
+                        (hash, output_names.to_owned())
+                    }),
+            ));
+
+            // write the ATerm of that to the hash function and return its digest.
+            Sha256::new_with_prefix(aterm_bytes).finalize().into()
+        })
+    }
+
+    /// This calculates all output paths of a Derivation and updates the struct.
+    /// It requires the struct to be initially without output paths.
+    /// This means, self.outputs[$outputName].path needs to be an empty string,
+    /// and self.environment[$outputName] needs to be an empty string.
+    ///
+    /// Output path calculation requires knowledge of the
+    /// [hash_derivation_modulo], which (in case of non-fixed-output
+    /// derivations) also requires knowledge of the [hash_derivation_modulo] of
+    /// input derivations (recursively).
+    ///
+    /// To avoid recursing and doing unnecessary calculation, we simply
+    /// ask the caller of this function to provide the result of the
+    /// [hash_derivation_modulo] call of the current [Derivation],
+    /// and leave it up to them to calculate it when needed.
+    ///
+    /// On completion, `self.environment[$outputName]` and
+    /// `self.outputs[$outputName].path` are set to the calculated output path for all
+    /// outputs.
+    pub fn calculate_output_paths(
+        &mut self,
+        name: &str,
+        hash_derivation_modulo: &[u8; 32],
+    ) -> Result<(), DerivationError> {
+        // The fingerprint and hash differs per output
+        for (output_name, output) in self.outputs.iter_mut() {
+            // Assert that outputs are not yet populated, to avoid using this function wrongly.
+            // We don't also go over self.environment, but it's a sufficient
+            // footgun prevention mechanism.
+            assert!(output.path.is_none());
+
+            let path_name = output_path_name(name, output_name);
+
+            // For fixed output derivation we use [build_ca_path], otherwise we
+            // use [build_output_path] with [hash_derivation_modulo].
+            let abs_store_path = if let Some(ref hwm) = output.ca_hash {
+                build_ca_path(&path_name, hwm, Vec::<String>::new(), false).map_err(|e| {
+                    DerivationError::InvalidOutputDerivationPath(output_name.to_string(), e)
+                })?
+            } else {
+                build_output_path(hash_derivation_modulo, output_name, &path_name).map_err(|e| {
+                    DerivationError::InvalidOutputDerivationPath(
+                        output_name.to_string(),
+                        store_path::BuildStorePathError::InvalidStorePath(e),
+                    )
+                })?
+            };
+
+            output.path = Some(abs_store_path.to_owned());
+            self.environment.insert(
+                output_name.to_string(),
+                abs_store_path.to_absolute_path().into(),
+            );
+        }
+
+        Ok(())
+    }
+}
+
+/// Calculate the name part of the store path of a derivation [Output].
+///
+/// It's the name, and (if it's the non-out output), the output name
+/// after a `-`.
+fn output_path_name(derivation_name: &str, output_name: &str) -> String {
+    let mut output_path_name = derivation_name.to_string();
+    if output_name != "out" {
+        output_path_name.push('-');
+        output_path_name.push_str(output_name);
+    }
+    output_path_name
+}
+
+/// For a [CAHash], return the "prefix" used for NAR purposes.
+/// For [CAHash::Flat], this is an empty string, for [CAHash::Nar], it's "r:".
+/// Panics for other [CAHash] kinds, as they're not valid in a derivation
+/// context.
+fn ca_kind_prefix(ca_hash: &CAHash) -> &'static str {
+    match ca_hash {
+        CAHash::Flat(_) => "",
+        CAHash::Nar(_) => "r:",
+        _ => panic!("invalid ca hash in derivation context: {:?}", ca_hash),
+    }
+}
diff --git a/tvix/nix-compat/src/derivation/output.rs b/tvix/nix-compat/src/derivation/output.rs
new file mode 100644
index 0000000000..266617f587
--- /dev/null
+++ b/tvix/nix-compat/src/derivation/output.rs
@@ -0,0 +1,189 @@
+use crate::nixhash::CAHash;
+use crate::store_path::StorePathRef;
+use crate::{derivation::OutputError, store_path::StorePath};
+use serde::de::Unexpected;
+use serde::{Deserialize, Serialize};
+use serde_json::Map;
+use std::borrow::Cow;
+
+/// References the derivation output.
+#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize)]
+pub struct Output {
+    /// Store path of build result.
+    pub path: Option<StorePath>,
+
+    #[serde(flatten)]
+    pub ca_hash: Option<CAHash>, // we can only represent a subset here.
+}
+
+impl<'de> Deserialize<'de> for Output {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: serde::Deserializer<'de>,
+    {
+        let fields = Map::deserialize(deserializer)?;
+        let path: &str = fields
+            .get("path")
+            .ok_or(serde::de::Error::missing_field(
+                "`path` is missing but required for outputs",
+            ))?
+            .as_str()
+            .ok_or(serde::de::Error::invalid_type(
+                serde::de::Unexpected::Other("certainly not a string"),
+                &"a string",
+            ))?;
+
+        let path = StorePathRef::from_absolute_path(path.as_bytes())
+            .map_err(|_| serde::de::Error::invalid_value(Unexpected::Str(path), &"StorePath"))?;
+        Ok(Self {
+            path: Some(path.to_owned()),
+            ca_hash: CAHash::from_map::<D>(&fields)?,
+        })
+    }
+}
+
+impl Output {
+    pub fn is_fixed(&self) -> bool {
+        self.ca_hash.is_some()
+    }
+
+    /// The output path as a string -- use `""` to indicate an unset output path.
+    pub fn path_str(&self) -> Cow<str> {
+        match &self.path {
+            None => Cow::Borrowed(""),
+            Some(path) => Cow::Owned(path.to_absolute_path()),
+        }
+    }
+
+    pub fn validate(&self, validate_output_paths: bool) -> Result<(), OutputError> {
+        if let Some(fixed_output_hash) = &self.ca_hash {
+            match fixed_output_hash {
+                CAHash::Flat(_) | CAHash::Nar(_) => {
+                    // all hashes allowed for Flat, and Nar.
+                }
+                _ => return Err(OutputError::InvalidCAHash(fixed_output_hash.clone())),
+            }
+        }
+
+        if validate_output_paths && self.path.is_none() {
+            return Err(OutputError::MissingOutputPath);
+        }
+        Ok(())
+    }
+}
+
+/// This ensures that a potentially valid input addressed
+/// output is deserialized as a non-fixed output.
+#[test]
+fn deserialize_valid_input_addressed_output() {
+    let json_bytes = r#"
+    {
+      "path": "/nix/store/00bgd045z0d4icpbc2yyz4gx48ak44la-net-tools-1.60_p20170221182432"
+    }"#;
+    let output: Output = serde_json::from_str(json_bytes).expect("must parse");
+
+    assert!(!output.is_fixed());
+}
+
+/// This ensures that a potentially valid fixed output
+/// output deserializes fine as a fixed output.
+#[test]
+fn deserialize_valid_fixed_output() {
+    let json_bytes = r#"
+    {
+        "path": "/nix/store/00bgd045z0d4icpbc2yyz4gx48ak44la-net-tools-1.60_p20170221182432",
+        "hash": "08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba",
+        "hashAlgo": "r:sha256"
+    }"#;
+    let output: Output = serde_json::from_str(json_bytes).expect("must parse");
+
+    assert!(output.is_fixed());
+}
+
+/// This ensures that parsing an input with the invalid hash encoding
+/// will result in a parsing failure.
+#[test]
+fn deserialize_with_error_invalid_hash_encoding_fixed_output() {
+    let json_bytes = r#"
+    {
+        "path": "/nix/store/00bgd045z0d4icpbc2yyz4gx48ak44la-net-tools-1.60_p20170221182432",
+        "hash": "IAMNOTVALIDNIXBASE32",
+        "hashAlgo": "r:sha256"
+    }"#;
+    let output: Result<Output, _> = serde_json::from_str(json_bytes);
+
+    assert!(output.is_err());
+}
+
+/// This ensures that parsing an input with the wrong hash algo
+/// will result in a parsing failure.
+#[test]
+fn deserialize_with_error_invalid_hash_algo_fixed_output() {
+    let json_bytes = r#"
+    {
+        "path": "/nix/store/00bgd045z0d4icpbc2yyz4gx48ak44la-net-tools-1.60_p20170221182432",
+        "hash": "08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba",
+        "hashAlgo": "r:sha1024"
+    }"#;
+    let output: Result<Output, _> = serde_json::from_str(json_bytes);
+
+    assert!(output.is_err());
+}
+
+/// This ensures that parsing an input with the missing hash algo but present hash will result in a
+/// parsing failure.
+#[test]
+fn deserialize_with_error_missing_hash_algo_fixed_output() {
+    let json_bytes = r#"
+    {
+        "path": "/nix/store/00bgd045z0d4icpbc2yyz4gx48ak44la-net-tools-1.60_p20170221182432",
+        "hash": "08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba",
+    }"#;
+    let output: Result<Output, _> = serde_json::from_str(json_bytes);
+
+    assert!(output.is_err());
+}
+
+/// This ensures that parsing an input with the missing hash but present hash algo will result in a
+/// parsing failure.
+#[test]
+fn deserialize_with_error_missing_hash_fixed_output() {
+    let json_bytes = r#"
+    {
+        "path": "/nix/store/00bgd045z0d4icpbc2yyz4gx48ak44la-net-tools-1.60_p20170221182432",
+        "hashAlgo": "r:sha1024"
+    }"#;
+    let output: Result<Output, _> = serde_json::from_str(json_bytes);
+
+    assert!(output.is_err());
+}
+
+#[test]
+fn serialize_deserialize() {
+    let json_bytes = r#"
+    {
+      "path": "/nix/store/00bgd045z0d4icpbc2yyz4gx48ak44la-net-tools-1.60_p20170221182432"
+    }"#;
+    let output: Output = serde_json::from_str(json_bytes).expect("must parse");
+
+    let s = serde_json::to_string(&output).expect("Serialize");
+    let output2: Output = serde_json::from_str(&s).expect("must parse again");
+
+    assert_eq!(output, output2);
+}
+
+#[test]
+fn serialize_deserialize_fixed() {
+    let json_bytes = r#"
+    {
+        "path": "/nix/store/00bgd045z0d4icpbc2yyz4gx48ak44la-net-tools-1.60_p20170221182432",
+        "hash": "08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba",
+        "hashAlgo": "r:sha256"
+    }"#;
+    let output: Output = serde_json::from_str(json_bytes).expect("must parse");
+
+    let s = serde_json::to_string_pretty(&output).expect("Serialize");
+    let output2: Output = serde_json::from_str(&s).expect("must parse again");
+
+    assert_eq!(output, output2);
+}
diff --git a/tvix/nix-compat/src/derivation/parse_error.rs b/tvix/nix-compat/src/derivation/parse_error.rs
new file mode 100644
index 0000000000..fc97f1a988
--- /dev/null
+++ b/tvix/nix-compat/src/derivation/parse_error.rs
@@ -0,0 +1,87 @@
+//! This contains error and result types that can happen while parsing
+//! Derivations from ATerm.
+use nom::IResult;
+
+use crate::{
+    nixhash,
+    store_path::{self, StorePath},
+};
+
+pub type NomResult<I, O> = IResult<I, O, NomError<I>>;
+
+#[derive(Debug, thiserror::Error, PartialEq)]
+pub enum ErrorKind {
+    /// duplicate key in map
+    #[error("duplicate map key: {0}")]
+    DuplicateMapKey(String),
+
+    /// Input derivation has two outputs with the same name
+    #[error("duplicate output name {1} for input derivation {0}")]
+    DuplicateInputDerivationOutputName(String, String),
+
+    #[error("duplicate input source: {0}")]
+    DuplicateInputSource(StorePath),
+
+    #[error("nix hash error: {0}")]
+    NixHashError(nixhash::Error),
+
+    #[error("store path error: {0}")]
+    StorePathError(#[from] store_path::Error),
+
+    #[error("nom error: {0:?}")]
+    Nom(nom::error::ErrorKind),
+}
+
+/// Our own error type to pass along parser-related errors.
+#[derive(Debug, PartialEq)]
+pub struct NomError<I> {
+    /// position of the error in the input data
+    pub input: I,
+    /// error code
+    pub code: ErrorKind,
+}
+
+impl<I, E> nom::error::FromExternalError<I, E> for NomError<I> {
+    fn from_external_error(input: I, kind: nom::error::ErrorKind, _e: E) -> Self {
+        Self {
+            input,
+            code: ErrorKind::Nom(kind),
+        }
+    }
+}
+
+impl<I> nom::error::ParseError<I> for NomError<I> {
+    fn from_error_kind(input: I, kind: nom::error::ErrorKind) -> Self {
+        Self {
+            input,
+            code: ErrorKind::Nom(kind),
+        }
+    }
+
+    // FUTUREWORK: implement, so we have support for backtracking through the
+    // parse tree?
+    fn append(_input: I, _kind: nom::error::ErrorKind, other: Self) -> Self {
+        other
+    }
+}
+
+/// This wraps a [nom::error::Error] into our error.
+impl<I> From<nom::error::Error<I>> for NomError<I> {
+    fn from(value: nom::error::Error<I>) -> Self {
+        Self {
+            input: value.input,
+            code: ErrorKind::Nom(value.code),
+        }
+    }
+}
+
+/// This essentially implements
+/// `From<nom::Err<nom::error::Error<I>>>` for `nom::Err<NomError<I>>`,
+/// which we can't because `nom::Err<_>` is a foreign type.
+pub(crate) fn into_nomerror<I>(e: nom::Err<nom::error::Error<I>>) -> nom::Err<NomError<I>> {
+    match e {
+        nom::Err::Incomplete(n) => nom::Err::Incomplete(n),
+        nom::Err::Error(e) => nom::Err::Error(e.into()),
+        nom::Err::Failure(e) => nom::Err::Failure(e.into()),
+    }
+}
diff --git a/tvix/nix-compat/src/derivation/parser.rs b/tvix/nix-compat/src/derivation/parser.rs
new file mode 100644
index 0000000000..2775294960
--- /dev/null
+++ b/tvix/nix-compat/src/derivation/parser.rs
@@ -0,0 +1,585 @@
+//! This module constructs a [Derivation] by parsing its [ATerm][]
+//! serialization.
+//!
+//! [ATerm]: http://program-transformation.org/Tools/ATermFormat.html
+
+use bstr::BString;
+use nom::bytes::complete::tag;
+use nom::character::complete::char as nomchar;
+use nom::combinator::{all_consuming, map_res};
+use nom::multi::{separated_list0, separated_list1};
+use nom::sequence::{delimited, preceded, separated_pair, terminated, tuple};
+use std::collections::{btree_map, BTreeMap, BTreeSet};
+use thiserror;
+
+use crate::derivation::parse_error::{into_nomerror, ErrorKind, NomError, NomResult};
+use crate::derivation::{write, CAHash, Derivation, Output};
+use crate::store_path::{self, StorePath, StorePathRef};
+use crate::{aterm, nixhash};
+
+#[derive(Debug, thiserror::Error)]
+pub enum Error<I> {
+    #[error("parsing error: {0}")]
+    Parser(NomError<I>),
+    #[error("premature EOF")]
+    Incomplete,
+    #[error("validation error: {0}")]
+    Validation(super::DerivationError),
+}
+
+pub(crate) fn parse(i: &[u8]) -> Result<Derivation, Error<&[u8]>> {
+    match all_consuming(parse_derivation)(i) {
+        Ok((rest, derivation)) => {
+            // this shouldn't happen, as all_consuming shouldn't return.
+            debug_assert!(rest.is_empty());
+
+            // invoke validate
+            derivation.validate(true).map_err(Error::Validation)?;
+
+            Ok(derivation)
+        }
+        Err(nom::Err::Incomplete(_)) => Err(Error::Incomplete),
+        Err(nom::Err::Error(e) | nom::Err::Failure(e)) => Err(Error::Parser(e)),
+    }
+}
+
+/// Consume a string containing the algo, and optionally a `r:`
+/// prefix, and a digest (bytes), return a [CAHash::Nar] or [CAHash::Flat].
+fn from_algo_and_mode_and_digest<B: AsRef<[u8]>>(
+    algo_and_mode: &str,
+    digest: B,
+) -> crate::nixhash::NixHashResult<CAHash> {
+    Ok(match algo_and_mode.strip_prefix("r:") {
+        Some(algo) => nixhash::CAHash::Nar(nixhash::from_algo_and_digest(
+            algo.try_into()?,
+            digest.as_ref(),
+        )?),
+        None => nixhash::CAHash::Flat(nixhash::from_algo_and_digest(
+            algo_and_mode.try_into()?,
+            digest.as_ref(),
+        )?),
+    })
+}
+
+/// Parse one output in ATerm. This is 4 string fields inside parans:
+/// output name, output path, algo (and mode), digest.
+/// Returns the output name and [Output] struct.
+fn parse_output(i: &[u8]) -> NomResult<&[u8], (String, Output)> {
+    delimited(
+        nomchar('('),
+        map_res(
+            |i| {
+                tuple((
+                    terminated(aterm::parse_string_field, nomchar(',')),
+                    terminated(aterm::parse_string_field, nomchar(',')),
+                    terminated(aterm::parse_string_field, nomchar(',')),
+                    aterm::parse_bstr_field,
+                ))(i)
+                .map_err(into_nomerror)
+            },
+            |(output_name, output_path, algo_and_mode, encoded_digest)| {
+                // convert these 4 fields into an [Output].
+                let ca_hash_res = {
+                    if algo_and_mode.is_empty() && encoded_digest.is_empty() {
+                        None
+                    } else {
+                        match data_encoding::HEXLOWER.decode(&encoded_digest) {
+                            Ok(digest) => {
+                                Some(from_algo_and_mode_and_digest(&algo_and_mode, digest))
+                            }
+                            Err(e) => Some(Err(nixhash::Error::InvalidBase64Encoding(e))),
+                        }
+                    }
+                }
+                .transpose();
+
+                match ca_hash_res {
+                    Ok(hash_with_mode) => Ok((
+                        output_name,
+                        Output {
+                            // TODO: Check if allowing empty paths here actually makes sense
+                            //       or we should make this code stricter.
+                            path: if output_path.is_empty() {
+                                None
+                            } else {
+                                Some(string_to_store_path(i, output_path)?)
+                            },
+                            ca_hash: hash_with_mode,
+                        },
+                    )),
+                    Err(e) => Err(nom::Err::Failure(NomError {
+                        input: i,
+                        code: ErrorKind::NixHashError(e),
+                    })),
+                }
+            },
+        ),
+        nomchar(')'),
+    )(i)
+}
+
+/// Parse multiple outputs in ATerm. This is a list of things acccepted by
+/// parse_output, and takes care of turning the (String, Output) returned from
+/// it to a BTreeMap.
+/// We don't use parse_kv here, as it's dealing with 2-tuples, and these are
+/// 4-tuples.
+fn parse_outputs(i: &[u8]) -> NomResult<&[u8], BTreeMap<String, Output>> {
+    let res = delimited(
+        nomchar('['),
+        separated_list1(tag(","), parse_output),
+        nomchar(']'),
+    )(i);
+
+    match res {
+        Ok((rst, outputs_lst)) => {
+            let mut outputs: BTreeMap<String, Output> = BTreeMap::default();
+            for (output_name, output) in outputs_lst.into_iter() {
+                if outputs.contains_key(&output_name) {
+                    return Err(nom::Err::Failure(NomError {
+                        input: i,
+                        code: ErrorKind::DuplicateMapKey(output_name),
+                    }));
+                }
+                outputs.insert(output_name, output);
+            }
+            Ok((rst, outputs))
+        }
+        // pass regular parse errors along
+        Err(e) => Err(e),
+    }
+}
+
+fn parse_input_derivations(i: &[u8]) -> NomResult<&[u8], BTreeMap<StorePath, BTreeSet<String>>> {
+    let (i, input_derivations_list) = parse_kv::<Vec<String>, _>(aterm::parse_str_list)(i)?;
+
+    // This is a HashMap of drv paths to a list of output names.
+    let mut input_derivations: BTreeMap<StorePath, BTreeSet<String>> = BTreeMap::new();
+
+    for (input_derivation, output_names) in input_derivations_list {
+        let mut new_output_names = BTreeSet::new();
+        for output_name in output_names.into_iter() {
+            if new_output_names.contains(&output_name) {
+                return Err(nom::Err::Failure(NomError {
+                    input: i,
+                    code: ErrorKind::DuplicateInputDerivationOutputName(
+                        input_derivation.to_string(),
+                        output_name.to_string(),
+                    ),
+                }));
+            }
+            new_output_names.insert(output_name);
+        }
+
+        let input_derivation: StorePath = string_to_store_path(i, input_derivation)?;
+
+        input_derivations.insert(input_derivation, new_output_names);
+    }
+
+    Ok((i, input_derivations))
+}
+
+fn parse_input_sources(i: &[u8]) -> NomResult<&[u8], BTreeSet<StorePath>> {
+    let (i, input_sources_lst) = aterm::parse_str_list(i).map_err(into_nomerror)?;
+
+    let mut input_sources: BTreeSet<_> = BTreeSet::new();
+    for input_source in input_sources_lst.into_iter() {
+        let input_source: StorePath = string_to_store_path(i, input_source)?;
+        if input_sources.contains(&input_source) {
+            return Err(nom::Err::Failure(NomError {
+                input: i,
+                code: ErrorKind::DuplicateInputSource(input_source),
+            }));
+        } else {
+            input_sources.insert(input_source);
+        }
+    }
+
+    Ok((i, input_sources))
+}
+
+fn string_to_store_path(
+    i: &[u8],
+    path_str: String,
+) -> Result<StorePath, nom::Err<NomError<&[u8]>>> {
+    #[cfg(debug_assertions)]
+    let path_str2 = path_str.clone();
+
+    let path: StorePath = StorePathRef::from_absolute_path(path_str.as_bytes())
+        .map_err(|e: store_path::Error| {
+            nom::Err::Failure(NomError {
+                input: i,
+                code: e.into(),
+            })
+        })?
+        .to_owned();
+
+    #[cfg(debug_assertions)]
+    assert_eq!(path_str2, path.to_absolute_path());
+
+    Ok(path)
+}
+
+pub fn parse_derivation(i: &[u8]) -> NomResult<&[u8], Derivation> {
+    use nom::Parser;
+    preceded(
+        tag(write::DERIVATION_PREFIX),
+        delimited(
+            // inside parens
+            nomchar('('),
+            // tuple requires all errors to be of the same type, so we need to be a
+            // bit verbose here wrapping generic IResult into [NomATermResult].
+            tuple((
+                // parse outputs
+                terminated(parse_outputs, nomchar(',')),
+                // // parse input derivations
+                terminated(parse_input_derivations, nomchar(',')),
+                // // parse input sources
+                terminated(parse_input_sources, nomchar(',')),
+                // // parse system
+                |i| terminated(aterm::parse_string_field, nomchar(','))(i).map_err(into_nomerror),
+                // // parse builder
+                |i| terminated(aterm::parse_string_field, nomchar(','))(i).map_err(into_nomerror),
+                // // parse arguments
+                |i| terminated(aterm::parse_str_list, nomchar(','))(i).map_err(into_nomerror),
+                // parse environment
+                parse_kv::<BString, _>(aterm::parse_bstr_field),
+            )),
+            nomchar(')'),
+        )
+        .map(
+            |(
+                outputs,
+                input_derivations,
+                input_sources,
+                system,
+                builder,
+                arguments,
+                environment,
+            )| {
+                Derivation {
+                    arguments,
+                    builder,
+                    environment,
+                    input_derivations,
+                    input_sources,
+                    outputs,
+                    system,
+                }
+            },
+        ),
+    )(i)
+}
+
+/// Parse a list of key/value pairs into a BTreeMap.
+/// The parser for the values can be passed in.
+/// In terms of ATerm, this is just a 2-tuple,
+/// but we have the additional restriction that the first element needs to be
+/// unique across all tuples.
+pub(crate) fn parse_kv<'a, V, VF>(
+    vf: VF,
+) -> impl FnMut(&'a [u8]) -> NomResult<&'a [u8], BTreeMap<String, V>> + 'static
+where
+    VF: FnMut(&'a [u8]) -> nom::IResult<&'a [u8], V, nom::error::Error<&'a [u8]>> + Clone + 'static,
+{
+    move |i|
+    // inside brackets
+    delimited(
+        nomchar('['),
+        |ii| {
+            let res = separated_list0(
+                nomchar(','),
+                // inside parens
+                delimited(
+                    nomchar('('),
+                    separated_pair(
+                        aterm::parse_string_field,
+                        nomchar(','),
+                        vf.clone(),
+                    ),
+                    nomchar(')'),
+                ),
+            )(ii).map_err(into_nomerror);
+
+            match res {
+                Ok((rest, pairs)) => {
+                    let mut kvs: BTreeMap<String, V> = BTreeMap::new();
+                    for (k, v) in pairs.into_iter() {
+                        // collect the 2-tuple to a BTreeMap,
+                        // and fail if the key was already seen before.
+                        match kvs.entry(k) {
+                            btree_map::Entry::Vacant(e) => { e.insert(v); },
+                            btree_map::Entry::Occupied(e) => {
+                                return Err(nom::Err::Failure(NomError {
+                                    input: i,
+                                    code: ErrorKind::DuplicateMapKey(e.key().clone()),
+                                }));
+                            }
+                        }
+                    }
+                    Ok((rest, kvs))
+                }
+                Err(e) => Err(e),
+            }
+        },
+        nomchar(']'),
+    )(i)
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::store_path::StorePathRef;
+    use std::collections::{BTreeMap, BTreeSet};
+
+    use crate::{
+        derivation::{
+            parse_error::ErrorKind, parser::from_algo_and_mode_and_digest, CAHash, NixHash, Output,
+        },
+        store_path::StorePath,
+    };
+    use bstr::{BString, ByteSlice};
+    use hex_literal::hex;
+    use lazy_static::lazy_static;
+    use rstest::rstest;
+
+    const DIGEST_SHA256: [u8; 32] =
+        hex!("a5ce9c155ed09397614646c9717fc7cd94b1023d7b76b618d409e4fefd6e9d39");
+
+    lazy_static! {
+        pub static ref NIXHASH_SHA256: NixHash = NixHash::Sha256(DIGEST_SHA256);
+        static ref EXP_MULTI_OUTPUTS: BTreeMap<String, Output> = {
+            let mut b = BTreeMap::new();
+            b.insert(
+                "lib".to_string(),
+                Output {
+                    path: Some(
+                        StorePath::from_bytes(
+                            b"2vixb94v0hy2xc6p7mbnxxcyc095yyia-has-multi-out-lib",
+                        )
+                        .unwrap(),
+                    ),
+                    ca_hash: None,
+                },
+            );
+            b.insert(
+                "out".to_string(),
+                Output {
+                    path: Some(
+                        StorePath::from_bytes(
+                            b"55lwldka5nyxa08wnvlizyqw02ihy8ic-has-multi-out".as_bytes(),
+                        )
+                        .unwrap(),
+                    ),
+                    ca_hash: None,
+                },
+            );
+            b
+        };
+        static ref EXP_AB_MAP: BTreeMap<String, BString> = {
+            let mut b = BTreeMap::new();
+            b.insert("a".to_string(), b"1".as_bstr().to_owned());
+            b.insert("b".to_string(), b"2".as_bstr().to_owned());
+            b
+        };
+        static ref EXP_INPUT_DERIVATIONS_SIMPLE: BTreeMap<StorePath, BTreeSet<String>> = {
+            let mut b = BTreeMap::new();
+            b.insert(
+                StorePath::from_bytes(b"8bjm87p310sb7r2r0sg4xrynlvg86j8k-hello-2.12.1.tar.gz.drv")
+                    .unwrap(),
+                {
+                    let mut output_names = BTreeSet::new();
+                    output_names.insert("out".to_string());
+                    output_names
+                },
+            );
+            b.insert(
+                StorePath::from_bytes(b"p3jc8aw45dza6h52v81j7lk69khckmcj-bash-5.2-p15.drv")
+                    .unwrap(),
+                {
+                    let mut output_names = BTreeSet::new();
+                    output_names.insert("out".to_string());
+                    output_names.insert("lib".to_string());
+                    output_names
+                },
+            );
+            b
+        };
+        static ref EXP_INPUT_DERIVATIONS_SIMPLE_ATERM: String = {
+            format!(
+                "[(\"{0}\",[\"out\"]),(\"{1}\",[\"out\",\"lib\"])]",
+                "/nix/store/8bjm87p310sb7r2r0sg4xrynlvg86j8k-hello-2.12.1.tar.gz.drv",
+                "/nix/store/p3jc8aw45dza6h52v81j7lk69khckmcj-bash-5.2-p15.drv"
+            )
+        };
+        static ref EXP_INPUT_SOURCES_SIMPLE: BTreeSet<String> = {
+            let mut b = BTreeSet::new();
+            b.insert("/nix/store/55lwldka5nyxa08wnvlizyqw02ihy8ic-has-multi-out".to_string());
+            b.insert("/nix/store/2vixb94v0hy2xc6p7mbnxxcyc095yyia-has-multi-out-lib".to_string());
+            b
+        };
+    }
+
+    /// Ensure parsing KVs works
+    #[rstest]
+    #[case::empty(b"[]", &BTreeMap::new(), b"")]
+    #[case::simple(b"[(\"a\",\"1\"),(\"b\",\"2\")]", &EXP_AB_MAP, b"")]
+    fn parse_kv(
+        #[case] input: &'static [u8],
+        #[case] expected: &BTreeMap<String, BString>,
+        #[case] exp_rest: &[u8],
+    ) {
+        let (rest, parsed) = super::parse_kv::<BString, _>(crate::aterm::parse_bstr_field)(input)
+            .expect("must parse");
+        assert_eq!(exp_rest, rest, "expected remainder");
+        assert_eq!(*expected, parsed);
+    }
+
+    /// Ensures the kv parser complains about duplicate map keys
+    #[test]
+    fn parse_kv_fail_dup_keys() {
+        let input: &'static [u8] = b"[(\"a\",\"1\"),(\"a\",\"2\")]";
+        let e = super::parse_kv::<BString, _>(crate::aterm::parse_bstr_field)(input)
+            .expect_err("must fail");
+
+        match e {
+            nom::Err::Failure(e) => {
+                assert_eq!(ErrorKind::DuplicateMapKey("a".to_string()), e.code);
+            }
+            _ => panic!("unexpected error"),
+        }
+    }
+
+    /// Ensure parsing input derivations works.
+    #[rstest]
+    #[case::empty(b"[]", &BTreeMap::new())]
+    #[case::simple(EXP_INPUT_DERIVATIONS_SIMPLE_ATERM.as_bytes(), &EXP_INPUT_DERIVATIONS_SIMPLE)]
+    fn parse_input_derivations(
+        #[case] input: &'static [u8],
+        #[case] expected: &BTreeMap<StorePath, BTreeSet<String>>,
+    ) {
+        let (rest, parsed) = super::parse_input_derivations(input).expect("must parse");
+
+        assert_eq!(expected, &parsed, "parsed mismatch");
+        assert!(rest.is_empty(), "rest must be empty");
+    }
+
+    /// Ensures the input derivation parser complains about duplicate output names
+    #[test]
+    fn parse_input_derivations_fail_dup_output_names() {
+        let input_str = format!(
+            "[(\"{0}\",[\"out\"]),(\"{1}\",[\"out\",\"out\"])]",
+            "/nix/store/8bjm87p310sb7r2r0sg4xrynlvg86j8k-hello-2.12.1.tar.gz.drv",
+            "/nix/store/p3jc8aw45dza6h52v81j7lk69khckmcj-bash-5.2-p15.drv"
+        );
+        let e = super::parse_input_derivations(input_str.as_bytes()).expect_err("must fail");
+
+        match e {
+            nom::Err::Failure(e) => {
+                assert_eq!(
+                    ErrorKind::DuplicateInputDerivationOutputName(
+                        "/nix/store/p3jc8aw45dza6h52v81j7lk69khckmcj-bash-5.2-p15.drv".to_string(),
+                        "out".to_string()
+                    ),
+                    e.code
+                );
+            }
+            _ => panic!("unexpected error"),
+        }
+    }
+
+    /// Ensure parsing input sources works
+    #[rstest]
+    #[case::empty(b"[]", &BTreeSet::new())]
+    #[case::simple(b"[\"/nix/store/55lwldka5nyxa08wnvlizyqw02ihy8ic-has-multi-out\",\"/nix/store/2vixb94v0hy2xc6p7mbnxxcyc095yyia-has-multi-out-lib\"]", &EXP_INPUT_SOURCES_SIMPLE)]
+    fn parse_input_sources(#[case] input: &'static [u8], #[case] expected: &BTreeSet<String>) {
+        let (rest, parsed) = super::parse_input_sources(input).expect("must parse");
+
+        assert_eq!(
+            expected,
+            &parsed
+                .iter()
+                .map(StorePath::to_absolute_path)
+                .collect::<BTreeSet<_>>(),
+            "parsed mismatch"
+        );
+        assert!(rest.is_empty(), "rest must be empty");
+    }
+
+    /// Ensures the input sources parser complains about duplicate input sources
+    #[test]
+    fn parse_input_sources_fail_dup_keys() {
+        let input: &'static [u8] = b"[\"/nix/store/55lwldka5nyxa08wnvlizyqw02ihy8ic-foo\",\"/nix/store/55lwldka5nyxa08wnvlizyqw02ihy8ic-foo\"]";
+        let e = super::parse_input_sources(input).expect_err("must fail");
+
+        match e {
+            nom::Err::Failure(e) => {
+                assert_eq!(
+                    ErrorKind::DuplicateInputSource(
+                        StorePathRef::from_absolute_path(
+                            "/nix/store/55lwldka5nyxa08wnvlizyqw02ihy8ic-foo".as_bytes()
+                        )
+                        .unwrap()
+                        .to_owned()
+                    ),
+                    e.code
+                );
+            }
+            _ => panic!("unexpected error"),
+        }
+    }
+
+    #[rstest]
+    #[case::simple(
+        br#"("out","/nix/store/5vyvcwah9l9kf07d52rcgdk70g2f4y13-foo","","")"#,
+        ("out".to_string(), Output {
+            path: Some(
+                StorePathRef::from_absolute_path("/nix/store/5vyvcwah9l9kf07d52rcgdk70g2f4y13-foo".as_bytes()).unwrap().to_owned()),
+            ca_hash: None
+        })
+    )]
+    #[case::fod(
+        br#"("out","/nix/store/4q0pg5zpfmznxscq3avycvf9xdvx50n3-bar","r:sha256","08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba")"#,
+        ("out".to_string(), Output {
+            path: Some(
+                StorePathRef::from_absolute_path(
+                "/nix/store/4q0pg5zpfmznxscq3avycvf9xdvx50n3-bar".as_bytes()).unwrap().to_owned()),
+            ca_hash: Some(from_algo_and_mode_and_digest("r:sha256",
+                   data_encoding::HEXLOWER.decode(b"08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba").unwrap()            ).unwrap()),
+        })
+     )]
+    fn parse_output(#[case] input: &[u8], #[case] expected: (String, Output)) {
+        let (rest, parsed) = super::parse_output(input).expect("must parse");
+        assert!(rest.is_empty());
+        assert_eq!(expected, parsed);
+    }
+
+    #[rstest]
+    #[case::multi_out(
+        br#"[("lib","/nix/store/2vixb94v0hy2xc6p7mbnxxcyc095yyia-has-multi-out-lib","",""),("out","/nix/store/55lwldka5nyxa08wnvlizyqw02ihy8ic-has-multi-out","","")]"#,
+        &EXP_MULTI_OUTPUTS
+    )]
+    fn parse_outputs(#[case] input: &[u8], #[case] expected: &BTreeMap<String, Output>) {
+        let (rest, parsed) = super::parse_outputs(input).expect("must parse");
+        assert!(rest.is_empty());
+        assert_eq!(*expected, parsed);
+    }
+
+    #[rstest]
+    #[case::sha256_flat("sha256", &DIGEST_SHA256, CAHash::Flat(NIXHASH_SHA256.clone()))]
+    #[case::sha256_recursive("r:sha256", &DIGEST_SHA256, CAHash::Nar(NIXHASH_SHA256.clone()))]
+    fn test_from_algo_and_mode_and_digest(
+        #[case] algo_and_mode: &str,
+        #[case] digest: &[u8],
+        #[case] expected: CAHash,
+    ) {
+        assert_eq!(
+            expected,
+            from_algo_and_mode_and_digest(algo_and_mode, digest).unwrap()
+        );
+    }
+
+    #[test]
+    fn from_algo_and_mode_and_digest_failure() {
+        assert!(from_algo_and_mode_and_digest("r:sha256", []).is_err());
+        assert!(from_algo_and_mode_and_digest("ha256", DIGEST_SHA256).is_err());
+    }
+}
diff --git a/tvix/nix-compat/src/derivation/tests/derivation_tests/duplicate.drv b/tvix/nix-compat/src/derivation/tests/derivation_tests/duplicate.drv
new file mode 100644
index 0000000000..072561a29e
--- /dev/null
+++ b/tvix/nix-compat/src/derivation/tests/derivation_tests/duplicate.drv
@@ -0,0 +1 @@
+Derive([("out","/nix/store/5vyvcwah9l9kf07d52rcgdk70g2f4y13-foo","","")],[("/nix/store/0hm2f1psjpcwg8fijsmr4wwxrx59s092-bar.drv",["out"])],[],":",":",[],[("bar","/nix/store/4q0pg5zpfmznxscq3avycvf9xdvx50n3-bar"),("builder",":"),("name","foo"),("name","bar"),("out","/nix/store/5vyvcwah9l9kf07d52rcgdk70g2f4y13-foo"),("system",":")])
\ No newline at end of file
diff --git a/tvix/nix-compat/src/derivation/tests/derivation_tests/ok/0hm2f1psjpcwg8fijsmr4wwxrx59s092-bar.drv b/tvix/nix-compat/src/derivation/tests/derivation_tests/ok/0hm2f1psjpcwg8fijsmr4wwxrx59s092-bar.drv
new file mode 100644
index 0000000000..a4fea3c5f4
--- /dev/null
+++ b/tvix/nix-compat/src/derivation/tests/derivation_tests/ok/0hm2f1psjpcwg8fijsmr4wwxrx59s092-bar.drv
@@ -0,0 +1 @@
+Derive([("out","/nix/store/4q0pg5zpfmznxscq3avycvf9xdvx50n3-bar","r:sha256","08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba")],[],[],":",":",[],[("builder",":"),("name","bar"),("out","/nix/store/4q0pg5zpfmznxscq3avycvf9xdvx50n3-bar"),("outputHash","08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba"),("outputHashAlgo","sha256"),("outputHashMode","recursive"),("system",":")])
\ No newline at end of file
diff --git a/tvix/nix-compat/src/derivation/tests/derivation_tests/ok/0hm2f1psjpcwg8fijsmr4wwxrx59s092-bar.drv.json b/tvix/nix-compat/src/derivation/tests/derivation_tests/ok/0hm2f1psjpcwg8fijsmr4wwxrx59s092-bar.drv.json
new file mode 100644
index 0000000000..c8bbc4cbb5
--- /dev/null
+++ b/tvix/nix-compat/src/derivation/tests/derivation_tests/ok/0hm2f1psjpcwg8fijsmr4wwxrx59s092-bar.drv.json
@@ -0,0 +1,23 @@
+{
+  "args": [],
+  "builder": ":",
+  "env": {
+    "builder": ":",
+    "name": "bar",
+    "out": "/nix/store/4q0pg5zpfmznxscq3avycvf9xdvx50n3-bar",
+    "outputHash": "08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba",
+    "outputHashAlgo": "sha256",
+    "outputHashMode": "recursive",
+    "system": ":"
+  },
+  "inputDrvs": {},
+  "inputSrcs": [],
+  "outputs": {
+    "out": {
+      "hash": "08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba",
+      "hashAlgo": "r:sha256",
+      "path": "/nix/store/4q0pg5zpfmznxscq3avycvf9xdvx50n3-bar"
+    }
+  },
+  "system": ":"
+}
diff --git a/tvix/nix-compat/src/derivation/tests/derivation_tests/ok/292w8yzv5nn7nhdpxcs8b7vby2p27s09-nested-json.drv b/tvix/nix-compat/src/derivation/tests/derivation_tests/ok/292w8yzv5nn7nhdpxcs8b7vby2p27s09-nested-json.drv
new file mode 100644
index 0000000000..f0d9230a5a
--- /dev/null
+++ b/tvix/nix-compat/src/derivation/tests/derivation_tests/ok/292w8yzv5nn7nhdpxcs8b7vby2p27s09-nested-json.drv
@@ -0,0 +1 @@
+Derive([("out","/nix/store/pzr7lsd3q9pqsnb42r9b23jc5sh8irvn-nested-json","","")],[],[],":",":",[],[("builder",":"),("json","{\"hello\":\"moto\\n\"}"),("name","nested-json"),("out","/nix/store/pzr7lsd3q9pqsnb42r9b23jc5sh8irvn-nested-json"),("system",":")])
\ No newline at end of file
diff --git a/tvix/nix-compat/src/derivation/tests/derivation_tests/ok/292w8yzv5nn7nhdpxcs8b7vby2p27s09-nested-json.drv.json b/tvix/nix-compat/src/derivation/tests/derivation_tests/ok/292w8yzv5nn7nhdpxcs8b7vby2p27s09-nested-json.drv.json
new file mode 100644
index 0000000000..9cb0b43b4c
--- /dev/null
+++ b/tvix/nix-compat/src/derivation/tests/derivation_tests/ok/292w8yzv5nn7nhdpxcs8b7vby2p27s09-nested-json.drv.json
@@ -0,0 +1,19 @@
+{
+  "args": [],
+  "builder": ":",
+  "env": {
+    "builder": ":",
+    "json": "{\"hello\":\"moto\\n\"}",
+    "name": "nested-json",
+    "out": "/nix/store/pzr7lsd3q9pqsnb42r9b23jc5sh8irvn-nested-json",
+    "system": ":"
+  },
+  "inputDrvs": {},
+  "inputSrcs": [],
+  "outputs": {
+    "out": {
+      "path": "/nix/store/pzr7lsd3q9pqsnb42r9b23jc5sh8irvn-nested-json"
+    }
+  },
+  "system": ":"
+}
diff --git a/tvix/nix-compat/src/derivation/tests/derivation_tests/ok/4wvvbi4jwn0prsdxb7vs673qa5h9gr7x-foo.drv b/tvix/nix-compat/src/derivation/tests/derivation_tests/ok/4wvvbi4jwn0prsdxb7vs673qa5h9gr7x-foo.drv
new file mode 100644
index 0000000000..a2cf9d31f9
--- /dev/null
+++ b/tvix/nix-compat/src/derivation/tests/derivation_tests/ok/4wvvbi4jwn0prsdxb7vs673qa5h9gr7x-foo.drv
@@ -0,0 +1 @@
+Derive([("out","/nix/store/5vyvcwah9l9kf07d52rcgdk70g2f4y13-foo","","")],[("/nix/store/0hm2f1psjpcwg8fijsmr4wwxrx59s092-bar.drv",["out"])],[],":",":",[],[("bar","/nix/store/4q0pg5zpfmznxscq3avycvf9xdvx50n3-bar"),("builder",":"),("name","foo"),("out","/nix/store/5vyvcwah9l9kf07d52rcgdk70g2f4y13-foo"),("system",":")])
\ No newline at end of file
diff --git a/tvix/nix-compat/src/derivation/tests/derivation_tests/ok/4wvvbi4jwn0prsdxb7vs673qa5h9gr7x-foo.drv.json b/tvix/nix-compat/src/derivation/tests/derivation_tests/ok/4wvvbi4jwn0prsdxb7vs673qa5h9gr7x-foo.drv.json
new file mode 100644
index 0000000000..957a85ccab
--- /dev/null
+++ b/tvix/nix-compat/src/derivation/tests/derivation_tests/ok/4wvvbi4jwn0prsdxb7vs673qa5h9gr7x-foo.drv.json
@@ -0,0 +1,23 @@
+{
+  "args": [],
+  "builder": ":",
+  "env": {
+    "bar": "/nix/store/4q0pg5zpfmznxscq3avycvf9xdvx50n3-bar",
+    "builder": ":",
+    "name": "foo",
+    "out": "/nix/store/5vyvcwah9l9kf07d52rcgdk70g2f4y13-foo",
+    "system": ":"
+  },
+  "inputDrvs": {
+    "/nix/store/0hm2f1psjpcwg8fijsmr4wwxrx59s092-bar.drv": [
+      "out"
+    ]
+  },
+  "inputSrcs": [],
+  "outputs": {
+    "out": {
+      "path": "/nix/store/5vyvcwah9l9kf07d52rcgdk70g2f4y13-foo"
+    }
+  },
+  "system": ":"
+}
diff --git a/tvix/nix-compat/src/derivation/tests/derivation_tests/ok/52a9id8hx688hvlnz4d1n25ml1jdykz0-unicode.drv b/tvix/nix-compat/src/derivation/tests/derivation_tests/ok/52a9id8hx688hvlnz4d1n25ml1jdykz0-unicode.drv
new file mode 100644
index 0000000000..bbe88c02c7
--- /dev/null
+++ b/tvix/nix-compat/src/derivation/tests/derivation_tests/ok/52a9id8hx688hvlnz4d1n25ml1jdykz0-unicode.drv
@@ -0,0 +1 @@
+Derive([("out","/nix/store/vgvdj6nf7s8kvfbl2skbpwz9kc7xjazc-unicode","","")],[],[],":",":",[],[("builder",":"),("letters","räksmörgås\nrødgrød med fløde\nLübeck\n肥猪\nこんにちは / 今日は\n🌮\n"),("name","unicode"),("out","/nix/store/vgvdj6nf7s8kvfbl2skbpwz9kc7xjazc-unicode"),("system",":")])
\ No newline at end of file
diff --git a/tvix/nix-compat/src/derivation/tests/derivation_tests/ok/52a9id8hx688hvlnz4d1n25ml1jdykz0-unicode.drv.json b/tvix/nix-compat/src/derivation/tests/derivation_tests/ok/52a9id8hx688hvlnz4d1n25ml1jdykz0-unicode.drv.json
new file mode 100644
index 0000000000..f8f33c1bba
--- /dev/null
+++ b/tvix/nix-compat/src/derivation/tests/derivation_tests/ok/52a9id8hx688hvlnz4d1n25ml1jdykz0-unicode.drv.json
@@ -0,0 +1,19 @@
+{
+  "outputs": {
+    "out": {
+      "path": "/nix/store/vgvdj6nf7s8kvfbl2skbpwz9kc7xjazc-unicode"
+    }
+  },
+  "inputSrcs": [],
+  "inputDrvs": {},
+  "system": ":",
+  "builder": ":",
+  "args": [],
+  "env": {
+    "builder": ":",
+    "letters": "räksmörgås\nrødgrød med fløde\nLübeck\n肥猪\nこんにちは / 今日は\n🌮\n",
+    "name": "unicode",
+    "out": "/nix/store/vgvdj6nf7s8kvfbl2skbpwz9kc7xjazc-unicode",
+    "system": ":"
+  }
+}
diff --git a/tvix/nix-compat/src/derivation/tests/derivation_tests/ok/9lj1lkjm2ag622mh4h9rpy6j607an8g2-structured-attrs.drv b/tvix/nix-compat/src/derivation/tests/derivation_tests/ok/9lj1lkjm2ag622mh4h9rpy6j607an8g2-structured-attrs.drv
new file mode 100644
index 0000000000..4b9338c0b9
--- /dev/null
+++ b/tvix/nix-compat/src/derivation/tests/derivation_tests/ok/9lj1lkjm2ag622mh4h9rpy6j607an8g2-structured-attrs.drv
@@ -0,0 +1 @@
+Derive([("out","/nix/store/6a39dl014j57bqka7qx25k0vb20vkqm6-structured-attrs","","")],[],[],":",":",[],[("__json","{\"builder\":\":\",\"name\":\"structured-attrs\",\"system\":\":\"}"),("out","/nix/store/6a39dl014j57bqka7qx25k0vb20vkqm6-structured-attrs")])
\ No newline at end of file
diff --git a/tvix/nix-compat/src/derivation/tests/derivation_tests/ok/9lj1lkjm2ag622mh4h9rpy6j607an8g2-structured-attrs.drv.json b/tvix/nix-compat/src/derivation/tests/derivation_tests/ok/9lj1lkjm2ag622mh4h9rpy6j607an8g2-structured-attrs.drv.json
new file mode 100644
index 0000000000..74e3d7df55
--- /dev/null
+++ b/tvix/nix-compat/src/derivation/tests/derivation_tests/ok/9lj1lkjm2ag622mh4h9rpy6j607an8g2-structured-attrs.drv.json
@@ -0,0 +1,16 @@
+{
+  "args": [],
+  "builder": ":",
+  "env": {
+    "__json": "{\"builder\":\":\",\"name\":\"structured-attrs\",\"system\":\":\"}",
+    "out": "/nix/store/6a39dl014j57bqka7qx25k0vb20vkqm6-structured-attrs"
+  },
+  "inputDrvs": {},
+  "inputSrcs": [],
+  "outputs": {
+    "out": {
+      "path": "/nix/store/6a39dl014j57bqka7qx25k0vb20vkqm6-structured-attrs"
+    }
+  },
+  "system": ":"
+}
diff --git a/tvix/nix-compat/src/derivation/tests/derivation_tests/ok/ch49594n9avinrf8ip0aslidkc4lxkqv-foo.drv b/tvix/nix-compat/src/derivation/tests/derivation_tests/ok/ch49594n9avinrf8ip0aslidkc4lxkqv-foo.drv
new file mode 100644
index 0000000000..1699c2a75e
--- /dev/null
+++ b/tvix/nix-compat/src/derivation/tests/derivation_tests/ok/ch49594n9avinrf8ip0aslidkc4lxkqv-foo.drv
@@ -0,0 +1 @@
+Derive([("out","/nix/store/fhaj6gmwns62s6ypkcldbaj2ybvkhx3p-foo","","")],[("/nix/store/ss2p4wmxijn652haqyd7dckxwl4c7hxx-bar.drv",["out"])],[],":",":",[],[("bar","/nix/store/mp57d33657rf34lzvlbpfa1gjfv5gmpg-bar"),("builder",":"),("name","foo"),("out","/nix/store/fhaj6gmwns62s6ypkcldbaj2ybvkhx3p-foo"),("system",":")])
\ No newline at end of file
diff --git a/tvix/nix-compat/src/derivation/tests/derivation_tests/ok/ch49594n9avinrf8ip0aslidkc4lxkqv-foo.drv.json b/tvix/nix-compat/src/derivation/tests/derivation_tests/ok/ch49594n9avinrf8ip0aslidkc4lxkqv-foo.drv.json
new file mode 100644
index 0000000000..831d27956d
--- /dev/null
+++ b/tvix/nix-compat/src/derivation/tests/derivation_tests/ok/ch49594n9avinrf8ip0aslidkc4lxkqv-foo.drv.json
@@ -0,0 +1,23 @@
+{
+  "args": [],
+  "builder": ":",
+  "env": {
+    "bar": "/nix/store/mp57d33657rf34lzvlbpfa1gjfv5gmpg-bar",
+    "builder": ":",
+    "name": "foo",
+    "out": "/nix/store/fhaj6gmwns62s6ypkcldbaj2ybvkhx3p-foo",
+    "system": ":"
+  },
+  "inputDrvs": {
+    "/nix/store/ss2p4wmxijn652haqyd7dckxwl4c7hxx-bar.drv": [
+      "out"
+    ]
+  },
+  "inputSrcs": [],
+  "outputs": {
+    "out": {
+      "path": "/nix/store/fhaj6gmwns62s6ypkcldbaj2ybvkhx3p-foo"
+    }
+  },
+  "system": ":"
+}
diff --git a/tvix/nix-compat/src/derivation/tests/derivation_tests/ok/h32dahq0bx5rp1krcdx3a53asj21jvhk-has-multi-out.drv b/tvix/nix-compat/src/derivation/tests/derivation_tests/ok/h32dahq0bx5rp1krcdx3a53asj21jvhk-has-multi-out.drv
new file mode 100644
index 0000000000..523612238c
--- /dev/null
+++ b/tvix/nix-compat/src/derivation/tests/derivation_tests/ok/h32dahq0bx5rp1krcdx3a53asj21jvhk-has-multi-out.drv
@@ -0,0 +1 @@
+Derive([("lib","/nix/store/2vixb94v0hy2xc6p7mbnxxcyc095yyia-has-multi-out-lib","",""),("out","/nix/store/55lwldka5nyxa08wnvlizyqw02ihy8ic-has-multi-out","","")],[],[],":",":",[],[("builder",":"),("lib","/nix/store/2vixb94v0hy2xc6p7mbnxxcyc095yyia-has-multi-out-lib"),("name","has-multi-out"),("out","/nix/store/55lwldka5nyxa08wnvlizyqw02ihy8ic-has-multi-out"),("outputs","out lib"),("system",":")])
\ No newline at end of file
diff --git a/tvix/nix-compat/src/derivation/tests/derivation_tests/ok/h32dahq0bx5rp1krcdx3a53asj21jvhk-has-multi-out.drv.json b/tvix/nix-compat/src/derivation/tests/derivation_tests/ok/h32dahq0bx5rp1krcdx3a53asj21jvhk-has-multi-out.drv.json
new file mode 100644
index 0000000000..0bd7a2991c
--- /dev/null
+++ b/tvix/nix-compat/src/derivation/tests/derivation_tests/ok/h32dahq0bx5rp1krcdx3a53asj21jvhk-has-multi-out.drv.json
@@ -0,0 +1,23 @@
+{
+  "args": [],
+  "builder": ":",
+  "env": {
+    "builder": ":",
+    "lib": "/nix/store/2vixb94v0hy2xc6p7mbnxxcyc095yyia-has-multi-out-lib",
+    "name": "has-multi-out",
+    "out": "/nix/store/55lwldka5nyxa08wnvlizyqw02ihy8ic-has-multi-out",
+    "outputs": "out lib",
+    "system": ":"
+  },
+  "inputDrvs": {},
+  "inputSrcs": [],
+  "outputs": {
+    "lib": {
+      "path": "/nix/store/2vixb94v0hy2xc6p7mbnxxcyc095yyia-has-multi-out-lib"
+    },
+    "out": {
+      "path": "/nix/store/55lwldka5nyxa08wnvlizyqw02ihy8ic-has-multi-out"
+    }
+  },
+  "system": ":"
+}
diff --git a/tvix/nix-compat/src/derivation/tests/derivation_tests/ok/m1vfixn8iprlf0v9abmlrz7mjw1xj8kp-cp1252.drv b/tvix/nix-compat/src/derivation/tests/derivation_tests/ok/m1vfixn8iprlf0v9abmlrz7mjw1xj8kp-cp1252.drv
new file mode 100644
index 0000000000..6a7a35c58c
--- /dev/null
+++ b/tvix/nix-compat/src/derivation/tests/derivation_tests/ok/m1vfixn8iprlf0v9abmlrz7mjw1xj8kp-cp1252.drv
@@ -0,0 +1 @@
+Derive([("out","/nix/store/drr2mjp9fp9vvzsf5f9p0a80j33dxy7m-cp1252","","")],[],[],":",":",[],[("builder",":"),("chars",""),("name","cp1252"),("out","/nix/store/drr2mjp9fp9vvzsf5f9p0a80j33dxy7m-cp1252"),("system",":")])
\ No newline at end of file
diff --git a/tvix/nix-compat/src/derivation/tests/derivation_tests/ok/m1vfixn8iprlf0v9abmlrz7mjw1xj8kp-cp1252.drv.json b/tvix/nix-compat/src/derivation/tests/derivation_tests/ok/m1vfixn8iprlf0v9abmlrz7mjw1xj8kp-cp1252.drv.json
new file mode 100644
index 0000000000..9d6ba8b797
--- /dev/null
+++ b/tvix/nix-compat/src/derivation/tests/derivation_tests/ok/m1vfixn8iprlf0v9abmlrz7mjw1xj8kp-cp1252.drv.json
@@ -0,0 +1,21 @@
+{
+  "/nix/store/m1vfixn8iprlf0v9abmlrz7mjw1xj8kp-cp1252.drv": {
+    "outputs": {
+      "out": {
+        "path": "/nix/store/drr2mjp9fp9vvzsf5f9p0a80j33dxy7m-cp1252"
+      }
+    },
+    "inputSrcs": [],
+    "inputDrvs": {},
+    "system": ":",
+    "builder": ":",
+    "args": [],
+    "env": {
+      "builder": ":",
+      "chars": "",
+      "name": "cp1252",
+      "out": "/nix/store/drr2mjp9fp9vvzsf5f9p0a80j33dxy7m-cp1252",
+      "system": ":"
+    }
+  }
+}
diff --git a/tvix/nix-compat/src/derivation/tests/derivation_tests/ok/ss2p4wmxijn652haqyd7dckxwl4c7hxx-bar.drv b/tvix/nix-compat/src/derivation/tests/derivation_tests/ok/ss2p4wmxijn652haqyd7dckxwl4c7hxx-bar.drv
new file mode 100644
index 0000000000..559e93ed0e
--- /dev/null
+++ b/tvix/nix-compat/src/derivation/tests/derivation_tests/ok/ss2p4wmxijn652haqyd7dckxwl4c7hxx-bar.drv
@@ -0,0 +1 @@
+Derive([("out","/nix/store/mp57d33657rf34lzvlbpfa1gjfv5gmpg-bar","r:sha1","0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33")],[],[],":",":",[],[("builder",":"),("name","bar"),("out","/nix/store/mp57d33657rf34lzvlbpfa1gjfv5gmpg-bar"),("outputHash","0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33"),("outputHashAlgo","sha1"),("outputHashMode","recursive"),("system",":")])
\ No newline at end of file
diff --git a/tvix/nix-compat/src/derivation/tests/derivation_tests/ok/ss2p4wmxijn652haqyd7dckxwl4c7hxx-bar.drv.json b/tvix/nix-compat/src/derivation/tests/derivation_tests/ok/ss2p4wmxijn652haqyd7dckxwl4c7hxx-bar.drv.json
new file mode 100644
index 0000000000..e297d27159
--- /dev/null
+++ b/tvix/nix-compat/src/derivation/tests/derivation_tests/ok/ss2p4wmxijn652haqyd7dckxwl4c7hxx-bar.drv.json
@@ -0,0 +1,23 @@
+{
+  "args": [],
+  "builder": ":",
+  "env": {
+    "builder": ":",
+    "name": "bar",
+    "out": "/nix/store/mp57d33657rf34lzvlbpfa1gjfv5gmpg-bar",
+    "outputHash": "0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33",
+    "outputHashAlgo": "sha1",
+    "outputHashMode": "recursive",
+    "system": ":"
+  },
+  "inputDrvs": {},
+  "inputSrcs": [],
+  "outputs": {
+    "out": {
+      "hash": "0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33",
+      "hashAlgo": "r:sha1",
+      "path": "/nix/store/mp57d33657rf34lzvlbpfa1gjfv5gmpg-bar"
+    }
+  },
+  "system": ":"
+}
diff --git a/tvix/nix-compat/src/derivation/tests/derivation_tests/ok/x6p0hg79i3wg0kkv7699935f7rrj9jf3-latin1.drv b/tvix/nix-compat/src/derivation/tests/derivation_tests/ok/x6p0hg79i3wg0kkv7699935f7rrj9jf3-latin1.drv
new file mode 100644
index 0000000000..b19fd8eb2c
--- /dev/null
+++ b/tvix/nix-compat/src/derivation/tests/derivation_tests/ok/x6p0hg79i3wg0kkv7699935f7rrj9jf3-latin1.drv
@@ -0,0 +1 @@
+Derive([("out","/nix/store/x1f6jfq9qgb6i8jrmpifkn9c64fg4hcm-latin1","","")],[],[],":",":",[],[("builder",":"),("chars",""),("name","latin1"),("out","/nix/store/x1f6jfq9qgb6i8jrmpifkn9c64fg4hcm-latin1"),("system",":")])
\ No newline at end of file
diff --git a/tvix/nix-compat/src/derivation/tests/derivation_tests/ok/x6p0hg79i3wg0kkv7699935f7rrj9jf3-latin1.drv.json b/tvix/nix-compat/src/derivation/tests/derivation_tests/ok/x6p0hg79i3wg0kkv7699935f7rrj9jf3-latin1.drv.json
new file mode 100644
index 0000000000..ffd5c08da8
--- /dev/null
+++ b/tvix/nix-compat/src/derivation/tests/derivation_tests/ok/x6p0hg79i3wg0kkv7699935f7rrj9jf3-latin1.drv.json
@@ -0,0 +1,21 @@
+{
+  "/nix/store/x6p0hg79i3wg0kkv7699935f7rrj9jf3-latin1.drv": {
+    "outputs": {
+      "out": {
+        "path": "/nix/store/x1f6jfq9qgb6i8jrmpifkn9c64fg4hcm-latin1"
+      }
+    },
+    "inputSrcs": [],
+    "inputDrvs": {},
+    "system": ":",
+    "builder": ":",
+    "args": [],
+    "env": {
+      "builder": ":",
+      "chars": "",
+      "name": "latin1",
+      "out": "/nix/store/x1f6jfq9qgb6i8jrmpifkn9c64fg4hcm-latin1",
+      "system": ":"
+    }
+  }
+}
diff --git a/tvix/nix-compat/src/derivation/tests/mod.rs b/tvix/nix-compat/src/derivation/tests/mod.rs
new file mode 100644
index 0000000000..48d4e8926a
--- /dev/null
+++ b/tvix/nix-compat/src/derivation/tests/mod.rs
@@ -0,0 +1,436 @@
+use super::parse_error::ErrorKind;
+use crate::derivation::output::Output;
+use crate::derivation::parse_error::NomError;
+use crate::derivation::parser::Error;
+use crate::derivation::Derivation;
+use crate::store_path::StorePath;
+use bstr::{BStr, BString};
+use hex_literal::hex;
+use rstest::rstest;
+use std::collections::BTreeSet;
+use std::fs;
+use std::path::{Path, PathBuf};
+use std::str::FromStr;
+
+const RESOURCES_PATHS: &str = "src/derivation/tests/derivation_tests";
+
+#[rstest]
+fn check_serialization(
+    #[files("src/derivation/tests/derivation_tests/ok/*.drv")]
+    #[exclude("(cp1252)|(latin1)")] // skip JSON files known to fail parsing
+    path_to_drv_file: PathBuf,
+) {
+    let json_bytes =
+        fs::read(path_to_drv_file.with_extension("drv.json")).expect("unable to read JSON");
+    let derivation: Derivation =
+        serde_json::from_slice(&json_bytes).expect("JSON was not well-formatted");
+
+    let mut serialized_derivation = Vec::new();
+    derivation.serialize(&mut serialized_derivation).unwrap();
+
+    let expected = fs::read(&path_to_drv_file).expect("unable to read .drv");
+
+    assert_eq!(expected, BStr::new(&serialized_derivation));
+}
+
+#[rstest]
+fn validate(
+    #[files("src/derivation/tests/derivation_tests/ok/*.drv")]
+    #[exclude("(cp1252)|(latin1)")] // skip JSON files known to fail parsing
+    path_to_drv_file: PathBuf,
+) {
+    let json_bytes =
+        fs::read(path_to_drv_file.with_extension("drv.json")).expect("unable to read JSON");
+    let derivation: Derivation =
+        serde_json::from_slice(&json_bytes).expect("JSON was not well-formatted");
+
+    derivation
+        .validate(true)
+        .expect("derivation failed to validate")
+}
+
+#[rstest]
+fn check_to_aterm_bytes(
+    #[files("src/derivation/tests/derivation_tests/ok/*.drv")]
+    #[exclude("(cp1252)|(latin1)")] // skip JSON files known to fail parsing
+    path_to_drv_file: PathBuf,
+) {
+    let json_bytes =
+        fs::read(path_to_drv_file.with_extension("drv.json")).expect("unable to read JSON");
+    let derivation: Derivation =
+        serde_json::from_slice(&json_bytes).expect("JSON was not well-formatted");
+
+    let expected = fs::read(&path_to_drv_file).expect("unable to read .drv");
+
+    assert_eq!(expected, BStr::new(&derivation.to_aterm_bytes()));
+}
+
+/// Reads in derivations in ATerm representation, parses with that parser,
+/// then compares the structs with the ones obtained by parsing the JSON
+/// representations.
+#[rstest]
+fn from_aterm_bytes(
+    #[files("src/derivation/tests/derivation_tests/ok/*.drv")] path_to_drv_file: PathBuf,
+) {
+    // Read in ATerm representation.
+    let aterm_bytes = fs::read(&path_to_drv_file).expect("unable to read .drv");
+    let parsed_drv = Derivation::from_aterm_bytes(&aterm_bytes).expect("must succeed");
+
+    // For where we're able to load JSON fixtures, parse them and compare the structs.
+    // For where we're not, compare the bytes manually.
+    if path_to_drv_file.file_name().is_some_and(|s| {
+        s.as_encoded_bytes().ends_with(b"cp1252.drv")
+            || s.as_encoded_bytes().ends_with(b"latin1.drv")
+    }) {
+        assert_eq!(
+            &[0xc5, 0xc4, 0xd6][..],
+            parsed_drv.environment.get("chars").unwrap(),
+            "expected bytes to match",
+        );
+    } else {
+        let json_bytes =
+            fs::read(path_to_drv_file.with_extension("drv.json")).expect("unable to read JSON");
+        let fixture_derivation: Derivation =
+            serde_json::from_slice(&json_bytes).expect("JSON was not well-formatted");
+
+        assert_eq!(fixture_derivation, parsed_drv);
+    }
+
+    // Finally, write the ATerm serialization to another buffer, ensuring it's
+    // stable (and we compare all fields we couldn't compare in the non-utf8
+    // derivations)
+
+    assert_eq!(
+        &aterm_bytes,
+        &BString::new(parsed_drv.to_aterm_bytes()),
+        "expected serialized ATerm to match initial input"
+    );
+}
+
+#[test]
+fn from_aterm_bytes_duplicate_map_key() {
+    let buf: Vec<u8> =
+        fs::read(format!("{}/{}", RESOURCES_PATHS, "duplicate.drv")).expect("unable to read .drv");
+
+    let err = Derivation::from_aterm_bytes(&buf).expect_err("must fail");
+
+    match err {
+        Error::Parser(NomError { input: _, code }) => {
+            assert_eq!(code, ErrorKind::DuplicateMapKey("name".to_string()));
+        }
+        _ => {
+            panic!("unexpected error");
+        }
+    }
+}
+
+/// Read in a derivation in ATerm, but add some garbage at the end.
+/// Ensure the parser detects and fails in this case.
+#[test]
+fn from_aterm_bytes_trailer() {
+    let mut buf: Vec<u8> = fs::read(format!(
+        "{}/ok/{}",
+        RESOURCES_PATHS, "0hm2f1psjpcwg8fijsmr4wwxrx59s092-bar.drv"
+    ))
+    .expect("unable to read .drv");
+
+    buf.push(0x00);
+
+    Derivation::from_aterm_bytes(&buf).expect_err("must fail");
+}
+
+#[rstest]
+#[case::fixed_sha256("bar", "0hm2f1psjpcwg8fijsmr4wwxrx59s092-bar.drv")]
+#[case::simple_sha256("foo", "4wvvbi4jwn0prsdxb7vs673qa5h9gr7x-foo.drv")]
+#[case::fixed_sha1("bar", "ss2p4wmxijn652haqyd7dckxwl4c7hxx-bar.drv")]
+#[case::simple_sha1("foo", "ch49594n9avinrf8ip0aslidkc4lxkqv-foo.drv")]
+#[case::multiple_outputs("has-multi-out", "h32dahq0bx5rp1krcdx3a53asj21jvhk-has-multi-out.drv")]
+#[case::structured_attrs(
+    "structured-attrs",
+    "9lj1lkjm2ag622mh4h9rpy6j607an8g2-structured-attrs.drv"
+)]
+#[case::unicode("unicode", "52a9id8hx688hvlnz4d1n25ml1jdykz0-unicode.drv")]
+fn derivation_path(#[case] name: &str, #[case] expected_path: &str) {
+    let json_bytes = fs::read(format!("{}/ok/{}.json", RESOURCES_PATHS, expected_path))
+        .expect("unable to read JSON");
+    let derivation: Derivation =
+        serde_json::from_slice(&json_bytes).expect("JSON was not well-formatted");
+
+    assert_eq!(
+        derivation.calculate_derivation_path(name).unwrap(),
+        StorePath::from_str(expected_path).unwrap()
+    );
+}
+
+/// This trims all output paths from a Derivation struct,
+/// by setting outputs[$outputName].path and environment[$outputName] to the empty string.
+fn derivation_without_output_paths(derivation: &Derivation) -> Derivation {
+    let mut trimmed_env = derivation.environment.clone();
+    let mut trimmed_outputs = derivation.outputs.clone();
+
+    for (output_name, output) in &derivation.outputs {
+        trimmed_env.insert(output_name.clone(), "".into());
+        assert!(trimmed_outputs.contains_key(output_name));
+        trimmed_outputs.insert(
+            output_name.to_string(),
+            Output {
+                path: None,
+                ..output.clone()
+            },
+        );
+    }
+
+    // replace environment and outputs with the trimmed variants
+    Derivation {
+        environment: trimmed_env,
+        outputs: trimmed_outputs,
+        ..derivation.clone()
+    }
+}
+
+#[rstest]
+#[case::fixed_sha256("0hm2f1psjpcwg8fijsmr4wwxrx59s092-bar.drv", hex!("724f3e3634fce4cbbbd3483287b8798588e80280660b9a63fd13a1bc90485b33"))]
+#[case::fixed_sha1("ss2p4wmxijn652haqyd7dckxwl4c7hxx-bar.drv", hex!("c79aebd0ce3269393d4a1fde2cbd1d975d879b40f0bf40a48f550edc107fd5df"))]
+fn hash_derivation_modulo_fixed(#[case] drv_path: &str, #[case] expected_digest: [u8; 32]) {
+    // read in the fixture
+    let json_bytes =
+        fs::read(format!("{}/ok/{}.json", RESOURCES_PATHS, drv_path)).expect("unable to read JSON");
+    let drv: Derivation = serde_json::from_slice(&json_bytes).expect("must deserialize");
+
+    let actual = drv.hash_derivation_modulo(|_| panic!("must not be called"));
+    assert_eq!(expected_digest, actual);
+}
+
+/// This reads a Derivation (in A-Term), trims out all fields containing
+/// calculated output paths, then triggers the output path calculation and
+/// compares the struct to match what was originally read in.
+#[rstest]
+#[case::fixed_sha256("bar", "0hm2f1psjpcwg8fijsmr4wwxrx59s092-bar.drv")]
+#[case::simple_sha256("foo", "4wvvbi4jwn0prsdxb7vs673qa5h9gr7x-foo.drv")]
+#[case::fixed_sha1("bar", "ss2p4wmxijn652haqyd7dckxwl4c7hxx-bar.drv")]
+#[case::simple_sha1("foo", "ch49594n9avinrf8ip0aslidkc4lxkqv-foo.drv")]
+#[case::multiple_outputs("has-multi-out", "h32dahq0bx5rp1krcdx3a53asj21jvhk-has-multi-out.drv")]
+#[case::structured_attrs(
+    "structured-attrs",
+    "9lj1lkjm2ag622mh4h9rpy6j607an8g2-structured-attrs.drv"
+)]
+#[case::unicode("unicode", "52a9id8hx688hvlnz4d1n25ml1jdykz0-unicode.drv")]
+#[case::cp1252("cp1252", "m1vfixn8iprlf0v9abmlrz7mjw1xj8kp-cp1252.drv")]
+#[case::latin1("latin1", "x6p0hg79i3wg0kkv7699935f7rrj9jf3-latin1.drv")]
+fn output_paths(#[case] name: &str, #[case] drv_path_str: &str) {
+    // read in the derivation
+    let expected_derivation = Derivation::from_aterm_bytes(
+        &fs::read(format!("{}/ok/{}", RESOURCES_PATHS, drv_path_str)).expect("unable to read .drv"),
+    )
+    .expect("must succeed");
+
+    // create a version without output paths, simulating we constructed the
+    // struct.
+    let mut derivation = derivation_without_output_paths(&expected_derivation);
+
+    // calculate the hash_derivation_modulo of Derivation
+    // We don't expect the lookup function to be called for most derivations.
+    let actual_hash_derivation_modulo = derivation.hash_derivation_modulo(|parent_drv_path| {
+        // 4wvvbi4jwn0prsdxb7vs673qa5h9gr7x-foo.drv may lookup /nix/store/0hm2f1psjpcwg8fijsmr4wwxrx59s092-bar.drv
+        // ch49594n9avinrf8ip0aslidkc4lxkqv-foo.drv may lookup /nix/store/ss2p4wmxijn652haqyd7dckxwl4c7hxx-bar.drv
+        if name == "foo"
+            && ((drv_path_str == "4wvvbi4jwn0prsdxb7vs673qa5h9gr7x-foo.drv"
+                && parent_drv_path.to_string() == "0hm2f1psjpcwg8fijsmr4wwxrx59s092-bar.drv")
+                || (drv_path_str == "ch49594n9avinrf8ip0aslidkc4lxkqv-foo.drv"
+                    && parent_drv_path.to_string() == "ss2p4wmxijn652haqyd7dckxwl4c7hxx-bar.drv"))
+        {
+            // do the lookup, by reading in the fixture of the requested
+            // drv_name, and calculating its drv replacement (on the non-stripped version)
+            // In a real-world scenario you would have already done this during construction.
+
+            let json_bytes = fs::read(format!(
+                "{}/ok/{}.json",
+                RESOURCES_PATHS,
+                Path::new(&parent_drv_path.to_string())
+                    .file_name()
+                    .unwrap()
+                    .to_string_lossy()
+            ))
+            .expect("unable to read JSON");
+
+            let drv: Derivation = serde_json::from_slice(&json_bytes).expect("must deserialize");
+
+            // calculate hash_derivation_modulo for each parent.
+            // This may not trigger subsequent requests, as both parents are FOD.
+            drv.hash_derivation_modulo(|_| panic!("must not lookup"))
+        } else {
+            // we only expect this to be called in the "foo" testcase, for the "bar derivations"
+            panic!("may only be called for foo testcase on bar derivations");
+        }
+    });
+
+    derivation
+        .calculate_output_paths(name, &actual_hash_derivation_modulo)
+        .unwrap();
+
+    // The derivation should now look like it was before
+    assert_eq!(expected_derivation, derivation);
+}
+
+/// Exercises the output path calculation functions like a constructing client
+/// (an implementation of builtins.derivation) would do:
+///
+/// ```nix
+/// rec {
+///   bar = builtins.derivation {
+///     name = "bar";
+///     builder = ":";
+///     system = ":";
+///     outputHash = "08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba";
+///     outputHashAlgo = "sha256";
+///     outputHashMode = "recursive";
+///   };
+///
+///   foo = builtins.derivation {
+///     name = "foo";
+///     builder = ":";
+///     system = ":";
+///     inherit bar;
+///   };
+/// }
+/// ```
+/// It first assembles the bar derivation, does the output path calculation on
+/// it, then continues with the foo derivation.
+///
+/// The code ensures the resulting Derivations match our fixtures.
+#[test]
+fn output_path_construction() {
+    // create the bar derivation
+    let mut bar_drv = Derivation {
+        builder: ":".to_string(),
+        system: ":".to_string(),
+        ..Default::default()
+    };
+
+    // assemble bar env
+    let bar_env = &mut bar_drv.environment;
+    bar_env.insert("builder".to_string(), ":".into());
+    bar_env.insert("name".to_string(), "bar".into());
+    bar_env.insert("out".to_string(), "".into()); // will be calculated
+    bar_env.insert(
+        "outputHash".to_string(),
+        "08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba".into(),
+    );
+    bar_env.insert("outputHashAlgo".to_string(), "sha256".into());
+    bar_env.insert("outputHashMode".to_string(), "recursive".into());
+    bar_env.insert("system".to_string(), ":".into());
+
+    // assemble bar outputs
+    bar_drv.outputs.insert(
+        "out".to_string(),
+        Output {
+            path: None, // will be calculated
+            ca_hash: Some(crate::nixhash::CAHash::Nar(
+                crate::nixhash::from_algo_and_digest(
+                    crate::nixhash::HashAlgo::Sha256,
+                    &data_encoding::HEXLOWER
+                        .decode(
+                            "08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba"
+                                .as_bytes(),
+                        )
+                        .unwrap(),
+                )
+                .unwrap(),
+            )),
+        },
+    );
+
+    // calculate bar output paths
+    let bar_calc_result = bar_drv.calculate_output_paths(
+        "bar",
+        &bar_drv.hash_derivation_modulo(|_| panic!("is FOD, should not lookup")),
+    );
+    assert!(bar_calc_result.is_ok());
+
+    // ensure it matches our bar fixture
+    let bar_json_bytes = fs::read(format!(
+        "{}/ok/{}.json",
+        RESOURCES_PATHS, "0hm2f1psjpcwg8fijsmr4wwxrx59s092-bar.drv"
+    ))
+    .expect("unable to read JSON");
+    let bar_drv_expected: Derivation =
+        serde_json::from_slice(&bar_json_bytes).expect("must deserialize");
+    assert_eq!(bar_drv_expected, bar_drv);
+
+    // now construct foo, which requires bar_drv
+    // Note how we refer to the output path, drv name and replacement_str (with calculated output paths) of bar.
+    let bar_output_path = &bar_drv.outputs.get("out").expect("must exist").path;
+    let bar_drv_hash_derivation_modulo =
+        bar_drv.hash_derivation_modulo(|_| panic!("is FOD, should not lookup"));
+
+    let bar_drv_path = bar_drv
+        .calculate_derivation_path("bar")
+        .expect("must succeed");
+
+    // create foo derivation
+    let mut foo_drv = Derivation {
+        builder: ":".to_string(),
+        system: ":".to_string(),
+        ..Default::default()
+    };
+
+    // assemble foo env
+    let foo_env = &mut foo_drv.environment;
+    // foo_env.insert("bar".to_string(), StorePathRef:: bar_output_path.to_owned().try_into().unwrap());
+    foo_env.insert(
+        "bar".to_string(),
+        bar_output_path
+            .as_ref()
+            .unwrap()
+            .to_absolute_path()
+            .as_bytes()
+            .into(),
+    );
+    foo_env.insert("builder".to_string(), ":".into());
+    foo_env.insert("name".to_string(), "foo".into());
+    foo_env.insert("out".to_string(), "".into()); // will be calculated
+    foo_env.insert("system".to_string(), ":".into());
+
+    // asssemble foo outputs
+    foo_drv.outputs.insert(
+        "out".to_string(),
+        Output {
+            path: None, // will be calculated
+            ca_hash: None,
+        },
+    );
+
+    // assemble foo input_derivations
+    foo_drv
+        .input_derivations
+        .insert(bar_drv_path, BTreeSet::from(["out".to_string()]));
+
+    // calculate foo output paths
+    let foo_calc_result = foo_drv.calculate_output_paths(
+        "foo",
+        &foo_drv.hash_derivation_modulo(|drv_path| {
+            if drv_path.to_string() != "0hm2f1psjpcwg8fijsmr4wwxrx59s092-bar.drv" {
+                panic!("lookup called with unexpected drv_path: {}", drv_path);
+            }
+            bar_drv_hash_derivation_modulo
+        }),
+    );
+    assert!(foo_calc_result.is_ok());
+
+    // ensure it matches our foo fixture
+    let foo_json_bytes = fs::read(format!(
+        "{}/ok/{}.json",
+        RESOURCES_PATHS, "4wvvbi4jwn0prsdxb7vs673qa5h9gr7x-foo.drv",
+    ))
+    .expect("unable to read JSON");
+    let foo_drv_expected: Derivation =
+        serde_json::from_slice(&foo_json_bytes).expect("must deserialize");
+    assert_eq!(foo_drv_expected, foo_drv);
+
+    assert_eq!(
+        StorePath::from_str("4wvvbi4jwn0prsdxb7vs673qa5h9gr7x-foo.drv").expect("must succeed"),
+        foo_drv
+            .calculate_derivation_path("foo")
+            .expect("must succeed")
+    );
+}
diff --git a/tvix/nix-compat/src/derivation/validate.rs b/tvix/nix-compat/src/derivation/validate.rs
new file mode 100644
index 0000000000..e7b24d84ee
--- /dev/null
+++ b/tvix/nix-compat/src/derivation/validate.rs
@@ -0,0 +1,141 @@
+use crate::derivation::{Derivation, DerivationError};
+use crate::store_path;
+
+impl Derivation {
+    /// validate ensures a Derivation struct is properly populated,
+    /// and returns a [DerivationError] if not.
+    ///
+    /// if `validate_output_paths` is set to false, the output paths are
+    /// excluded from validation.
+    ///
+    /// This is helpful to validate struct population before invoking
+    /// [Derivation::calculate_output_paths].
+    pub fn validate(&self, validate_output_paths: bool) -> Result<(), DerivationError> {
+        // Ensure the number of outputs is > 1
+        if self.outputs.is_empty() {
+            return Err(DerivationError::NoOutputs());
+        }
+
+        // Validate all outputs
+        for (output_name, output) in &self.outputs {
+            // empty output names are invalid.
+            //
+            // `drv` is an invalid output name too, as this would cause
+            // a `builtins.derivation` call to return an attrset with a
+            // `drvPath` key (which already exists) and has a different
+            // meaning.
+            //
+            // Other output names that don't match the name restrictions from
+            // [StorePathRef] will fail the [StorePathRef::validate_name] check.
+            if output_name.is_empty()
+                || output_name == "drv"
+                || store_path::validate_name(output_name.as_bytes()).is_err()
+            {
+                return Err(DerivationError::InvalidOutputName(output_name.to_string()));
+            }
+
+            if output.is_fixed() {
+                if self.outputs.len() != 1 {
+                    return Err(DerivationError::MoreThanOneOutputButFixed());
+                }
+                if output_name != "out" {
+                    return Err(DerivationError::InvalidOutputNameForFixed(
+                        output_name.to_string(),
+                    ));
+                }
+            }
+
+            if let Err(e) = output.validate(validate_output_paths) {
+                return Err(DerivationError::InvalidOutput(output_name.to_string(), e));
+            }
+        }
+
+        // Validate all input_derivations
+        for (input_derivation_path, output_names) in &self.input_derivations {
+            // Validate input_derivation_path
+            if !input_derivation_path.name().ends_with(".drv") {
+                return Err(DerivationError::InvalidInputDerivationPrefix(
+                    input_derivation_path.to_string(),
+                ));
+            }
+
+            if output_names.is_empty() {
+                return Err(DerivationError::EmptyInputDerivationOutputNames(
+                    input_derivation_path.to_string(),
+                ));
+            }
+
+            for output_name in output_names.iter() {
+                // empty output names are invalid.
+                //
+                // `drv` is an invalid output name too, as this would cause
+                // a `builtins.derivation` call to return an attrset with a
+                // `drvPath` key (which already exists) and has a different
+                // meaning.
+                //
+                // Other output names that don't match the name restrictions from
+                // [StorePath] will fail the [StorePathRef::validate_name] check.
+                if output_name.is_empty()
+                    || output_name == "drv"
+                    || store_path::validate_name(output_name.as_bytes()).is_err()
+                {
+                    return Err(DerivationError::InvalidInputDerivationOutputName(
+                        input_derivation_path.to_string(),
+                        output_name.to_string(),
+                    ));
+                }
+            }
+        }
+
+        // validate platform
+        if self.system.is_empty() {
+            return Err(DerivationError::InvalidPlatform(self.system.to_string()));
+        }
+
+        // validate builder
+        if self.builder.is_empty() {
+            return Err(DerivationError::InvalidBuilder(self.builder.to_string()));
+        }
+
+        // validate env, none of the keys may be empty.
+        // We skip the `name` validation seen in go-nix.
+        for k in self.environment.keys() {
+            if k.is_empty() {
+                return Err(DerivationError::InvalidEnvironmentKey(k.to_string()));
+            }
+        }
+
+        Ok(())
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use std::collections::BTreeMap;
+
+    use crate::derivation::{CAHash, Derivation, Output};
+
+    /// Regression test: produce a Derivation that's almost valid, except its
+    /// fixed-output output has the wrong hash specified.
+    #[test]
+    fn output_validate() {
+        let mut outputs = BTreeMap::new();
+        outputs.insert(
+            "out".to_string(),
+            Output {
+                path: None,
+                ca_hash: Some(CAHash::Text([0; 32])), // This is disallowed
+            },
+        );
+
+        let drv = Derivation {
+            arguments: vec![],
+            builder: "/bin/sh".to_string(),
+            outputs,
+            system: "x86_64-linux".to_string(),
+            ..Default::default()
+        };
+
+        drv.validate(false).expect_err("must fail");
+    }
+}
diff --git a/tvix/nix-compat/src/derivation/write.rs b/tvix/nix-compat/src/derivation/write.rs
new file mode 100644
index 0000000000..735b781574
--- /dev/null
+++ b/tvix/nix-compat/src/derivation/write.rs
@@ -0,0 +1,257 @@
+//! This module implements the serialisation of derivations into the
+//! [ATerm][] format used by C++ Nix.
+//!
+//! [ATerm]: http://program-transformation.org/Tools/ATermFormat.html
+
+use crate::aterm::escape_bytes;
+use crate::derivation::{ca_kind_prefix, output::Output};
+use crate::nixbase32;
+use crate::store_path::{StorePath, StorePathRef, STORE_DIR_WITH_SLASH};
+use bstr::BString;
+use data_encoding::HEXLOWER;
+
+use std::{
+    collections::{BTreeMap, BTreeSet},
+    io,
+    io::Error,
+    io::Write,
+};
+
+pub const DERIVATION_PREFIX: &str = "Derive";
+pub const PAREN_OPEN: char = '(';
+pub const PAREN_CLOSE: char = ')';
+pub const BRACKET_OPEN: char = '[';
+pub const BRACKET_CLOSE: char = ']';
+pub const COMMA: char = ',';
+pub const QUOTE: char = '"';
+
+/// Something that can be written as ATerm.
+///
+/// Note that we mostly use explicit `write_*` calls
+/// instead since the serialization of the items depends on
+/// the context a lot.
+pub(crate) trait AtermWriteable {
+    fn aterm_write(&self, writer: &mut impl Write) -> std::io::Result<()>;
+
+    fn aterm_bytes(&self) -> Vec<u8> {
+        let mut bytes = Vec::new();
+        self.aterm_write(&mut bytes)
+            .expect("unexpected write errors to Vec");
+        bytes
+    }
+}
+
+impl AtermWriteable for StorePathRef<'_> {
+    fn aterm_write(&self, writer: &mut impl Write) -> std::io::Result<()> {
+        write_char(writer, QUOTE)?;
+        writer.write_all(STORE_DIR_WITH_SLASH.as_bytes())?;
+        writer.write_all(nixbase32::encode(self.digest()).as_bytes())?;
+        write_char(writer, '-')?;
+        writer.write_all(self.name().as_bytes())?;
+        write_char(writer, QUOTE)?;
+        Ok(())
+    }
+}
+
+impl AtermWriteable for StorePath {
+    fn aterm_write(&self, writer: &mut impl Write) -> std::io::Result<()> {
+        let r: StorePathRef = self.into();
+        r.aterm_write(writer)
+    }
+}
+
+impl AtermWriteable for String {
+    fn aterm_write(&self, writer: &mut impl Write) -> std::io::Result<()> {
+        write_field(writer, self, true)
+    }
+}
+
+impl AtermWriteable for [u8; 32] {
+    fn aterm_write(&self, writer: &mut impl Write) -> std::io::Result<()> {
+        write_field(writer, HEXLOWER.encode(self), false)
+    }
+}
+
+// Writes a character to the writer.
+pub(crate) fn write_char(writer: &mut impl Write, c: char) -> io::Result<()> {
+    let mut buf = [0; 4];
+    let b = c.encode_utf8(&mut buf).as_bytes();
+    writer.write_all(b)
+}
+
+// Write a string `s` as a quoted field to the writer.
+// The `escape` argument controls whether escaping will be skipped.
+// This is the case if `s` is known to only contain characters that need no
+// escaping.
+pub(crate) fn write_field<S: AsRef<[u8]>>(
+    writer: &mut impl Write,
+    s: S,
+    escape: bool,
+) -> io::Result<()> {
+    write_char(writer, QUOTE)?;
+
+    if !escape {
+        writer.write_all(s.as_ref())?;
+    } else {
+        writer.write_all(&escape_bytes(s.as_ref()))?;
+    }
+
+    write_char(writer, QUOTE)?;
+
+    Ok(())
+}
+
+fn write_array_elements<S: AsRef<[u8]>>(
+    writer: &mut impl Write,
+    elements: &[S],
+) -> Result<(), io::Error> {
+    for (index, element) in elements.iter().enumerate() {
+        if index > 0 {
+            write_char(writer, COMMA)?;
+        }
+
+        write_field(writer, element, true)?;
+    }
+
+    Ok(())
+}
+
+pub(crate) fn write_outputs(
+    writer: &mut impl Write,
+    outputs: &BTreeMap<String, Output>,
+) -> Result<(), io::Error> {
+    write_char(writer, BRACKET_OPEN)?;
+    for (ii, (output_name, output)) in outputs.iter().enumerate() {
+        if ii > 0 {
+            write_char(writer, COMMA)?;
+        }
+
+        write_char(writer, PAREN_OPEN)?;
+
+        let path_str = output.path_str();
+        let mut elements: Vec<&str> = vec![output_name, &path_str];
+
+        let (mode_and_algo, digest) = match &output.ca_hash {
+            Some(ca_hash) => (
+                format!("{}{}", ca_kind_prefix(ca_hash), ca_hash.hash().algo()),
+                data_encoding::HEXLOWER.encode(ca_hash.hash().digest_as_bytes()),
+            ),
+            None => ("".to_string(), "".to_string()),
+        };
+
+        elements.push(&mode_and_algo);
+        elements.push(&digest);
+
+        write_array_elements(writer, &elements)?;
+
+        write_char(writer, PAREN_CLOSE)?;
+    }
+    write_char(writer, BRACKET_CLOSE)?;
+
+    Ok(())
+}
+
+pub(crate) fn write_input_derivations(
+    writer: &mut impl Write,
+    input_derivations: &BTreeMap<impl AtermWriteable, BTreeSet<String>>,
+) -> Result<(), io::Error> {
+    write_char(writer, BRACKET_OPEN)?;
+
+    for (ii, (input_derivation_aterm, output_names)) in input_derivations.iter().enumerate() {
+        if ii > 0 {
+            write_char(writer, COMMA)?;
+        }
+
+        write_char(writer, PAREN_OPEN)?;
+        input_derivation_aterm.aterm_write(writer)?;
+        write_char(writer, COMMA)?;
+
+        write_char(writer, BRACKET_OPEN)?;
+        write_array_elements(
+            writer,
+            &output_names
+                .iter()
+                .map(String::as_bytes)
+                .collect::<Vec<_>>(),
+        )?;
+        write_char(writer, BRACKET_CLOSE)?;
+
+        write_char(writer, PAREN_CLOSE)?;
+    }
+
+    write_char(writer, BRACKET_CLOSE)?;
+
+    Ok(())
+}
+
+pub(crate) fn write_input_sources(
+    writer: &mut impl Write,
+    input_sources: &BTreeSet<StorePath>,
+) -> Result<(), io::Error> {
+    write_char(writer, BRACKET_OPEN)?;
+    write_array_elements(
+        writer,
+        &input_sources
+            .iter()
+            .map(StorePath::to_absolute_path)
+            .collect::<Vec<_>>(),
+    )?;
+    write_char(writer, BRACKET_CLOSE)?;
+
+    Ok(())
+}
+
+pub(crate) fn write_system(writer: &mut impl Write, platform: &str) -> Result<(), Error> {
+    write_field(writer, platform, true)?;
+    Ok(())
+}
+
+pub(crate) fn write_builder(writer: &mut impl Write, builder: &str) -> Result<(), Error> {
+    write_field(writer, builder, true)?;
+    Ok(())
+}
+
+pub(crate) fn write_arguments(
+    writer: &mut impl Write,
+    arguments: &[String],
+) -> Result<(), io::Error> {
+    write_char(writer, BRACKET_OPEN)?;
+    write_array_elements(
+        writer,
+        &arguments
+            .iter()
+            .map(|s| s.as_bytes().to_vec().into())
+            .collect::<Vec<BString>>(),
+    )?;
+    write_char(writer, BRACKET_CLOSE)?;
+
+    Ok(())
+}
+
+pub(crate) fn write_environment<E, K, V>(
+    writer: &mut impl Write,
+    environment: E,
+) -> Result<(), io::Error>
+where
+    E: IntoIterator<Item = (K, V)>,
+    K: AsRef<[u8]>,
+    V: AsRef<[u8]>,
+{
+    write_char(writer, BRACKET_OPEN)?;
+
+    for (i, (k, v)) in environment.into_iter().enumerate() {
+        if i > 0 {
+            write_char(writer, COMMA)?;
+        }
+
+        write_char(writer, PAREN_OPEN)?;
+        write_field(writer, k, false)?;
+        write_char(writer, COMMA)?;
+        write_field(writer, v, true)?;
+        write_char(writer, PAREN_CLOSE)?;
+    }
+
+    write_char(writer, BRACKET_CLOSE)?;
+
+    Ok(())
+}
diff --git a/tvix/nix-compat/src/lib.rs b/tvix/nix-compat/src/lib.rs
new file mode 100644
index 0000000000..a71ede3eec
--- /dev/null
+++ b/tvix/nix-compat/src/lib.rs
@@ -0,0 +1,18 @@
+pub(crate) mod aterm;
+pub mod derivation;
+pub mod nar;
+pub mod narinfo;
+pub mod nixbase32;
+pub mod nixhash;
+pub mod path_info;
+pub mod store_path;
+
+#[cfg(feature = "wire")]
+pub mod wire;
+
+#[cfg(feature = "wire")]
+mod nix_daemon;
+#[cfg(feature = "wire")]
+pub use nix_daemon::worker_protocol;
+#[cfg(feature = "wire")]
+pub use nix_daemon::ProtocolVersion;
diff --git a/tvix/nix-compat/src/nar/mod.rs b/tvix/nix-compat/src/nar/mod.rs
new file mode 100644
index 0000000000..058977f4fc
--- /dev/null
+++ b/tvix/nix-compat/src/nar/mod.rs
@@ -0,0 +1,4 @@
+mod wire;
+
+pub mod reader;
+pub mod writer;
diff --git a/tvix/nix-compat/src/nar/reader/async/mod.rs b/tvix/nix-compat/src/nar/reader/async/mod.rs
new file mode 100644
index 0000000000..aaf00faf44
--- /dev/null
+++ b/tvix/nix-compat/src/nar/reader/async/mod.rs
@@ -0,0 +1,166 @@
+use std::{
+    pin::Pin,
+    task::{self, Poll},
+};
+
+use tokio::io::{self, AsyncBufRead, AsyncRead, ErrorKind::InvalidData};
+
+// Required reading for understanding this module.
+use crate::{
+    nar::{self, wire::PadPar},
+    wire::{self, BytesReader},
+};
+
+mod read;
+#[cfg(test)]
+mod test;
+
+pub type Reader<'a> = dyn AsyncBufRead + Unpin + Send + 'a;
+
+/// Start reading a NAR file from `reader`.
+pub async fn open<'a, 'r>(reader: &'a mut Reader<'r>) -> io::Result<Node<'a, 'r>> {
+    read::token(reader, &nar::wire::TOK_NAR).await?;
+    Node::new(reader).await
+}
+
+pub enum Node<'a, 'r: 'a> {
+    Symlink {
+        target: Vec<u8>,
+    },
+    File {
+        executable: bool,
+        reader: FileReader<'a, 'r>,
+    },
+    Directory(DirReader<'a, 'r>),
+}
+
+impl<'a, 'r: 'a> Node<'a, 'r> {
+    /// Start reading a [Node], matching the next [wire::Node].
+    ///
+    /// Reading the terminating [wire::TOK_PAR] is done immediately for [Node::Symlink],
+    /// but is otherwise left to [DirReader] or [BytesReader].
+    async fn new(reader: &'a mut Reader<'r>) -> io::Result<Self> {
+        Ok(match read::tag(reader).await? {
+            nar::wire::Node::Sym => {
+                let target = wire::read_bytes(reader, 1..=nar::wire::MAX_TARGET_LEN).await?;
+
+                if target.contains(&0) {
+                    return Err(InvalidData.into());
+                }
+
+                read::token(reader, &nar::wire::TOK_PAR).await?;
+
+                Node::Symlink { target }
+            }
+            tag @ (nar::wire::Node::Reg | nar::wire::Node::Exe) => Node::File {
+                executable: tag == nar::wire::Node::Exe,
+                reader: FileReader {
+                    inner: BytesReader::new_internal(reader, ..).await?,
+                },
+            },
+            nar::wire::Node::Dir => Node::Directory(DirReader::new(reader)),
+        })
+    }
+}
+
+/// File contents, readable through the [AsyncRead] trait.
+///
+/// It comes with some caveats:
+///  * You must always read the entire file, unless you intend to abandon the entire archive reader.
+///  * You must abandon the entire archive reader upon the first error.
+///
+/// It's fine to read exactly `reader.len()` bytes without ever seeing an explicit EOF.
+pub struct FileReader<'a, 'r> {
+    inner: BytesReader<&'a mut Reader<'r>, PadPar>,
+}
+
+impl<'a, 'r> FileReader<'a, 'r> {
+    pub fn is_empty(&self) -> bool {
+        self.len() == 0
+    }
+
+    pub fn len(&self) -> u64 {
+        self.inner.len()
+    }
+}
+
+impl<'a, 'r> AsyncRead for FileReader<'a, 'r> {
+    fn poll_read(
+        self: Pin<&mut Self>,
+        cx: &mut task::Context,
+        buf: &mut io::ReadBuf,
+    ) -> Poll<io::Result<()>> {
+        Pin::new(&mut self.get_mut().inner).poll_read(cx, buf)
+    }
+}
+
+/// A directory iterator, yielding a sequence of [Node]s.
+/// It must be fully consumed before reading further from the [DirReader] that produced it, if any.
+pub struct DirReader<'a, 'r> {
+    reader: &'a mut Reader<'r>,
+    /// Previous directory entry name.
+    /// We have to hang onto this to enforce name monotonicity.
+    prev_name: Option<Vec<u8>>,
+}
+
+pub struct Entry<'a, 'r> {
+    pub name: Vec<u8>,
+    pub node: Node<'a, 'r>,
+}
+
+impl<'a, 'r> DirReader<'a, 'r> {
+    fn new(reader: &'a mut Reader<'r>) -> Self {
+        Self {
+            reader,
+            prev_name: None,
+        }
+    }
+
+    /// Read the next [Entry] from the directory.
+    ///
+    /// We explicitly don't implement [Iterator], since treating this as
+    /// a regular Rust iterator will surely lead you astray.
+    ///
+    ///  * You must always consume the entire iterator, unless you abandon the entire archive reader.
+    ///  * You must abandon the entire archive reader on the first error.
+    ///  * You must abandon the directory reader upon the first [None].
+    ///  * Even if you know the amount of elements up front, you must keep reading until you encounter [None].
+    pub async fn next(&mut self) -> io::Result<Option<Entry<'_, 'r>>> {
+        // COME FROM the previous iteration: if we've already read an entry,
+        // read its terminating TOK_PAR here.
+        if self.prev_name.is_some() {
+            read::token(self.reader, &nar::wire::TOK_PAR).await?;
+        }
+
+        if let nar::wire::Entry::None = read::tag(self.reader).await? {
+            return Ok(None);
+        }
+
+        let name = wire::read_bytes(self.reader, 1..=nar::wire::MAX_NAME_LEN).await?;
+
+        if name.contains(&0) || name.contains(&b'/') || name == b"." || name == b".." {
+            return Err(InvalidData.into());
+        }
+
+        // Enforce strict monotonicity of directory entry names.
+        match &mut self.prev_name {
+            None => {
+                self.prev_name = Some(name.clone());
+            }
+            Some(prev_name) => {
+                if *prev_name >= name {
+                    return Err(InvalidData.into());
+                }
+
+                name[..].clone_into(prev_name);
+            }
+        }
+
+        read::token(self.reader, &nar::wire::TOK_NOD).await?;
+
+        Ok(Some(Entry {
+            name,
+            node: Node::new(self.reader).await?,
+        }))
+    }
+}
diff --git a/tvix/nix-compat/src/nar/reader/async/read.rs b/tvix/nix-compat/src/nar/reader/async/read.rs
new file mode 100644
index 0000000000..2adf894922
--- /dev/null
+++ b/tvix/nix-compat/src/nar/reader/async/read.rs
@@ -0,0 +1,69 @@
+use tokio::io::{
+    self, AsyncReadExt,
+    ErrorKind::{InvalidData, UnexpectedEof},
+};
+
+use crate::nar::wire::Tag;
+
+use super::Reader;
+
+/// Consume a known token from the reader.
+pub async fn token<const N: usize>(reader: &mut Reader<'_>, token: &[u8; N]) -> io::Result<()> {
+    let mut buf = [0u8; N];
+
+    // This implements something similar to [AsyncReadExt::read_exact], but verifies that
+    // the input data matches the token while we read it. These two slices respectively
+    // represent the remaining token to be verified, and the remaining input buffer.
+    let mut token = &token[..];
+    let mut buf = &mut buf[..];
+
+    while !token.is_empty() {
+        match reader.read(buf).await? {
+            0 => {
+                return Err(UnexpectedEof.into());
+            }
+            n => {
+                let (t, b);
+                (t, token) = token.split_at(n);
+                (b, buf) = buf.split_at_mut(n);
+
+                if t != b {
+                    return Err(InvalidData.into());
+                }
+            }
+        }
+    }
+
+    Ok(())
+}
+
+/// Consume a [Tag] from the reader.
+pub async fn tag<T: Tag>(reader: &mut Reader<'_>) -> io::Result<T> {
+    let mut buf = T::make_buf();
+    let buf = buf.as_mut();
+
+    // first read the known minimum length…
+    reader.read_exact(&mut buf[..T::MIN]).await?;
+
+    // then decide which tag we're expecting
+    let tag = T::from_u8(buf[T::OFF]).ok_or(InvalidData)?;
+    let (head, tail) = tag.as_bytes().split_at(T::MIN);
+
+    // make sure what we've read so far is valid
+    if buf[..T::MIN] != *head {
+        return Err(InvalidData.into());
+    }
+
+    // …then read the rest, if any
+    if !tail.is_empty() {
+        let rest = tail.len();
+        reader.read_exact(&mut buf[..rest]).await?;
+
+        // and make sure it's what we expect
+        if buf[..rest] != *tail {
+            return Err(InvalidData.into());
+        }
+    }
+
+    Ok(tag)
+}
diff --git a/tvix/nix-compat/src/nar/reader/async/test.rs b/tvix/nix-compat/src/nar/reader/async/test.rs
new file mode 100644
index 0000000000..58bb651fca
--- /dev/null
+++ b/tvix/nix-compat/src/nar/reader/async/test.rs
@@ -0,0 +1,310 @@
+use tokio::io::AsyncReadExt;
+
+mod nar {
+    pub use crate::nar::reader::r#async as reader;
+}
+
+#[tokio::test]
+async fn symlink() {
+    let mut f = std::io::Cursor::new(include_bytes!("../../tests/symlink.nar"));
+    let node = nar::reader::open(&mut f).await.unwrap();
+
+    match node {
+        nar::reader::Node::Symlink { target } => {
+            assert_eq!(
+                &b"/nix/store/somewhereelse"[..],
+                &target,
+                "target must match"
+            );
+        }
+        _ => panic!("unexpected type"),
+    }
+}
+
+#[tokio::test]
+async fn file() {
+    let mut f = std::io::Cursor::new(include_bytes!("../../tests/helloworld.nar"));
+    let node = nar::reader::open(&mut f).await.unwrap();
+
+    match node {
+        nar::reader::Node::File {
+            executable,
+            mut reader,
+        } => {
+            assert!(!executable);
+            let mut buf = vec![];
+            reader
+                .read_to_end(&mut buf)
+                .await
+                .expect("read must succeed");
+            assert_eq!(&b"Hello World!"[..], &buf);
+        }
+        _ => panic!("unexpected type"),
+    }
+}
+
+#[tokio::test]
+async fn complicated() {
+    let mut f = std::io::Cursor::new(include_bytes!("../../tests/complicated.nar"));
+    let node = nar::reader::open(&mut f).await.unwrap();
+
+    match node {
+        nar::reader::Node::Directory(mut dir_reader) => {
+            // first entry is .keep, an empty regular file.
+            must_read_file(
+                ".keep",
+                dir_reader
+                    .next()
+                    .await
+                    .expect("next must succeed")
+                    .expect("must be some"),
+            )
+            .await;
+
+            // second entry is aa, a symlink to /nix/store/somewhereelse
+            must_be_symlink(
+                "aa",
+                "/nix/store/somewhereelse",
+                dir_reader
+                    .next()
+                    .await
+                    .expect("next must be some")
+                    .expect("must be some"),
+            );
+
+            {
+                // third entry is a directory called "keep"
+                let entry = dir_reader
+                    .next()
+                    .await
+                    .expect("next must be some")
+                    .expect("must be some");
+
+                assert_eq!(&b"keep"[..], &entry.name);
+
+                match entry.node {
+                    nar::reader::Node::Directory(mut subdir_reader) => {
+                        {
+                            // first entry is .keep, an empty regular file.
+                            let entry = subdir_reader
+                                .next()
+                                .await
+                                .expect("next must succeed")
+                                .expect("must be some");
+
+                            must_read_file(".keep", entry).await;
+                        }
+
+                        // we must read the None
+                        assert!(
+                            subdir_reader
+                                .next()
+                                .await
+                                .expect("next must succeed")
+                                .is_none(),
+                            "keep directory contains only .keep"
+                        );
+                    }
+                    _ => panic!("unexpected type for keep/.keep"),
+                }
+            };
+
+            // reading more entries yields None (and we actually must read until this)
+            assert!(dir_reader.next().await.expect("must succeed").is_none());
+        }
+        _ => panic!("unexpected type"),
+    }
+}
+
+#[tokio::test]
+#[should_panic]
+#[ignore = "TODO: async poisoning"]
+async fn file_read_abandoned() {
+    let mut f = std::io::Cursor::new(include_bytes!("../../tests/complicated.nar"));
+    let node = nar::reader::open(&mut f).await.unwrap();
+
+    match node {
+        nar::reader::Node::Directory(mut dir_reader) => {
+            // first entry is .keep, an empty regular file.
+            {
+                let entry = dir_reader
+                    .next()
+                    .await
+                    .expect("next must succeed")
+                    .expect("must be some");
+
+                assert_eq!(&b".keep"[..], &entry.name);
+                // don't bother to finish reading it.
+            };
+
+            // this should panic (not return an error), because we are meant to abandon the archive reader now.
+            assert!(dir_reader.next().await.expect("must succeed").is_none());
+        }
+        _ => panic!("unexpected type"),
+    }
+}
+
+#[tokio::test]
+#[should_panic]
+#[ignore = "TODO: async poisoning"]
+async fn dir_read_abandoned() {
+    let mut f = std::io::Cursor::new(include_bytes!("../../tests/complicated.nar"));
+    let node = nar::reader::open(&mut f).await.unwrap();
+
+    match node {
+        nar::reader::Node::Directory(mut dir_reader) => {
+            // first entry is .keep, an empty regular file.
+            must_read_file(
+                ".keep",
+                dir_reader
+                    .next()
+                    .await
+                    .expect("next must succeed")
+                    .expect("must be some"),
+            )
+            .await;
+
+            // second entry is aa, a symlink to /nix/store/somewhereelse
+            must_be_symlink(
+                "aa",
+                "/nix/store/somewhereelse",
+                dir_reader
+                    .next()
+                    .await
+                    .expect("next must be some")
+                    .expect("must be some"),
+            );
+
+            {
+                // third entry is a directory called "keep"
+                let entry = dir_reader
+                    .next()
+                    .await
+                    .expect("next must be some")
+                    .expect("must be some");
+
+                assert_eq!(&b"keep"[..], &entry.name);
+
+                match entry.node {
+                    nar::reader::Node::Directory(_) => {
+                        // don't finish using it, which poisons the archive reader
+                    }
+                    _ => panic!("unexpected type for keep/.keep"),
+                }
+            };
+
+            // this should panic, because we didn't finish reading the child subdirectory
+            assert!(dir_reader.next().await.expect("must succeed").is_none());
+        }
+        _ => panic!("unexpected type"),
+    }
+}
+
+#[tokio::test]
+#[should_panic]
+#[ignore = "TODO: async poisoning"]
+async fn dir_read_after_none() {
+    let mut f = std::io::Cursor::new(include_bytes!("../../tests/complicated.nar"));
+    let node = nar::reader::open(&mut f).await.unwrap();
+
+    match node {
+        nar::reader::Node::Directory(mut dir_reader) => {
+            // first entry is .keep, an empty regular file.
+            must_read_file(
+                ".keep",
+                dir_reader
+                    .next()
+                    .await
+                    .expect("next must succeed")
+                    .expect("must be some"),
+            )
+            .await;
+
+            // second entry is aa, a symlink to /nix/store/somewhereelse
+            must_be_symlink(
+                "aa",
+                "/nix/store/somewhereelse",
+                dir_reader
+                    .next()
+                    .await
+                    .expect("next must be some")
+                    .expect("must be some"),
+            );
+
+            {
+                // third entry is a directory called "keep"
+                let entry = dir_reader
+                    .next()
+                    .await
+                    .expect("next must be some")
+                    .expect("must be some");
+
+                assert_eq!(&b"keep"[..], &entry.name);
+
+                match entry.node {
+                    nar::reader::Node::Directory(mut subdir_reader) => {
+                        // first entry is .keep, an empty regular file.
+                        must_read_file(
+                            ".keep",
+                            subdir_reader
+                                .next()
+                                .await
+                                .expect("next must succeed")
+                                .expect("must be some"),
+                        )
+                        .await;
+
+                        // we must read the None
+                        assert!(
+                            subdir_reader
+                                .next()
+                                .await
+                                .expect("next must succeed")
+                                .is_none(),
+                            "keep directory contains only .keep"
+                        );
+                    }
+                    _ => panic!("unexpected type for keep/.keep"),
+                }
+            };
+
+            // reading more entries yields None (and we actually must read until this)
+            assert!(dir_reader.next().await.expect("must succeed").is_none());
+
+            // this should panic, because we already got a none so we're meant to stop.
+            dir_reader.next().await.unwrap();
+            unreachable!()
+        }
+        _ => panic!("unexpected type"),
+    }
+}
+
+async fn must_read_file(name: &'static str, entry: nar::reader::Entry<'_, '_>) {
+    assert_eq!(name.as_bytes(), &entry.name);
+
+    match entry.node {
+        nar::reader::Node::File {
+            executable,
+            mut reader,
+        } => {
+            assert!(!executable);
+            assert_eq!(reader.read(&mut [0]).await.unwrap(), 0);
+        }
+        _ => panic!("unexpected type for {}", name),
+    }
+}
+
+fn must_be_symlink(
+    name: &'static str,
+    exp_target: &'static str,
+    entry: nar::reader::Entry<'_, '_>,
+) {
+    assert_eq!(name.as_bytes(), &entry.name);
+
+    match entry.node {
+        nar::reader::Node::Symlink { target } => {
+            assert_eq!(exp_target.as_bytes(), &target);
+        }
+        _ => panic!("unexpected type for {}", name),
+    }
+}
diff --git a/tvix/nix-compat/src/nar/reader/mod.rs b/tvix/nix-compat/src/nar/reader/mod.rs
new file mode 100644
index 0000000000..bddf175080
--- /dev/null
+++ b/tvix/nix-compat/src/nar/reader/mod.rs
@@ -0,0 +1,482 @@
+//! Parser for the Nix archive format, aka NAR.
+//!
+//! NAR files (and their hashed representations) are used in C++ Nix for
+//! a variety of things, including addressing fixed-output derivations
+//! and transferring store paths between Nix stores.
+
+use std::io::{
+    self, BufRead,
+    ErrorKind::{InvalidData, UnexpectedEof},
+    Read, Write,
+};
+
+#[cfg(not(debug_assertions))]
+use std::marker::PhantomData;
+
+// Required reading for understanding this module.
+use crate::nar::wire;
+
+#[cfg(feature = "async")]
+pub mod r#async;
+
+mod read;
+#[cfg(test)]
+mod test;
+
+pub type Reader<'a> = dyn BufRead + Send + 'a;
+
+struct ArchiveReader<'a, 'r> {
+    inner: &'a mut Reader<'r>,
+
+    /// In debug mode, also track when we need to abandon this archive reader.
+    /// The archive reader must be abandoned when:
+    ///   * An error is encountered at any point
+    ///   * A file or directory reader is dropped before being read entirely.
+    /// All of these checks vanish in release mode.
+    status: ArchiveReaderStatus<'a>,
+}
+
+macro_rules! try_or_poison {
+    ($it:expr, $ex:expr) => {
+        match $ex {
+            Ok(x) => x,
+            Err(e) => {
+                $it.status.poison();
+                return Err(e.into());
+            }
+        }
+    };
+}
+/// Start reading a NAR file from `reader`.
+pub fn open<'a, 'r>(reader: &'a mut Reader<'r>) -> io::Result<Node<'a, 'r>> {
+    read::token(reader, &wire::TOK_NAR)?;
+    Node::new(ArchiveReader {
+        inner: reader,
+        status: ArchiveReaderStatus::top(),
+    })
+}
+
+pub enum Node<'a, 'r> {
+    Symlink {
+        target: Vec<u8>,
+    },
+    File {
+        executable: bool,
+        reader: FileReader<'a, 'r>,
+    },
+    Directory(DirReader<'a, 'r>),
+}
+
+impl<'a, 'r> Node<'a, 'r> {
+    /// Start reading a [Node], matching the next [wire::Node].
+    ///
+    /// Reading the terminating [wire::TOK_PAR] is done immediately for [Node::Symlink],
+    /// but is otherwise left to [DirReader] or [FileReader].
+    fn new(mut reader: ArchiveReader<'a, 'r>) -> io::Result<Self> {
+        Ok(match read::tag(reader.inner)? {
+            wire::Node::Sym => {
+                let target =
+                    try_or_poison!(reader, read::bytes(reader.inner, wire::MAX_TARGET_LEN));
+
+                if target.is_empty() || target.contains(&0) {
+                    reader.status.poison();
+                    return Err(InvalidData.into());
+                }
+
+                try_or_poison!(reader, read::token(reader.inner, &wire::TOK_PAR));
+                reader.status.ready_parent(); // Immediately allow reading from parent again
+
+                Node::Symlink { target }
+            }
+            tag @ (wire::Node::Reg | wire::Node::Exe) => {
+                let len = try_or_poison!(&mut reader, read::u64(reader.inner));
+
+                Node::File {
+                    executable: tag == wire::Node::Exe,
+                    reader: FileReader::new(reader, len)?,
+                }
+            }
+            wire::Node::Dir => Node::Directory(DirReader::new(reader)),
+        })
+    }
+}
+
+/// File contents, readable through the [Read] trait.
+///
+/// It comes with some caveats:
+///  * You must always read the entire file, unless you intend to abandon the entire archive reader.
+///  * You must abandon the entire archive reader upon the first error.
+///
+/// It's fine to read exactly `reader.len()` bytes without ever seeing an explicit EOF.
+pub struct FileReader<'a, 'r> {
+    reader: ArchiveReader<'a, 'r>,
+    len: u64,
+    /// Truncated original file length for padding computation.
+    /// We only care about the 3 least significant bits; semantically, this is a u3.
+    pad: u8,
+}
+
+impl<'a, 'r> FileReader<'a, 'r> {
+    /// Instantiate a new reader, starting after [wire::TOK_REG] or [wire::TOK_EXE].
+    /// We handle the terminating [wire::TOK_PAR] on semantic EOF.
+    fn new(mut reader: ArchiveReader<'a, 'r>, len: u64) -> io::Result<Self> {
+        // For zero-length files, we have to read the terminating TOK_PAR
+        // immediately, since FileReader::read may never be called; we've
+        // already reached semantic EOF by definition.
+        if len == 0 {
+            read::token(reader.inner, &wire::TOK_PAR)?;
+            reader.status.ready_parent();
+        }
+
+        Ok(Self {
+            reader,
+            len,
+            pad: len as u8,
+        })
+    }
+
+    pub fn is_empty(&self) -> bool {
+        self.len == 0
+    }
+
+    pub fn len(&self) -> u64 {
+        self.len
+    }
+}
+
+impl FileReader<'_, '_> {
+    /// Equivalent to [BufRead::fill_buf]
+    ///
+    /// We can't directly implement [BufRead], because [FileReader::consume] needs
+    /// to perform fallible I/O.
+    pub fn fill_buf(&mut self) -> io::Result<&[u8]> {
+        if self.is_empty() {
+            return Ok(&[]);
+        }
+
+        self.reader.check_correct();
+
+        let mut buf = try_or_poison!(self.reader, self.reader.inner.fill_buf());
+
+        if buf.is_empty() {
+            self.reader.status.poison();
+            return Err(UnexpectedEof.into());
+        }
+
+        if buf.len() as u64 > self.len {
+            buf = &buf[..self.len as usize];
+        }
+
+        Ok(buf)
+    }
+
+    /// Analogous to [BufRead::consume], differing only in that it needs
+    /// to perform I/O in order to read padding and terminators.
+    pub fn consume(&mut self, n: usize) -> io::Result<()> {
+        if n == 0 {
+            return Ok(());
+        }
+
+        self.reader.check_correct();
+
+        self.len = self
+            .len
+            .checked_sub(n as u64)
+            .expect("consumed bytes past EOF");
+
+        self.reader.inner.consume(n);
+
+        if self.is_empty() {
+            self.finish()?;
+        }
+
+        Ok(())
+    }
+
+    /// Copy the (remaining) contents of the file into `dst`.
+    pub fn copy(&mut self, mut dst: impl Write) -> io::Result<()> {
+        while !self.is_empty() {
+            let buf = self.fill_buf()?;
+            let n = try_or_poison!(self.reader, dst.write(buf));
+            self.consume(n)?;
+        }
+
+        Ok(())
+    }
+}
+
+impl Read for FileReader<'_, '_> {
+    fn read(&mut self, mut buf: &mut [u8]) -> io::Result<usize> {
+        if buf.is_empty() || self.is_empty() {
+            return Ok(0);
+        }
+
+        self.reader.check_correct();
+
+        if buf.len() as u64 > self.len {
+            buf = &mut buf[..self.len as usize];
+        }
+
+        let n = try_or_poison!(self.reader, self.reader.inner.read(buf));
+        self.len -= n as u64;
+
+        if n == 0 {
+            self.reader.status.poison();
+            return Err(UnexpectedEof.into());
+        }
+
+        if self.is_empty() {
+            self.finish()?;
+        }
+
+        Ok(n)
+    }
+}
+
+impl FileReader<'_, '_> {
+    /// We've reached semantic EOF, consume and verify the padding and terminating TOK_PAR.
+    /// Files are padded to 64 bits (8 bytes), just like any other byte string in the wire format.
+    fn finish(&mut self) -> io::Result<()> {
+        let pad = (self.pad & 7) as usize;
+
+        if pad != 0 {
+            let mut buf = [0; 8];
+            try_or_poison!(self.reader, self.reader.inner.read_exact(&mut buf[pad..]));
+
+            if buf != [0; 8] {
+                self.reader.status.poison();
+                return Err(InvalidData.into());
+            }
+        }
+
+        try_or_poison!(self.reader, read::token(self.reader.inner, &wire::TOK_PAR));
+
+        // Done with reading this file, allow going back up the chain of readers
+        self.reader.status.ready_parent();
+
+        Ok(())
+    }
+}
+
+/// A directory iterator, yielding a sequence of [Node]s.
+/// It must be fully consumed before reading further from the [DirReader] that produced it, if any.
+pub struct DirReader<'a, 'r> {
+    reader: ArchiveReader<'a, 'r>,
+    /// Previous directory entry name.
+    /// We have to hang onto this to enforce name monotonicity.
+    prev_name: Option<Vec<u8>>,
+}
+
+pub struct Entry<'a, 'r> {
+    pub name: Vec<u8>,
+    pub node: Node<'a, 'r>,
+}
+
+impl<'a, 'r> DirReader<'a, 'r> {
+    fn new(reader: ArchiveReader<'a, 'r>) -> Self {
+        Self {
+            reader,
+            prev_name: None,
+        }
+    }
+
+    /// Read the next [Entry] from the directory.
+    ///
+    /// We explicitly don't implement [Iterator], since treating this as
+    /// a regular Rust iterator will surely lead you astray.
+    ///
+    ///  * You must always consume the entire iterator, unless you abandon the entire archive reader.
+    ///  * You must abandon the entire archive reader on the first error.
+    ///  * You must abandon the directory reader upon the first [None].
+    ///  * Even if you know the amount of elements up front, you must keep reading until you encounter [None].
+    #[allow(clippy::should_implement_trait)]
+    pub fn next(&mut self) -> io::Result<Option<Entry<'_, 'r>>> {
+        self.reader.check_correct();
+
+        // COME FROM the previous iteration: if we've already read an entry,
+        // read its terminating TOK_PAR here.
+        if self.prev_name.is_some() {
+            try_or_poison!(self.reader, read::token(self.reader.inner, &wire::TOK_PAR));
+        }
+
+        // Determine if there are more entries to follow
+        if let wire::Entry::None = try_or_poison!(self.reader, read::tag(self.reader.inner)) {
+            // We've reached the end of this directory.
+            self.reader.status.ready_parent();
+            return Ok(None);
+        }
+
+        let name = try_or_poison!(
+            self.reader,
+            read::bytes(self.reader.inner, wire::MAX_NAME_LEN)
+        );
+
+        if name.is_empty()
+            || name.contains(&0)
+            || name.contains(&b'/')
+            || name == b"."
+            || name == b".."
+        {
+            self.reader.status.poison();
+            return Err(InvalidData.into());
+        }
+
+        // Enforce strict monotonicity of directory entry names.
+        match &mut self.prev_name {
+            None => {
+                self.prev_name = Some(name.clone());
+            }
+            Some(prev_name) => {
+                if *prev_name >= name {
+                    self.reader.status.poison();
+                    return Err(InvalidData.into());
+                }
+
+                name[..].clone_into(prev_name);
+            }
+        }
+
+        try_or_poison!(self.reader, read::token(self.reader.inner, &wire::TOK_NOD));
+
+        Ok(Some(Entry {
+            name,
+            // Don't need to worry about poisoning here: Node::new will do it for us if needed
+            node: Node::new(self.reader.child())?,
+        }))
+    }
+}
+
+/// We use a stack of statuses to:
+///   * Share poisoned state across all objects from the same underlying reader,
+///     so we can check they are abandoned when an error occurs
+///   * Make sure only the most recently created object is read from, and is fully exhausted
+///     before anything it was created from is used again.
+enum ArchiveReaderStatus<'a> {
+    #[cfg(not(debug_assertions))]
+    None(PhantomData<&'a ()>),
+    #[cfg(debug_assertions)]
+    StackTop { poisoned: bool, ready: bool },
+    #[cfg(debug_assertions)]
+    StackChild {
+        poisoned: &'a mut bool,
+        parent_ready: &'a mut bool,
+        ready: bool,
+    },
+}
+
+impl ArchiveReaderStatus<'_> {
+    fn top() -> Self {
+        #[cfg(debug_assertions)]
+        {
+            ArchiveReaderStatus::StackTop {
+                poisoned: false,
+                ready: true,
+            }
+        }
+
+        #[cfg(not(debug_assertions))]
+        ArchiveReaderStatus::None(PhantomData)
+    }
+
+    /// Poison all the objects sharing the same reader, to be used when an error occurs
+    fn poison(&mut self) {
+        match self {
+            #[cfg(not(debug_assertions))]
+            ArchiveReaderStatus::None(_) => {}
+            #[cfg(debug_assertions)]
+            ArchiveReaderStatus::StackTop { poisoned: x, .. } => *x = true,
+            #[cfg(debug_assertions)]
+            ArchiveReaderStatus::StackChild { poisoned: x, .. } => **x = true,
+        }
+    }
+
+    /// Mark the parent as ready, allowing it to be used again and preventing this reference to the reader being used again.
+    fn ready_parent(&mut self) {
+        match self {
+            #[cfg(not(debug_assertions))]
+            ArchiveReaderStatus::None(_) => {}
+            #[cfg(debug_assertions)]
+            ArchiveReaderStatus::StackTop { ready, .. } => {
+                *ready = false;
+            }
+            #[cfg(debug_assertions)]
+            ArchiveReaderStatus::StackChild {
+                ready,
+                parent_ready,
+                ..
+            } => {
+                *ready = false;
+                **parent_ready = true;
+            }
+        };
+    }
+
+    fn poisoned(&self) -> bool {
+        match self {
+            #[cfg(not(debug_assertions))]
+            ArchiveReaderStatus::None(_) => false,
+            #[cfg(debug_assertions)]
+            ArchiveReaderStatus::StackTop { poisoned, .. } => *poisoned,
+            #[cfg(debug_assertions)]
+            ArchiveReaderStatus::StackChild { poisoned, .. } => **poisoned,
+        }
+    }
+
+    fn ready(&self) -> bool {
+        match self {
+            #[cfg(not(debug_assertions))]
+            ArchiveReaderStatus::None(_) => true,
+            #[cfg(debug_assertions)]
+            ArchiveReaderStatus::StackTop { ready, .. } => *ready,
+            #[cfg(debug_assertions)]
+            ArchiveReaderStatus::StackChild { ready, .. } => *ready,
+        }
+    }
+}
+
+impl<'a, 'r> ArchiveReader<'a, 'r> {
+    /// Create a new child reader from this one.
+    /// In debug mode, this reader will panic if called before the new child is exhausted / calls `ready_parent`
+    fn child(&mut self) -> ArchiveReader<'_, 'r> {
+        ArchiveReader {
+            inner: self.inner,
+            #[cfg(not(debug_assertions))]
+            status: ArchiveReaderStatus::None(PhantomData),
+            #[cfg(debug_assertions)]
+            status: match &mut self.status {
+                ArchiveReaderStatus::StackTop { poisoned, ready } => {
+                    *ready = false;
+                    ArchiveReaderStatus::StackChild {
+                        poisoned,
+                        parent_ready: ready,
+                        ready: true,
+                    }
+                }
+                ArchiveReaderStatus::StackChild {
+                    poisoned, ready, ..
+                } => {
+                    *ready = false;
+                    ArchiveReaderStatus::StackChild {
+                        poisoned,
+                        parent_ready: ready,
+                        ready: true,
+                    }
+                }
+            },
+        }
+    }
+
+    /// Check the reader is in the correct status.
+    /// Only does anything when debug assertions are on.
+    #[inline(always)]
+    fn check_correct(&self) {
+        assert!(
+            !self.status.poisoned(),
+            "Archive reader used after it was meant to be abandoned!"
+        );
+        assert!(
+            self.status.ready(),
+            "Non-ready archive reader used! (Should've been reading from something else)"
+        );
+    }
+}
diff --git a/tvix/nix-compat/src/nar/reader/read.rs b/tvix/nix-compat/src/nar/reader/read.rs
new file mode 100644
index 0000000000..1ce1613764
--- /dev/null
+++ b/tvix/nix-compat/src/nar/reader/read.rs
@@ -0,0 +1,109 @@
+//! Helpers for reading [crate::nar::wire] format.
+
+use std::io::{
+    self,
+    ErrorKind::{Interrupted, InvalidData, UnexpectedEof},
+};
+
+use super::Reader;
+use crate::nar::wire::Tag;
+
+/// Consume a little-endian [prim@u64] from the reader.
+pub fn u64(reader: &mut Reader) -> io::Result<u64> {
+    let mut buf = [0; 8];
+    reader.read_exact(&mut buf)?;
+    Ok(u64::from_le_bytes(buf))
+}
+
+/// Consume a byte string of up to `max_len` bytes from the reader.
+pub fn bytes(reader: &mut Reader, max_len: usize) -> io::Result<Vec<u8>> {
+    assert!(max_len <= isize::MAX as usize);
+
+    // read the length, and reject excessively large values
+    let len = self::u64(reader)?;
+    if len > max_len as u64 {
+        return Err(InvalidData.into());
+    }
+    // we know the length fits in a usize now
+    let len = len as usize;
+
+    // read the data and padding into a buffer
+    let buf_len = (len + 7) & !7;
+    let mut buf = vec![0; buf_len];
+    reader.read_exact(&mut buf)?;
+
+    // verify that the padding is all zeroes
+    for b in buf.drain(len..) {
+        if b != 0 {
+            return Err(InvalidData.into());
+        }
+    }
+
+    Ok(buf)
+}
+
+/// Consume a known token from the reader.
+pub fn token<const N: usize>(reader: &mut Reader, token: &[u8; N]) -> io::Result<()> {
+    let mut buf = [0u8; N];
+
+    // This implements something similar to [Read::read_exact], but verifies that
+    // the input data matches the token while we read it. These two slices respectively
+    // represent the remaining token to be verified, and the remaining input buffer.
+    let mut token = &token[..];
+    let mut buf = &mut buf[..];
+
+    while !token.is_empty() {
+        match reader.read(buf) {
+            Ok(0) => {
+                return Err(UnexpectedEof.into());
+            }
+            Ok(n) => {
+                let (t, b);
+                (t, token) = token.split_at(n);
+                (b, buf) = buf.split_at_mut(n);
+
+                if t != b {
+                    return Err(InvalidData.into());
+                }
+            }
+            Err(e) => {
+                if e.kind() != Interrupted {
+                    return Err(e);
+                }
+            }
+        }
+    }
+
+    Ok(())
+}
+
+/// Consume a [Tag] from the reader.
+pub fn tag<T: Tag>(reader: &mut Reader) -> io::Result<T> {
+    let mut buf = T::make_buf();
+    let buf = buf.as_mut();
+
+    // first read the known minimum length…
+    reader.read_exact(&mut buf[..T::MIN])?;
+
+    // then decide which tag we're expecting
+    let tag = T::from_u8(buf[T::OFF]).ok_or(InvalidData)?;
+    let (head, tail) = tag.as_bytes().split_at(T::MIN);
+
+    // make sure what we've read so far is valid
+    if buf[..T::MIN] != *head {
+        return Err(InvalidData.into());
+    }
+
+    // …then read the rest, if any
+    if !tail.is_empty() {
+        let rest = tail.len();
+        reader.read_exact(&mut buf[..rest])?;
+
+        // and make sure it's what we expect
+        if buf[..rest] != *tail {
+            return Err(InvalidData.into());
+        }
+    }
+
+    Ok(tag)
+}
diff --git a/tvix/nix-compat/src/nar/reader/test.rs b/tvix/nix-compat/src/nar/reader/test.rs
new file mode 100644
index 0000000000..02dc4767c9
--- /dev/null
+++ b/tvix/nix-compat/src/nar/reader/test.rs
@@ -0,0 +1,278 @@
+use std::io::Read;
+
+use crate::nar;
+
+#[test]
+fn symlink() {
+    let mut f = std::io::Cursor::new(include_bytes!("../tests/symlink.nar"));
+    let node = nar::reader::open(&mut f).unwrap();
+
+    match node {
+        nar::reader::Node::Symlink { target } => {
+            assert_eq!(
+                &b"/nix/store/somewhereelse"[..],
+                &target,
+                "target must match"
+            );
+        }
+        _ => panic!("unexpected type"),
+    }
+}
+
+#[test]
+fn file() {
+    let mut f = std::io::Cursor::new(include_bytes!("../tests/helloworld.nar"));
+    let node = nar::reader::open(&mut f).unwrap();
+
+    match node {
+        nar::reader::Node::File {
+            executable,
+            mut reader,
+        } => {
+            assert!(!executable);
+            let mut buf = vec![];
+            reader.read_to_end(&mut buf).expect("read must succeed");
+            assert_eq!(&b"Hello World!"[..], &buf);
+        }
+        _ => panic!("unexpected type"),
+    }
+}
+
+#[test]
+fn complicated() {
+    let mut f = std::io::Cursor::new(include_bytes!("../tests/complicated.nar"));
+    let node = nar::reader::open(&mut f).unwrap();
+
+    match node {
+        nar::reader::Node::Directory(mut dir_reader) => {
+            // first entry is .keep, an empty regular file.
+            must_read_file(
+                ".keep",
+                dir_reader
+                    .next()
+                    .expect("next must succeed")
+                    .expect("must be some"),
+            );
+
+            // second entry is aa, a symlink to /nix/store/somewhereelse
+            must_be_symlink(
+                "aa",
+                "/nix/store/somewhereelse",
+                dir_reader
+                    .next()
+                    .expect("next must be some")
+                    .expect("must be some"),
+            );
+
+            {
+                // third entry is a directory called "keep"
+                let entry = dir_reader
+                    .next()
+                    .expect("next must be some")
+                    .expect("must be some");
+
+                assert_eq!(&b"keep"[..], &entry.name);
+
+                match entry.node {
+                    nar::reader::Node::Directory(mut subdir_reader) => {
+                        {
+                            // first entry is .keep, an empty regular file.
+                            let entry = subdir_reader
+                                .next()
+                                .expect("next must succeed")
+                                .expect("must be some");
+
+                            must_read_file(".keep", entry);
+                        }
+
+                        // we must read the None
+                        assert!(
+                            subdir_reader.next().expect("next must succeed").is_none(),
+                            "keep directory contains only .keep"
+                        );
+                    }
+                    _ => panic!("unexpected type for keep/.keep"),
+                }
+            };
+
+            // reading more entries yields None (and we actually must read until this)
+            assert!(dir_reader.next().expect("must succeed").is_none());
+        }
+        _ => panic!("unexpected type"),
+    }
+}
+
+#[test]
+#[should_panic]
+fn file_read_abandoned() {
+    let mut f = std::io::Cursor::new(include_bytes!("../tests/complicated.nar"));
+    let node = nar::reader::open(&mut f).unwrap();
+
+    match node {
+        nar::reader::Node::Directory(mut dir_reader) => {
+            // first entry is .keep, an empty regular file.
+            {
+                let entry = dir_reader
+                    .next()
+                    .expect("next must succeed")
+                    .expect("must be some");
+
+                assert_eq!(&b".keep"[..], &entry.name);
+                // don't bother to finish reading it.
+            };
+
+            // this should panic (not return an error), because we are meant to abandon the archive reader now.
+            assert!(dir_reader.next().expect("must succeed").is_none());
+        }
+        _ => panic!("unexpected type"),
+    }
+}
+
+#[test]
+#[should_panic]
+fn dir_read_abandoned() {
+    let mut f = std::io::Cursor::new(include_bytes!("../tests/complicated.nar"));
+    let node = nar::reader::open(&mut f).unwrap();
+
+    match node {
+        nar::reader::Node::Directory(mut dir_reader) => {
+            // first entry is .keep, an empty regular file.
+            must_read_file(
+                ".keep",
+                dir_reader
+                    .next()
+                    .expect("next must succeed")
+                    .expect("must be some"),
+            );
+
+            // second entry is aa, a symlink to /nix/store/somewhereelse
+            must_be_symlink(
+                "aa",
+                "/nix/store/somewhereelse",
+                dir_reader
+                    .next()
+                    .expect("next must be some")
+                    .expect("must be some"),
+            );
+
+            {
+                // third entry is a directory called "keep"
+                let entry = dir_reader
+                    .next()
+                    .expect("next must be some")
+                    .expect("must be some");
+
+                assert_eq!(&b"keep"[..], &entry.name);
+
+                match entry.node {
+                    nar::reader::Node::Directory(_) => {
+                        // don't finish using it, which poisons the archive reader
+                    }
+                    _ => panic!("unexpected type for keep/.keep"),
+                }
+            };
+
+            // this should panic, because we didn't finish reading the child subdirectory
+            assert!(dir_reader.next().expect("must succeed").is_none());
+        }
+        _ => panic!("unexpected type"),
+    }
+}
+
+#[test]
+#[should_panic]
+fn dir_read_after_none() {
+    let mut f = std::io::Cursor::new(include_bytes!("../tests/complicated.nar"));
+    let node = nar::reader::open(&mut f).unwrap();
+
+    match node {
+        nar::reader::Node::Directory(mut dir_reader) => {
+            // first entry is .keep, an empty regular file.
+            must_read_file(
+                ".keep",
+                dir_reader
+                    .next()
+                    .expect("next must succeed")
+                    .expect("must be some"),
+            );
+
+            // second entry is aa, a symlink to /nix/store/somewhereelse
+            must_be_symlink(
+                "aa",
+                "/nix/store/somewhereelse",
+                dir_reader
+                    .next()
+                    .expect("next must be some")
+                    .expect("must be some"),
+            );
+
+            {
+                // third entry is a directory called "keep"
+                let entry = dir_reader
+                    .next()
+                    .expect("next must be some")
+                    .expect("must be some");
+
+                assert_eq!(&b"keep"[..], &entry.name);
+
+                match entry.node {
+                    nar::reader::Node::Directory(mut subdir_reader) => {
+                        // first entry is .keep, an empty regular file.
+                        must_read_file(
+                            ".keep",
+                            subdir_reader
+                                .next()
+                                .expect("next must succeed")
+                                .expect("must be some"),
+                        );
+
+                        // we must read the None
+                        assert!(
+                            subdir_reader.next().expect("next must succeed").is_none(),
+                            "keep directory contains only .keep"
+                        );
+                    }
+                    _ => panic!("unexpected type for keep/.keep"),
+                }
+            };
+
+            // reading more entries yields None (and we actually must read until this)
+            assert!(dir_reader.next().expect("must succeed").is_none());
+
+            // this should panic, because we already got a none so we're meant to stop.
+            dir_reader.next().unwrap();
+            unreachable!()
+        }
+        _ => panic!("unexpected type"),
+    }
+}
+
+fn must_read_file(name: &'static str, entry: nar::reader::Entry<'_, '_>) {
+    assert_eq!(name.as_bytes(), &entry.name);
+
+    match entry.node {
+        nar::reader::Node::File {
+            executable,
+            mut reader,
+        } => {
+            assert!(!executable);
+            assert_eq!(reader.read(&mut [0]).unwrap(), 0);
+        }
+        _ => panic!("unexpected type for {}", name),
+    }
+}
+
+fn must_be_symlink(
+    name: &'static str,
+    exp_target: &'static str,
+    entry: nar::reader::Entry<'_, '_>,
+) {
+    assert_eq!(name.as_bytes(), &entry.name);
+
+    match entry.node {
+        nar::reader::Node::Symlink { target } => {
+            assert_eq!(exp_target.as_bytes(), &target);
+        }
+        _ => panic!("unexpected type for {}", name),
+    }
+}
diff --git a/tvix/nix-compat/src/nar/tests/complicated.nar b/tvix/nix-compat/src/nar/tests/complicated.nar
new file mode 100644
index 0000000000..6a137f5fbb
--- /dev/null
+++ b/tvix/nix-compat/src/nar/tests/complicated.nar
Binary files differdiff --git a/tvix/nix-compat/src/nar/tests/helloworld.nar b/tvix/nix-compat/src/nar/tests/helloworld.nar
new file mode 100644
index 0000000000..2e12681152
--- /dev/null
+++ b/tvix/nix-compat/src/nar/tests/helloworld.nar
Binary files differdiff --git a/tvix/nix-compat/src/nar/tests/symlink.nar b/tvix/nix-compat/src/nar/tests/symlink.nar
new file mode 100644
index 0000000000..7990e4ad5b
--- /dev/null
+++ b/tvix/nix-compat/src/nar/tests/symlink.nar
Binary files differdiff --git a/tvix/nix-compat/src/nar/wire/mod.rs b/tvix/nix-compat/src/nar/wire/mod.rs
new file mode 100644
index 0000000000..9e99b530ce
--- /dev/null
+++ b/tvix/nix-compat/src/nar/wire/mod.rs
@@ -0,0 +1,150 @@
+//! NAR wire format, without I/O details, since those differ between
+//! the synchronous and asynchronous implementations.
+//!
+//! The wire format is an S-expression format, encoded onto the wire
+//! using simple encoding rules.
+//!
+//! # Encoding
+//!
+//! Lengths are represented as 64-bit unsigned integers in little-endian
+//! format. Byte strings, including file contents and syntactic strings
+//! part of the grammar, are prefixed by their 64-bit length, and padded
+//! to 8-byte (64-bit) alignment with zero bytes. The zero-length string
+//! is therefore encoded as eight zero bytes representing its length.
+//!
+//! # Grammar
+//!
+//! The NAR grammar is as follows:
+//! ```plain
+//! archive ::= "nix-archive-1" node
+//!
+//! node ::= "(" "type" "symlink" "target" string ")"
+//!      ||= "(" "type" "regular" ("executable" "")? "contents" string ")"
+//!      ||= "(" "type" "directory" entry* ")"
+//!
+//! entry ::= "entry" "(" "name" string "node" node ")"
+//! ```
+//!
+//! We rewrite it to pull together the purely syntactic elements into
+//! unified tokens, producing an equivalent grammar that can be parsed
+//! and serialized more elegantly:
+//! ```plain
+//! archive ::= TOK_NAR node
+//! node ::= TOK_SYM string             TOK_PAR
+//!      ||= (TOK_REG | TOK_EXE) string TOK_PAR
+//!      ||= TOK_DIR entry*             TOK_PAR
+//!
+//! entry ::= TOK_ENT string TOK_NOD node TOK_PAR
+//!
+//! TOK_NAR ::= "nix-archive-1" "(" "type"
+//! TOK_SYM ::= "symlink" "target"
+//! TOK_REG ::= "regular" "contents"
+//! TOK_EXE ::= "regular" "executable" ""
+//! TOK_DIR ::= "directory"
+//! TOK_ENT ::= "entry" "(" "name"
+//! TOK_NOD ::= "node" "(" "type"
+//! TOK_PAR ::= ")"
+//! ```
+//!
+//! # Restrictions
+//!
+//! NOTE: These restrictions are not (and cannot be) enforced by this module,
+//! but must be enforced by its consumers, [super::reader] and [super::writer].
+//!
+//! Directory entry names cannot have the reserved names `.` and `..`, nor contain
+//! forward slashes. They must appear in strictly ascending lexicographic order
+//! within a directory, and can be at most [MAX_NAME_LEN] bytes in length.
+//!
+//! Symlink targets can be at most [MAX_TARGET_LEN] bytes in length.
+//!
+//! Neither is permitted to be empty, or contain null bytes.
+
+// These values are the standard Linux length limits
+/// Maximum length of a directory entry name
+pub const MAX_NAME_LEN: usize = 255;
+/// Maximum length of a symlink target
+pub const MAX_TARGET_LEN: usize = 4095;
+
+#[cfg(test)]
+fn token(xs: &[&str]) -> Vec<u8> {
+    let mut out = vec![];
+    for x in xs {
+        let len = x.len() as u64;
+        out.extend_from_slice(&len.to_le_bytes());
+        out.extend_from_slice(x.as_bytes());
+
+        let n = x.len() & 7;
+        if n != 0 {
+            const ZERO: [u8; 8] = [0; 8];
+            out.extend_from_slice(&ZERO[n..]);
+        }
+    }
+    out
+}
+
+pub const TOK_NAR: [u8; 56] = *b"\x0d\0\0\0\0\0\0\0nix-archive-1\0\0\0\x01\0\0\0\0\0\0\0(\0\0\0\0\0\0\0\x04\0\0\0\0\0\0\0type\0\0\0\0";
+pub const TOK_SYM: [u8; 32] = *b"\x07\0\0\0\0\0\0\0symlink\0\x06\0\0\0\0\0\0\0target\0\0";
+pub const TOK_REG: [u8; 32] = *b"\x07\0\0\0\0\0\0\0regular\0\x08\0\0\0\0\0\0\0contents";
+pub const TOK_EXE: [u8; 64] = *b"\x07\0\0\0\0\0\0\0regular\0\x0a\0\0\0\0\0\0\0executable\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x08\0\0\0\0\0\0\0contents";
+pub const TOK_DIR: [u8; 24] = *b"\x09\0\0\0\0\0\0\0directory\0\0\0\0\0\0\0";
+pub const TOK_ENT: [u8; 48] = *b"\x05\0\0\0\0\0\0\0entry\0\0\0\x01\0\0\0\0\0\0\0(\0\0\0\0\0\0\0\x04\0\0\0\0\0\0\0name\0\0\0\0";
+pub const TOK_NOD: [u8; 48] = *b"\x04\0\0\0\0\0\0\0node\0\0\0\0\x01\0\0\0\0\0\0\0(\0\0\0\0\0\0\0\x04\0\0\0\0\0\0\0type\0\0\0\0";
+pub const TOK_PAR: [u8; 16] = *b"\x01\0\0\0\0\0\0\0)\0\0\0\0\0\0\0";
+#[cfg(feature = "async")]
+const TOK_PAD_PAR: [u8; 24] = *b"\0\0\0\0\0\0\0\0\x01\0\0\0\0\0\0\0)\0\0\0\0\0\0\0";
+
+#[cfg(feature = "async")]
+#[derive(Debug)]
+pub(crate) enum PadPar {}
+
+#[cfg(feature = "async")]
+impl crate::wire::reader::Tag for PadPar {
+    const PATTERN: &'static [u8] = &TOK_PAD_PAR;
+
+    type Buf = [u8; 24];
+
+    fn make_buf() -> Self::Buf {
+        [0; 24]
+    }
+}
+
+#[test]
+fn tokens() {
+    let cases: &[(&[u8], &[&str])] = &[
+        (&TOK_NAR, &["nix-archive-1", "(", "type"]),
+        (&TOK_SYM, &["symlink", "target"]),
+        (&TOK_REG, &["regular", "contents"]),
+        (&TOK_EXE, &["regular", "executable", "", "contents"]),
+        (&TOK_DIR, &["directory"]),
+        (&TOK_ENT, &["entry", "(", "name"]),
+        (&TOK_NOD, &["node", "(", "type"]),
+        (&TOK_PAR, &[")"]),
+    ];
+
+    for &(tok, xs) in cases {
+        assert_eq!(tok, token(xs));
+    }
+}
+
+pub use tag::Tag;
+mod tag;
+
+tag::make! {
+    /// These are the node tokens, succeeding [TOK_NAR] or [TOK_NOD],
+    /// and preceding the next variable-length element.
+    pub enum Node[16] {
+        Sym = TOK_SYM,
+        Reg = TOK_REG,
+        Exe = TOK_EXE,
+        Dir = TOK_DIR,
+    }
+
+    /// Directory entry or terminator
+    pub enum Entry[0] {
+        /// End of directory
+        None = TOK_PAR,
+        /// Directory entry
+        /// Followed by a name string, [TOK_NOD], and a [Node].
+        Some = TOK_ENT,
+    }
+}
diff --git a/tvix/nix-compat/src/nar/wire/tag.rs b/tvix/nix-compat/src/nar/wire/tag.rs
new file mode 100644
index 0000000000..4982a0d707
--- /dev/null
+++ b/tvix/nix-compat/src/nar/wire/tag.rs
@@ -0,0 +1,166 @@
+/// A type implementing Tag represents a static hash set of byte strings,
+/// with a very simple perfect hash function: every element has a unique
+/// discriminant at a common byte offset. The values of the type represent
+/// the members by this single discriminant byte; they are indices into the
+/// hash set.
+pub trait Tag: Sized {
+    /// Discriminant offset
+    const OFF: usize;
+    /// Minimum variant length
+    const MIN: usize;
+
+    /// Minimal suitably sized buffer for reading the wire representation
+    ///
+    /// HACK: This is a workaround for const generics limitations.
+    type Buf: AsMut<[u8]> + Send;
+
+    /// Make an instance of [Self::Buf]
+    fn make_buf() -> Self::Buf;
+
+    /// Convert a discriminant into the corresponding variant
+    fn from_u8(x: u8) -> Option<Self>;
+
+    /// Convert a variant back into the wire representation
+    fn as_bytes(&self) -> &'static [u8];
+}
+
+/// Generate an enum implementing [Tag], enforcing at compile time that
+/// the discriminant values are distinct.
+macro_rules! make {
+    (
+        $(
+            $(#[doc = $doc:expr])*
+            $vis:vis enum $Enum:ident[$off:expr] {
+                $(
+                    $(#[doc = $var_doc:expr])*
+                    $Var:ident = $TOK:ident,
+                )+
+            }
+        )*
+    ) => {
+        $(
+            $(#[doc = $doc])*
+            #[derive(Debug, PartialEq, Eq)]
+            #[repr(u8)]
+            $vis enum $Enum {
+                $(
+                    $(#[doc = $var_doc])*
+                    $Var = $TOK[$Enum::OFF]
+                ),+
+            }
+
+            impl Tag for $Enum {
+                /// Discriminant offset
+                const OFF: usize = $off;
+                /// Minimum variant length
+                const MIN: usize = tag::min_of(&[$($TOK.len()),+]);
+
+                /// Minimal suitably sized buffer for reading the wire representation
+                type Buf = [u8; tag::buf_of(&[$($TOK.len()),+])];
+
+                /// Make an instance of [Self::Buf]
+                #[inline(always)]
+                fn make_buf() -> Self::Buf {
+                    [0u8; tag::buf_of(&[$($TOK.len()),+])]
+                }
+
+                /// Convert a discriminant into the corresponding variant
+                #[inline(always)]
+                fn from_u8(x: u8) -> Option<Self> {
+                    #[allow(non_upper_case_globals)]
+                    mod __variant {
+                        $(
+                            pub const $Var: u8 = super::$Enum::$Var as u8;
+                        )+
+                    }
+
+                    match x {
+                        $(__variant::$Var => Some(Self::$Var),)+
+                        _ => None
+                    }
+                }
+
+                /// Convert a variant back into the wire representation
+                #[inline(always)]
+                fn as_bytes(&self) -> &'static [u8] {
+                    match self {
+                        $(Self::$Var => &$TOK,)+
+                    }
+                }
+            }
+        )*
+    };
+}
+
+// The following functions are written somewhat unusually,
+// since they're const functions that cannot use iterators.
+
+/// Maximum element of a slice
+const fn max_of(mut xs: &[usize]) -> usize {
+    let mut y = usize::MIN;
+    while let &[x, ref tail @ ..] = xs {
+        y = if x > y { x } else { y };
+        xs = tail;
+    }
+    y
+}
+
+/// Minimum element of a slice
+pub const fn min_of(mut xs: &[usize]) -> usize {
+    let mut y = usize::MAX;
+    while let &[x, ref tail @ ..] = xs {
+        y = if x < y { x } else { y };
+        xs = tail;
+    }
+    y
+}
+
+/// Minimum buffer size to contain either of `0..Tag::MIN` and `Tag::MIN..`
+/// at a particular time, for all possible tag wire representations, given
+/// the sizes of all wire representations.
+///
+/// # Example
+///
+/// ```plain
+/// OFF = 16
+/// MIN = 24
+/// MAX = 64
+///
+/// BUF = max(MIN, MAX-MIN)
+///     = max(24, 64-24)
+///     = max(24, 40)
+///     = 40
+/// ```
+pub const fn buf_of(xs: &[usize]) -> usize {
+    max_of(&[min_of(xs), max_of(xs) - min_of(xs)])
+}
+
+pub(crate) use make;
+
+#[cfg(test)]
+mod test {
+    use super::super::tag::{self, Tag};
+
+    const TOK_A: [u8; 3] = [0xed, 0xef, 0x1c];
+    const TOK_B: [u8; 3] = [0xed, 0xf0, 0x1c];
+
+    const OFFSET: usize = 1;
+
+    make! {
+        enum Token[OFFSET] {
+            A = TOK_A,
+            B = TOK_B,
+        }
+    }
+
+    #[test]
+    fn example() {
+        assert_eq!(Token::from_u8(0xed), None);
+
+        let tag = Token::from_u8(0xef).unwrap();
+        assert_eq!(tag.as_bytes(), &TOK_A[..]);
+
+        let tag = Token::from_u8(0xf0).unwrap();
+        assert_eq!(tag.as_bytes(), &TOK_B[..]);
+    }
+}
diff --git a/tvix/nix-compat/src/nar/writer/async.rs b/tvix/nix-compat/src/nar/writer/async.rs
new file mode 100644
index 0000000000..a2ce68fc3c
--- /dev/null
+++ b/tvix/nix-compat/src/nar/writer/async.rs
@@ -0,0 +1,235 @@
+//! Implements an interface for writing the Nix archive format (NAR).
+//!
+//! NAR files (and their hashed representations) are used in C++ Nix for
+//! addressing fixed-output derivations and a variety of other things.
+//!
+//! NAR files can be output to any type that implements [`AsyncWrite`], and content
+//! can be read from any type that implementes [`AsyncBufRead`].
+//!
+//! Writing a single file might look like this:
+//!
+//! ```rust
+//! # futures::executor::block_on(async {
+//! # use tokio::io::BufReader;
+//! # let some_file: Vec<u8> = vec![0, 1, 2, 3, 4];
+//!
+//! // Output location to write the NAR to.
+//! let mut sink: Vec<u8> = Vec::new();
+//!
+//! // Instantiate writer for this output location.
+//! let mut nar = nix_compat::nar::writer::r#async::open(&mut sink).await?;
+//!
+//! // Acquire metadata for the single file to output, and pass it in a
+//! // `BufRead`-implementing type.
+//!
+//! let executable = false;
+//! let size = some_file.len() as u64;
+//! let mut reader = BufReader::new(some_file.as_slice());
+//! nar.file(executable, size, &mut reader).await?;
+//! # Ok::<(), std::io::Error>(())
+//! # });
+//! ```
+
+use crate::nar::wire;
+use std::{
+    io::{
+        self,
+        ErrorKind::{InvalidInput, UnexpectedEof},
+    },
+    pin::Pin,
+};
+use tokio::io::{AsyncBufRead, AsyncBufReadExt, AsyncWrite, AsyncWriteExt};
+
+/// Convenience type alias for types implementing [`AsyncWrite`].
+pub type Writer<'a> = dyn AsyncWrite + Unpin + Send + 'a;
+
+/// Create a new NAR, writing the output to the specified writer.
+pub async fn open<'a, 'w: 'a>(writer: &'a mut Writer<'w>) -> io::Result<Node<'a, 'w>> {
+    let mut node = Node { writer };
+    node.write(&wire::TOK_NAR).await?;
+    Ok(node)
+}
+
+/// Single node in a NAR file.
+///
+/// A NAR can be thought of as a tree of nodes represented by this type. Each
+/// node can be a file, a symlink or a directory containing other nodes.
+pub struct Node<'a, 'w: 'a> {
+    writer: &'a mut Writer<'w>,
+}
+
+impl<'a, 'w> Node<'a, 'w> {
+    async fn write(&mut self, data: &[u8]) -> io::Result<()> {
+        self.writer.write_all(data).await
+    }
+
+    async fn pad(&mut self, n: u64) -> io::Result<()> {
+        match (n & 7) as usize {
+            0 => Ok(()),
+            n => self.write(&[0; 8][n..]).await,
+        }
+    }
+
+    /// Make this node a symlink.
+    pub async fn symlink(mut self, target: &[u8]) -> io::Result<()> {
+        debug_assert!(
+            target.len() <= wire::MAX_TARGET_LEN,
+            "target.len() > {}",
+            wire::MAX_TARGET_LEN
+        );
+        debug_assert!(!target.is_empty(), "target is empty");
+        debug_assert!(!target.contains(&0), "target contains null byte");
+
+        self.write(&wire::TOK_SYM).await?;
+        self.write(&target.len().to_le_bytes()).await?;
+        self.write(target).await?;
+        self.pad(target.len() as u64).await?;
+        self.write(&wire::TOK_PAR).await?;
+        Ok(())
+    }
+
+    /// Make this node a single file.
+    pub async fn file(
+        mut self,
+        executable: bool,
+        size: u64,
+        reader: &mut (dyn AsyncBufRead + Unpin + Send),
+    ) -> io::Result<()> {
+        self.write(if executable {
+            &wire::TOK_EXE
+        } else {
+            &wire::TOK_REG
+        })
+        .await?;
+
+        self.write(&size.to_le_bytes()).await?;
+
+        let mut need = size;
+        while need != 0 {
+            let data = reader.fill_buf().await?;
+
+            if data.is_empty() {
+                return Err(UnexpectedEof.into());
+            }
+
+            let n = need.min(data.len() as u64) as usize;
+            self.write(&data[..n]).await?;
+
+            need -= n as u64;
+            Pin::new(&mut *reader).consume(n);
+        }
+
+        // bail if there's still data left in the passed reader.
+        // This uses the same code as [BufRead::has_data_left] (unstable).
+        if reader.fill_buf().await.map(|b| !b.is_empty())? {
+            return Err(io::Error::new(
+                InvalidInput,
+                "reader contained more data than specified size",
+            ));
+        }
+
+        self.pad(size).await?;
+        self.write(&wire::TOK_PAR).await?;
+
+        Ok(())
+    }
+
+    /// Make this node a directory, the content of which is set using the
+    /// resulting [`Directory`] value.
+    ///
+    /// It is the caller's responsibility to invoke [`Directory::close`],
+    /// or invalid archives will be produced silently.
+    pub async fn directory(mut self) -> io::Result<Directory<'a, 'w>> {
+        self.write(&wire::TOK_DIR).await?;
+        Ok(Directory::new(self))
+    }
+}
+
+#[cfg(debug_assertions)]
+type Name = Vec<u8>;
+#[cfg(not(debug_assertions))]
+type Name = ();
+
+fn into_name(_name: &[u8]) -> Name {
+    #[cfg(debug_assertions)]
+    _name.to_owned()
+}
+
+/// Content of a NAR node that represents a directory.
+pub struct Directory<'a, 'w> {
+    node: Node<'a, 'w>,
+    prev_name: Option<Name>,
+}
+
+impl<'a, 'w> Directory<'a, 'w> {
+    fn new(node: Node<'a, 'w>) -> Self {
+        Self {
+            node,
+            prev_name: None,
+        }
+    }
+
+    /// Add an entry to the directory.
+    ///
+    /// The entry is simply another [`Node`], which can then be filled like the
+    /// root of a NAR (including, of course, by nesting directories).
+    ///
+    /// It is the caller's responsibility to ensure that directory entries are
+    /// written in order of ascending name. If this is not ensured, this method
+    /// may panic or silently produce invalid archives.
+    pub async fn entry(&mut self, name: &[u8]) -> io::Result<Node<'_, 'w>> {
+        debug_assert!(
+            name.len() <= wire::MAX_NAME_LEN,
+            "name.len() > {}",
+            wire::MAX_NAME_LEN
+        );
+        debug_assert!(!name.is_empty(), "name is empty");
+        debug_assert!(!name.contains(&0), "name contains null byte");
+        debug_assert!(!name.contains(&b'/'), "name contains {:?}", '/');
+        debug_assert!(name != b".", "name == {:?}", ".");
+        debug_assert!(name != b"..", "name == {:?}", "..");
+
+        match self.prev_name {
+            None => {
+                self.prev_name = Some(into_name(name));
+            }
+            Some(ref mut _prev_name) => {
+                #[cfg(debug_assertions)]
+                {
+                    use bstr::ByteSlice;
+                    assert!(
+                        &**_prev_name < name,
+                        "misordered names: {:?} >= {:?}",
+                        _prev_name.as_bstr(),
+                        name.as_bstr()
+                    );
+                    name.clone_into(_prev_name);
+                }
+                self.node.write(&wire::TOK_PAR).await?;
+            }
+        }
+
+        self.node.write(&wire::TOK_ENT).await?;
+        self.node.write(&name.len().to_le_bytes()).await?;
+        self.node.write(name).await?;
+        self.node.pad(name.len() as u64).await?;
+        self.node.write(&wire::TOK_NOD).await?;
+
+        Ok(Node {
+            writer: &mut *self.node.writer,
+        })
+    }
+
+    /// Close a directory and write terminators for the directory to the NAR.
+    ///
+    /// **Important:** This *must* be called when all entries have been written
+    /// in a directory, otherwise the resulting NAR file will be invalid.
+    pub async fn close(mut self) -> io::Result<()> {
+        if self.prev_name.is_some() {
+            self.node.write(&wire::TOK_PAR).await?;
+        }
+
+        self.node.write(&wire::TOK_PAR).await?;
+        Ok(())
+    }
+}
diff --git a/tvix/nix-compat/src/nar/writer/mod.rs b/tvix/nix-compat/src/nar/writer/mod.rs
new file mode 100644
index 0000000000..fe8ccccb37
--- /dev/null
+++ b/tvix/nix-compat/src/nar/writer/mod.rs
@@ -0,0 +1,9 @@
+pub use sync::*;
+
+pub mod sync;
+
+#[cfg(test)]
+mod test;
+
+#[cfg(feature = "async")]
+pub mod r#async;
diff --git a/tvix/nix-compat/src/nar/writer/sync.rs b/tvix/nix-compat/src/nar/writer/sync.rs
new file mode 100644
index 0000000000..6270129028
--- /dev/null
+++ b/tvix/nix-compat/src/nar/writer/sync.rs
@@ -0,0 +1,224 @@
+//! Implements an interface for writing the Nix archive format (NAR).
+//!
+//! NAR files (and their hashed representations) are used in C++ Nix for
+//! addressing fixed-output derivations and a variety of other things.
+//!
+//! NAR files can be output to any type that implements [`Write`], and content
+//! can be read from any type that implementes [`BufRead`].
+//!
+//! Writing a single file might look like this:
+//!
+//! ```rust
+//! # use std::io::BufReader;
+//! # let some_file: Vec<u8> = vec![0, 1, 2, 3, 4];
+//!
+//! // Output location to write the NAR to.
+//! let mut sink: Vec<u8> = Vec::new();
+//!
+//! // Instantiate writer for this output location.
+//! let mut nar = nix_compat::nar::writer::open(&mut sink)?;
+//!
+//! // Acquire metadata for the single file to output, and pass it in a
+//! // `BufRead`-implementing type.
+//!
+//! let executable = false;
+//! let size = some_file.len() as u64;
+//! let mut reader = BufReader::new(some_file.as_slice());
+//! nar.file(executable, size, &mut reader)?;
+//! # Ok::<(), std::io::Error>(())
+//! ```
+
+use crate::nar::wire;
+use std::io::{
+    self, BufRead,
+    ErrorKind::{InvalidInput, UnexpectedEof},
+    Write,
+};
+
+/// Convenience type alias for types implementing [`Write`].
+pub type Writer<'a> = dyn Write + Send + 'a;
+
+/// Create a new NAR, writing the output to the specified writer.
+pub fn open<'a, 'w: 'a>(writer: &'a mut Writer<'w>) -> io::Result<Node<'a, 'w>> {
+    let mut node = Node { writer };
+    node.write(&wire::TOK_NAR)?;
+    Ok(node)
+}
+
+/// Single node in a NAR file.
+///
+/// A NAR can be thought of as a tree of nodes represented by this type. Each
+/// node can be a file, a symlink or a directory containing other nodes.
+pub struct Node<'a, 'w: 'a> {
+    writer: &'a mut Writer<'w>,
+}
+
+impl<'a, 'w> Node<'a, 'w> {
+    fn write(&mut self, data: &[u8]) -> io::Result<()> {
+        self.writer.write_all(data)
+    }
+
+    fn pad(&mut self, n: u64) -> io::Result<()> {
+        match (n & 7) as usize {
+            0 => Ok(()),
+            n => self.write(&[0; 8][n..]),
+        }
+    }
+
+    /// Make this node a symlink.
+    pub fn symlink(mut self, target: &[u8]) -> io::Result<()> {
+        debug_assert!(
+            target.len() <= wire::MAX_TARGET_LEN,
+            "target.len() > {}",
+            wire::MAX_TARGET_LEN
+        );
+        debug_assert!(!target.is_empty(), "target is empty");
+        debug_assert!(!target.contains(&0), "target contains null byte");
+
+        self.write(&wire::TOK_SYM)?;
+        self.write(&target.len().to_le_bytes())?;
+        self.write(target)?;
+        self.pad(target.len() as u64)?;
+        self.write(&wire::TOK_PAR)?;
+        Ok(())
+    }
+
+    /// Make this node a single file.
+    pub fn file(mut self, executable: bool, size: u64, reader: &mut dyn BufRead) -> io::Result<()> {
+        self.write(if executable {
+            &wire::TOK_EXE
+        } else {
+            &wire::TOK_REG
+        })?;
+
+        self.write(&size.to_le_bytes())?;
+
+        let mut need = size;
+        while need != 0 {
+            let data = reader.fill_buf()?;
+
+            if data.is_empty() {
+                return Err(UnexpectedEof.into());
+            }
+
+            let n = need.min(data.len() as u64) as usize;
+            self.write(&data[..n])?;
+
+            need -= n as u64;
+            reader.consume(n);
+        }
+
+        // bail if there's still data left in the passed reader.
+        // This uses the same code as [BufRead::has_data_left] (unstable).
+        if reader.fill_buf().map(|b| !b.is_empty())? {
+            return Err(io::Error::new(
+                InvalidInput,
+                "reader contained more data than specified size",
+            ));
+        }
+
+        self.pad(size)?;
+        self.write(&wire::TOK_PAR)?;
+
+        Ok(())
+    }
+
+    /// Make this node a directory, the content of which is set using the
+    /// resulting [`Directory`] value.
+    ///
+    /// It is the caller's responsibility to invoke [`Directory::close`],
+    /// or invalid archives will be produced silently.
+    pub fn directory(mut self) -> io::Result<Directory<'a, 'w>> {
+        self.write(&wire::TOK_DIR)?;
+        Ok(Directory::new(self))
+    }
+}
+
+#[cfg(debug_assertions)]
+type Name = Vec<u8>;
+#[cfg(not(debug_assertions))]
+type Name = ();
+
+fn into_name(_name: &[u8]) -> Name {
+    #[cfg(debug_assertions)]
+    _name.to_owned()
+}
+
+/// Content of a NAR node that represents a directory.
+pub struct Directory<'a, 'w> {
+    node: Node<'a, 'w>,
+    prev_name: Option<Name>,
+}
+
+impl<'a, 'w> Directory<'a, 'w> {
+    fn new(node: Node<'a, 'w>) -> Self {
+        Self {
+            node,
+            prev_name: None,
+        }
+    }
+
+    /// Add an entry to the directory.
+    ///
+    /// The entry is simply another [`Node`], which can then be filled like the
+    /// root of a NAR (including, of course, by nesting directories).
+    ///
+    /// It is the caller's responsibility to ensure that directory entries are
+    /// written in order of ascending name. If this is not ensured, this method
+    /// may panic or silently produce invalid archives.
+    pub fn entry(&mut self, name: &[u8]) -> io::Result<Node<'_, 'w>> {
+        debug_assert!(
+            name.len() <= wire::MAX_NAME_LEN,
+            "name.len() > {}",
+            wire::MAX_NAME_LEN
+        );
+        debug_assert!(!name.is_empty(), "name is empty");
+        debug_assert!(!name.contains(&0), "name contains null byte");
+        debug_assert!(!name.contains(&b'/'), "name contains {:?}", '/');
+        debug_assert!(name != b".", "name == {:?}", ".");
+        debug_assert!(name != b"..", "name == {:?}", "..");
+
+        match self.prev_name {
+            None => {
+                self.prev_name = Some(into_name(name));
+            }
+            Some(ref mut _prev_name) => {
+                #[cfg(debug_assertions)]
+                {
+                    use bstr::ByteSlice;
+                    assert!(
+                        &**_prev_name < name,
+                        "misordered names: {:?} >= {:?}",
+                        _prev_name.as_bstr(),
+                        name.as_bstr()
+                    );
+                    name.clone_into(_prev_name);
+                }
+                self.node.write(&wire::TOK_PAR)?;
+            }
+        }
+
+        self.node.write(&wire::TOK_ENT)?;
+        self.node.write(&name.len().to_le_bytes())?;
+        self.node.write(name)?;
+        self.node.pad(name.len() as u64)?;
+        self.node.write(&wire::TOK_NOD)?;
+
+        Ok(Node {
+            writer: &mut *self.node.writer,
+        })
+    }
+
+    /// Close a directory and write terminators for the directory to the NAR.
+    ///
+    /// **Important:** This *must* be called when all entries have been written
+    /// in a directory, otherwise the resulting NAR file will be invalid.
+    pub fn close(mut self) -> io::Result<()> {
+        if self.prev_name.is_some() {
+            self.node.write(&wire::TOK_PAR)?;
+        }
+
+        self.node.write(&wire::TOK_PAR)?;
+        Ok(())
+    }
+}
diff --git a/tvix/nix-compat/src/nar/writer/test.rs b/tvix/nix-compat/src/nar/writer/test.rs
new file mode 100644
index 0000000000..d7f18a49af
--- /dev/null
+++ b/tvix/nix-compat/src/nar/writer/test.rs
@@ -0,0 +1,128 @@
+use crate::nar;
+
+#[test]
+fn symlink() {
+    let mut buf = vec![];
+    let node = nar::writer::open(&mut buf).unwrap();
+
+    node.symlink("/nix/store/somewhereelse".as_bytes()).unwrap();
+
+    assert_eq!(include_bytes!("../tests/symlink.nar"), buf.as_slice());
+}
+
+#[cfg(feature = "async")]
+#[tokio::test]
+async fn symlink_async() {
+    let mut buf = vec![];
+
+    let node = nar::writer::r#async::open(&mut buf).await.unwrap();
+    node.symlink("/nix/store/somewhereelse".as_bytes())
+        .await
+        .unwrap();
+
+    assert_eq!(include_bytes!("../tests/symlink.nar"), buf.as_slice());
+}
+
+#[test]
+fn file() {
+    let mut buf = vec![];
+    let node = nar::writer::open(&mut buf).unwrap();
+
+    let file_contents = "Hello World!".to_string();
+    node.file(
+        false,
+        file_contents.len() as u64,
+        &mut std::io::Cursor::new(file_contents),
+    )
+    .unwrap();
+
+    assert_eq!(include_bytes!("../tests/helloworld.nar"), buf.as_slice());
+}
+
+#[cfg(feature = "async")]
+#[tokio::test]
+async fn file_async() {
+    use std::io::Cursor;
+
+    let mut buf = vec![];
+
+    let node = nar::writer::r#async::open(&mut buf).await.unwrap();
+
+    let file_contents = "Hello World!".to_string();
+    node.file(
+        false,
+        file_contents.len() as u64,
+        &mut Cursor::new(file_contents),
+    )
+    .await
+    .unwrap();
+
+    assert_eq!(include_bytes!("../tests/helloworld.nar"), buf.as_slice());
+}
+
+#[test]
+fn complicated() {
+    let mut buf = vec![];
+    let node = nar::writer::open(&mut buf).unwrap();
+
+    let mut dir_node = node.directory().unwrap();
+
+    let e = dir_node.entry(".keep".as_bytes()).unwrap();
+    e.file(false, 0, &mut std::io::Cursor::new([]))
+        .expect("read .keep must succeed");
+
+    let e = dir_node.entry("aa".as_bytes()).unwrap();
+    e.symlink("/nix/store/somewhereelse".as_bytes())
+        .expect("symlink must succeed");
+
+    let e = dir_node.entry("keep".as_bytes()).unwrap();
+    let mut subdir_node = e.directory().expect("directory must succeed");
+
+    let e_sub = subdir_node
+        .entry(".keep".as_bytes())
+        .expect("subdir entry must succeed");
+    e_sub.file(false, 0, &mut std::io::Cursor::new([])).unwrap();
+
+    // close the subdir, and then the dir, which is required.
+    subdir_node.close().unwrap();
+    dir_node.close().unwrap();
+
+    assert_eq!(include_bytes!("../tests/complicated.nar"), buf.as_slice());
+}
+
+#[cfg(feature = "async")]
+#[tokio::test]
+async fn complicated_async() {
+    use std::io::Cursor;
+
+    let mut buf = vec![];
+
+    let node = nar::writer::r#async::open(&mut buf).await.unwrap();
+
+    let mut dir_node = node.directory().await.unwrap();
+
+    let e = dir_node.entry(".keep".as_bytes()).await.unwrap();
+    e.file(false, 0, &mut Cursor::new([]))
+        .await
+        .expect("read .keep must succeed");
+
+    let e = dir_node.entry("aa".as_bytes()).await.unwrap();
+    e.symlink("/nix/store/somewhereelse".as_bytes())
+        .await
+        .expect("symlink must succeed");
+
+    let e = dir_node.entry("keep".as_bytes()).await.unwrap();
+    let mut subdir_node = e.directory().await.expect("directory must succeed");
+
+    let e_sub = subdir_node
+        .entry(".keep".as_bytes())
+        .await
+        .expect("subdir entry must succeed");
+    e_sub.file(false, 0, &mut Cursor::new([])).await.unwrap();
+
+    // close the subdir, and then the dir, which is required.
+    subdir_node.close().await.unwrap();
+    dir_node.close().await.unwrap();
+
+    assert_eq!(include_bytes!("../tests/complicated.nar"), buf.as_slice());
+}
diff --git a/tvix/nix-compat/src/narinfo/fingerprint.rs b/tvix/nix-compat/src/narinfo/fingerprint.rs
new file mode 100644
index 0000000000..3e02aca571
--- /dev/null
+++ b/tvix/nix-compat/src/narinfo/fingerprint.rs
@@ -0,0 +1,50 @@
+use crate::{nixbase32, store_path::StorePathRef};
+
+/// Computes the fingerprint string for certain fields in a [super::NarInfo].
+/// This fingerprint is signed by an ed25519 key, and in the case of a Nix HTTP
+/// Binary cache, included in the NARInfo files served from there.
+pub fn fingerprint<'a, R: Iterator<Item = &'a StorePathRef<'a>>>(
+    store_path: &StorePathRef,
+    nar_sha256: &[u8; 32],
+    nar_size: u64,
+    references: R,
+) -> String {
+    format!(
+        "1;{};sha256:{};{};{}",
+        store_path.to_absolute_path(),
+        nixbase32::encode(nar_sha256),
+        nar_size,
+        // references are absolute paths, joined with `,`.
+        references
+            .map(|r| r.to_absolute_path())
+            .collect::<Vec<String>>()
+            .join(",")
+    )
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::narinfo::NarInfo;
+
+    const NARINFO_STR: &str = r#"StorePath: /nix/store/syd87l2rxw8cbsxmxl853h0r6pdwhwjr-curl-7.82.0-bin
+URL: nar/05ra3y72i3qjri7xskf9qj8kb29r6naqy1sqpbs3azi3xcigmj56.nar.xz
+Compression: xz
+FileHash: sha256:05ra3y72i3qjri7xskf9qj8kb29r6naqy1sqpbs3azi3xcigmj56
+FileSize: 68852
+NarHash: sha256:1b4sb93wp679q4zx9k1ignby1yna3z7c4c2ri3wphylbc2dwsys0
+NarSize: 196040
+References: 0jqd0rlxzra1rs38rdxl43yh6rxchgc6-curl-7.82.0 6w8g7njm4mck5dmjxws0z1xnrxvl81xa-glibc-2.34-115 j5jxw3iy7bbz4a57fh9g2xm2gxmyal8h-zlib-1.2.12 yxvjs9drzsphm9pcf42a4byzj1kb9m7k-openssl-1.1.1n
+Deriver: 5rwxzi7pal3qhpsyfc16gzkh939q1np6-curl-7.82.0.drv
+Sig: cache.nixos.org-1:TsTTb3WGTZKphvYdBHXwo6weVILmTytUjLB+vcX89fOjjRicCHmKA4RCPMVLkj6TMJ4GMX3HPVWRdD1hkeKZBQ==
+Sig: test1:519iiVLx/c4Rdt5DNt6Y2Jm6hcWE9+XY69ygiWSZCNGVcmOcyL64uVAJ3cV8vaTusIZdbTnYo9Y7vDNeTmmMBQ==
+"#;
+
+    #[test]
+    fn fingerprint() {
+        let parsed = NarInfo::parse(NARINFO_STR).expect("must parse");
+        assert_eq!(
+            "1;/nix/store/syd87l2rxw8cbsxmxl853h0r6pdwhwjr-curl-7.82.0-bin;sha256:1b4sb93wp679q4zx9k1ignby1yna3z7c4c2ri3wphylbc2dwsys0;196040;/nix/store/0jqd0rlxzra1rs38rdxl43yh6rxchgc6-curl-7.82.0,/nix/store/6w8g7njm4mck5dmjxws0z1xnrxvl81xa-glibc-2.34-115,/nix/store/j5jxw3iy7bbz4a57fh9g2xm2gxmyal8h-zlib-1.2.12,/nix/store/yxvjs9drzsphm9pcf42a4byzj1kb9m7k-openssl-1.1.1n",
+            parsed.fingerprint()
+        );
+    }
+}
diff --git a/tvix/nix-compat/src/narinfo/mod.rs b/tvix/nix-compat/src/narinfo/mod.rs
new file mode 100644
index 0000000000..b1c10bceb2
--- /dev/null
+++ b/tvix/nix-compat/src/narinfo/mod.rs
@@ -0,0 +1,527 @@
+//! NAR info files describe a store path in a traditional Nix binary cache.
+//! Over the wire, they are formatted as "Key: value" pairs separated by newlines.
+//!
+//! It contains four kinds of information:
+//! 1. the description of the store path itself
+//!    * store path prefix, digest, and name
+//!    * NAR hash and size
+//!    * references
+//! 2. authenticity information
+//!    * zero or more signatures over that description
+//!    * an optional [CAHash] for content-addressed paths (fixed outputs, sources, and derivations)
+//! 3. derivation metadata
+//!    * deriver (the derivation that produced this path)
+//!    * system (the system value of that derivation)
+//! 4. cache-specific information
+//!    * URL of the compressed NAR, relative to the NAR info file
+//!    * compression algorithm used for the NAR
+//!    * hash and size of the compressed NAR
+
+use bitflags::bitflags;
+use data_encoding::HEXLOWER;
+use std::{
+    fmt::{self, Display},
+    mem,
+};
+
+use crate::{nixbase32, nixhash::CAHash, store_path::StorePathRef};
+
+mod fingerprint;
+mod public_keys;
+mod signature;
+
+pub use fingerprint::fingerprint;
+
+pub use public_keys::{Error as PubKeyError, PubKey};
+pub use signature::{Error as SignatureError, Signature};
+
+#[derive(Debug)]
+pub struct NarInfo<'a> {
+    pub flags: Flags,
+    // core (authenticated, but unverified here)
+    /// Store path described by this [NarInfo]
+    pub store_path: StorePathRef<'a>,
+    /// SHA-256 digest of the NAR file
+    pub nar_hash: [u8; 32],
+    /// Size of the NAR file in bytes
+    pub nar_size: u64,
+    /// Store paths known to be referenced by the contents
+    pub references: Vec<StorePathRef<'a>>,
+    // authenticity
+    /// Ed25519 signature over the path fingerprint
+    pub signatures: Vec<Signature<'a>>,
+    /// Content address (for content-defined paths)
+    pub ca: Option<CAHash>,
+    // derivation metadata
+    /// Nix system triple of [NarInfo::deriver]
+    pub system: Option<&'a str>,
+    /// Store path of the derivation that produced this. The last .drv suffix is stripped.
+    pub deriver: Option<StorePathRef<'a>>,
+    // cache-specific untrusted metadata
+    /// Relative URL of the compressed NAR file
+    pub url: &'a str,
+    /// Compression method of the NAR file
+    /// `None` means `Compression: none`.
+    ///
+    /// Nix interprets a missing `Compression` field as `Some("bzip2")`,
+    /// so we do as well. We haven't found any examples of this in the
+    /// wild, not even in the cache.nixos.org dataset.
+    pub compression: Option<&'a str>,
+    /// SHA-256 digest of the file at `url`
+    pub file_hash: Option<[u8; 32]>,
+    /// Size of the file at `url` in bytes
+    pub file_size: Option<u64>,
+}
+
+bitflags! {
+    /// TODO(edef): be conscious of these when roundtripping
+    #[derive(Debug, Copy, Clone)]
+    pub struct Flags: u8 {
+        const UNKNOWN_FIELD = 1 << 0;
+        const COMPRESSION_DEFAULT = 1 << 1;
+        // Format quirks encountered in the cache.nixos.org dataset
+        const REFERENCES_OUT_OF_ORDER = 1 << 2;
+        const NAR_HASH_HEX = 1 << 3;
+    }
+}
+
+impl<'a> NarInfo<'a> {
+    pub fn parse(input: &'a str) -> Result<Self, Error> {
+        let mut flags = Flags::empty();
+        let mut store_path = None;
+        let mut url = None;
+        let mut compression = None;
+        let mut file_hash = None;
+        let mut file_size = None;
+        let mut nar_hash = None;
+        let mut nar_size = None;
+        let mut references = None;
+        let mut system = None;
+        let mut deriver = None;
+        let mut signatures = vec![];
+        let mut ca = None;
+
+        for line in input.lines() {
+            let (tag, val) = line
+                .split_once(':')
+                .ok_or_else(|| Error::InvalidLine(line.to_string()))?;
+
+            let val = val
+                .strip_prefix(' ')
+                .ok_or_else(|| Error::InvalidLine(line.to_string()))?;
+
+            match tag {
+                "StorePath" => {
+                    let val = val
+                        .strip_prefix("/nix/store/")
+                        .ok_or(Error::InvalidStorePath(
+                            crate::store_path::Error::MissingStoreDir,
+                        ))?;
+                    let val = StorePathRef::from_bytes(val.as_bytes())
+                        .map_err(Error::InvalidStorePath)?;
+
+                    if store_path.replace(val).is_some() {
+                        return Err(Error::DuplicateField(tag.to_string()));
+                    }
+                }
+                "URL" => {
+                    if val.is_empty() {
+                        return Err(Error::EmptyField(tag.to_string()));
+                    }
+
+                    if url.replace(val).is_some() {
+                        return Err(Error::DuplicateField(tag.to_string()));
+                    }
+                }
+                "Compression" => {
+                    if val.is_empty() {
+                        return Err(Error::EmptyField(tag.to_string()));
+                    }
+
+                    if compression.replace(val).is_some() {
+                        return Err(Error::DuplicateField(tag.to_string()));
+                    }
+                }
+                "FileHash" => {
+                    let val = val
+                        .strip_prefix("sha256:")
+                        .ok_or_else(|| Error::MissingPrefixForHash(tag.to_string()))?;
+                    let val = nixbase32::decode_fixed::<32>(val)
+                        .map_err(|e| Error::UnableToDecodeHash(tag.to_string(), e))?;
+
+                    if file_hash.replace(val).is_some() {
+                        return Err(Error::DuplicateField(tag.to_string()));
+                    }
+                }
+                "FileSize" => {
+                    let val = val
+                        .parse::<u64>()
+                        .map_err(|_| Error::UnableToParseSize(tag.to_string(), val.to_string()))?;
+
+                    if file_size.replace(val).is_some() {
+                        return Err(Error::DuplicateField(tag.to_string()));
+                    }
+                }
+                "NarHash" => {
+                    let val = val
+                        .strip_prefix("sha256:")
+                        .ok_or_else(|| Error::MissingPrefixForHash(tag.to_string()))?;
+
+                    let val = if val.len() != HEXLOWER.encode_len(32) {
+                        nixbase32::decode_fixed::<32>(val)
+                    } else {
+                        flags |= Flags::NAR_HASH_HEX;
+
+                        let val = val.as_bytes();
+                        let mut buf = [0u8; 32];
+
+                        HEXLOWER
+                            .decode_mut(val, &mut buf)
+                            .map_err(|e| e.error)
+                            .map(|_| buf)
+                    };
+
+                    let val = val.map_err(|e| Error::UnableToDecodeHash(tag.to_string(), e))?;
+
+                    if nar_hash.replace(val).is_some() {
+                        return Err(Error::DuplicateField(tag.to_string()));
+                    }
+                }
+                "NarSize" => {
+                    let val = val
+                        .parse::<u64>()
+                        .map_err(|_| Error::UnableToParseSize(tag.to_string(), val.to_string()))?;
+
+                    if nar_size.replace(val).is_some() {
+                        return Err(Error::DuplicateField(tag.to_string()));
+                    }
+                }
+                "References" => {
+                    let val: Vec<StorePathRef> = if !val.is_empty() {
+                        let mut prev = "";
+                        val.split(' ')
+                            .enumerate()
+                            .map(|(i, s)| {
+                                // TODO(edef): track *duplicates* if this occurs
+                                if mem::replace(&mut prev, s) >= s {
+                                    flags |= Flags::REFERENCES_OUT_OF_ORDER;
+                                }
+
+                                StorePathRef::from_bytes(s.as_bytes())
+                                    .map_err(|err| Error::InvalidReference(i, err))
+                            })
+                            .collect::<Result<_, _>>()?
+                    } else {
+                        vec![]
+                    };
+
+                    if references.replace(val).is_some() {
+                        return Err(Error::DuplicateField(tag.to_string()));
+                    }
+                }
+                "System" => {
+                    if val.is_empty() {
+                        return Err(Error::EmptyField(tag.to_string()));
+                    }
+
+                    if system.replace(val).is_some() {
+                        return Err(Error::DuplicateField(tag.to_string()));
+                    }
+                }
+                "Deriver" => {
+                    match val.strip_suffix(".drv") {
+                        Some(val) => {
+                            let val = StorePathRef::from_bytes(val.as_bytes())
+                                .map_err(Error::InvalidDeriverStorePath)?;
+
+                            if deriver.replace(val).is_some() {
+                                return Err(Error::DuplicateField(tag.to_string()));
+                            }
+                        }
+                        None => {
+                            return Err(Error::InvalidDeriverStorePathMissingSuffix);
+                        }
+                    };
+                }
+                "Sig" => {
+                    let val = Signature::parse(val)
+                        .map_err(|e| Error::UnableToParseSignature(signatures.len(), e))?;
+
+                    signatures.push(val);
+                }
+                "CA" => {
+                    let val = CAHash::from_nix_hex_str(val)
+                        .ok_or_else(|| Error::UnableToParseCA(val.to_string()))?;
+
+                    if ca.replace(val).is_some() {
+                        return Err(Error::DuplicateField(tag.to_string()));
+                    }
+                }
+                _ => {
+                    flags |= Flags::UNKNOWN_FIELD;
+                }
+            }
+        }
+
+        Ok(NarInfo {
+            store_path: store_path.ok_or(Error::MissingField("StorePath"))?,
+            nar_hash: nar_hash.ok_or(Error::MissingField("NarHash"))?,
+            nar_size: nar_size.ok_or(Error::MissingField("NarSize"))?,
+            references: references.ok_or(Error::MissingField("References"))?,
+            signatures,
+            ca,
+            system,
+            deriver,
+            url: url.ok_or(Error::MissingField("URL"))?,
+            compression: match compression {
+                Some("none") => None,
+                None => {
+                    flags |= Flags::COMPRESSION_DEFAULT;
+                    Some("bzip2")
+                }
+                _ => compression,
+            },
+            file_hash,
+            file_size,
+            flags,
+        })
+    }
+
+    /// Computes the fingerprint string for certain fields in this [NarInfo].
+    /// This fingerprint is signed in [self.signatures].
+    pub fn fingerprint(&self) -> String {
+        fingerprint(
+            &self.store_path,
+            &self.nar_hash,
+            self.nar_size,
+            self.references.iter(),
+        )
+    }
+}
+
+impl Display for NarInfo<'_> {
+    fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result {
+        writeln!(w, "StorePath: /nix/store/{}", self.store_path)?;
+        writeln!(w, "URL: {}", self.url)?;
+
+        if let Some(compression) = self.compression {
+            writeln!(w, "Compression: {compression}")?;
+        }
+
+        if let Some(file_hash) = self.file_hash {
+            writeln!(w, "FileHash: sha256:{}", nixbase32::encode(&file_hash),)?;
+        }
+
+        if let Some(file_size) = self.file_size {
+            writeln!(w, "FileSize: {file_size}")?;
+        }
+
+        writeln!(w, "NarHash: sha256:{}", nixbase32::encode(&self.nar_hash),)?;
+        writeln!(w, "NarSize: {}", self.nar_size)?;
+
+        write!(w, "References:")?;
+        if self.references.is_empty() {
+            write!(w, " ")?;
+        } else {
+            for path in &self.references {
+                write!(w, " {path}")?;
+            }
+        }
+        writeln!(w)?;
+
+        if let Some(deriver) = &self.deriver {
+            writeln!(w, "Deriver: {deriver}.drv")?;
+        }
+
+        if let Some(system) = self.system {
+            writeln!(w, "System: {system}")?;
+        }
+
+        for sig in &self.signatures {
+            writeln!(w, "Sig: {sig}")?;
+        }
+
+        if let Some(ca) = &self.ca {
+            writeln!(w, "CA: {}", ca.to_nix_nixbase32_string())?;
+        }
+
+        Ok(())
+    }
+}
+
+#[derive(thiserror::Error, Debug)]
+pub enum Error {
+    #[error("duplicate field: {0}")]
+    DuplicateField(String),
+
+    #[error("missing field: {0}")]
+    MissingField(&'static str),
+
+    #[error("invalid line: {0}")]
+    InvalidLine(String),
+
+    #[error("invalid StorePath: {0}")]
+    InvalidStorePath(crate::store_path::Error),
+
+    #[error("field {0} may not be empty string")]
+    EmptyField(String),
+
+    #[error("invalid {0}: {1}")]
+    UnableToParseSize(String, String),
+
+    #[error("unable to parse #{0} reference: {1}")]
+    InvalidReference(usize, crate::store_path::Error),
+
+    #[error("invalid Deriver store path: {0}")]
+    InvalidDeriverStorePath(crate::store_path::Error),
+
+    #[error("invalid Deriver store path, must end with .drv")]
+    InvalidDeriverStorePathMissingSuffix,
+
+    #[error("missing prefix for {0}")]
+    MissingPrefixForHash(String),
+
+    #[error("unable to decode {0}: {1}")]
+    UnableToDecodeHash(String, data_encoding::DecodeError),
+
+    #[error("unable to parse signature #{0}: {1}")]
+    UnableToParseSignature(usize, SignatureError),
+
+    #[error("unable to parse CA field: {0}")]
+    UnableToParseCA(String),
+}
+
+#[cfg(test)]
+mod test {
+    use hex_literal::hex;
+    use lazy_static::lazy_static;
+    use pretty_assertions::assert_eq;
+    use std::{io, str};
+
+    use crate::{
+        nixhash::{CAHash, NixHash},
+        store_path::StorePathRef,
+    };
+
+    use super::{Flags, NarInfo};
+
+    lazy_static! {
+        static ref CASES: &'static [&'static str] = {
+            let data = zstd::decode_all(io::Cursor::new(include_bytes!(
+                "../../testdata/narinfo.zst"
+            )))
+            .unwrap();
+            let data = str::from_utf8(Vec::leak(data)).unwrap();
+            Vec::leak(
+                data.split_inclusive("\n\n")
+                    .map(|s| s.strip_suffix('\n').unwrap())
+                    .collect::<Vec<_>>(),
+            )
+        };
+    }
+
+    #[test]
+    fn roundtrip() {
+        for &input in *CASES {
+            let parsed = NarInfo::parse(input).expect("should parse");
+            let output = format!("{parsed}");
+            assert_eq!(input, output, "should roundtrip");
+        }
+    }
+
+    #[test]
+    fn references_out_of_order() {
+        let parsed = NarInfo::parse(
+            r#"StorePath: /nix/store/xi429w4ddvb1r77978hm7jfb2jsn559r-gcc-3.4.6
+URL: nar/1hr09cgkyw1hcsfkv5qp5jlpmf2mqrkrqs3xj5zklq9c1h9544ff.nar.bz2
+Compression: bzip2
+FileHash: sha256:1hr09cgkyw1hcsfkv5qp5jlpmf2mqrkrqs3xj5zklq9c1h9544ff
+FileSize: 4006
+NarHash: sha256:0ik9mpqxpd9hv325hdblj2nawqj5w7951qdyy8ikxgwr6fq7m11c
+NarSize: 21264
+References: a8922c0h87iilxzzvwn2hmv8x210aqb9-glibc-2.7 7w2acjgalb0cm7b3bg8yswza4l7iil9y-binutils-2.18 mm631h09mj964hm9q04l5fd8vw12j1mm-bash-3.2-p39 nx2zs2qd6snfcpzw4a0jnh26z9m0yihz-gcc-3.4.6 xi429w4ddvb1r77978hm7jfb2jsn559r-gcc-3.4.6
+Deriver: 2dzpn70c1hawczwhg9aavqk18zp9zsva-gcc-3.4.6.drv
+Sig: cache.nixos.org-1:o1DTsjCz0PofLJ216P2RBuSulI8BAb6zHxWE4N+tzlcELk5Uk/GO2SCxWTRN5wJutLZZ+cHTMdWqOHF88KGQDg==
+"#).expect("should parse");
+
+        assert!(parsed.flags.contains(Flags::REFERENCES_OUT_OF_ORDER));
+        assert_eq!(
+            vec![
+                "a8922c0h87iilxzzvwn2hmv8x210aqb9-glibc-2.7",
+                "7w2acjgalb0cm7b3bg8yswza4l7iil9y-binutils-2.18",
+                "mm631h09mj964hm9q04l5fd8vw12j1mm-bash-3.2-p39",
+                "nx2zs2qd6snfcpzw4a0jnh26z9m0yihz-gcc-3.4.6",
+                "xi429w4ddvb1r77978hm7jfb2jsn559r-gcc-3.4.6"
+            ],
+            parsed
+                .references
+                .iter()
+                .map(StorePathRef::to_string)
+                .collect::<Vec<_>>(),
+        );
+    }
+
+    #[test]
+    fn ca_nar_hash_sha1() {
+        let parsed = NarInfo::parse(
+            r#"StorePath: /nix/store/k20pahypzvr49fy82cw5sx72hdfg3qcr-texlive-hyphenex-37354
+URL: nar/0i5biw0g01514llhfswxy6xfav8lxxdq1xg6ik7hgsqbpw0f06yi.nar.xz
+Compression: xz
+FileHash: sha256:0i5biw0g01514llhfswxy6xfav8lxxdq1xg6ik7hgsqbpw0f06yi
+FileSize: 7120
+NarHash: sha256:0h1bm4sj1cnfkxgyhvgi8df1qavnnv94sd0v09wcrm971602shfg
+NarSize: 22552
+References: 
+Sig: cache.nixos.org-1:u01BybwQhyI5H1bW1EIWXssMDhDDIvXOG5uh8Qzgdyjz6U1qg6DHhMAvXZOUStIj6X5t4/ufFgR8i3fjf0bMAw==
+CA: fixed:r:sha1:1ak1ymbmsfx7z8kh09jzkr3a4dvkrfjw
+"#).expect("should parse");
+
+        assert_eq!(
+            parsed.ca,
+            Some(CAHash::Nar(NixHash::Sha1(hex!(
+                "5cba3c77236ae4f9650270a27fbad375551fa60a"
+            ))))
+        );
+    }
+
+    #[test]
+    fn compression_default() {
+        // This doesn't exist as such in cache.nixos.org.
+        // We explicitly removed the compression field for the sake of this test.
+        let parsed = NarInfo::parse(r#"StorePath: /nix/store/a1jjalr4csx9hcga7fnm122aqabrjnch-digikam-2.6.0
+URL: nar/1fzimfnvq2k8b40n4g54abmncpx2ddckh6qlb77pgq6xiysyil69.nar.bz2
+FileHash: sha256:1fzimfnvq2k8b40n4g54abmncpx2ddckh6qlb77pgq6xiysyil69
+FileSize: 43503778
+NarHash: sha256:0zpbbwipqzr5p8mlpag9wrsp5hlaxkq7gax5jj0hg3vvdziypcw5
+NarSize: 100658640
+References: 0izkyk7bq2ag9393nvnhgm87p75cq09w-liblqr-1-0.4.1 1cslpgyb7vb30inj3210jv6agqv42jxz-qca-2.0.3 1sya3bwjxkzpkmwn67gfzp4gz4g62l36-libXrandr-1.3.1 26yxdaa9z0ma5sgw02i670rsqnl57crs-glib-2.30.3 27lnjh99236kmhbpc5747599zcymfzmg-qt-4.8.2 2v6x378vcfvyxilkvihs60zha54z2x2y-qjson-0.7.1 45hgr3fbnr45n795hn2x7hsymp0h2j2m-libjpeg-8c 4kw1b212s80ap2iyibxrimcqb5imhfj7-libkexiv2-4.7.4 7dvylm5crlc0sfafcc0n46mb5ch67q0j-glibc-2.13 a05cbh1awjbl1rbyb2ynyf4k42v5a9a7-boost-1.47.0 a1jjalr4csx9hcga7fnm122aqabrjnch-digikam-2.6.0 aav5ffg8wlnilgnvdb2jnrv2aam4zmmz-perl-5.14.2 ab0m9h30nsr13w48qriv0k350kmwx567-kdelibs-4.7.4 avffkd49cqvpwdkzry8bn69dkbw4cy29-lensfun-0.2.5 cy8rl8h4yp2j3h8987vkklg328q3wmjz-gcc-4.6.3 dmmh5ihyg1r2dm4azgsfj2kprj92czlg-libSM-1.2.0 fl56j5n4shfw9c0r6vs2i4f1h9zx5kac-soprano-2.7.6 g15cmvh15ggdjcwapskngv20q4yhix40-jasper-1.900.1 i04maxd0din6v92rnqcwl9yra0kl2vk5-marble-4.7.4 kqjjb3m26rdddwwwkk8v45821aps877k-libICE-1.0.7 lxz9r135wkndvi642z4bjgmvyypsgirb-libtiff-3.9.4 m9c8i0a6cl30lcqp654dqkbag3wjmd00-libX11-1.4.1 mpnj4k2ijrgyfkh48fg96nzcmklfh5pl-coreutils-8.15 nppljblap477s0893c151lyq7r7n5v1q-zlib-1.2.7 nw9mdbyp8kyn3v4vkdzq0gsnqbc4mnx3-expat-2.0.1 p1a0dn931mzdkvj6h5yzshbmgxba5r0z-libgphoto2-2.4.11 pvjj07xa1cfkad3gwk376nzdrgknbcqm-mesa-7.11.2 pzcxag98jqccp9ycbxknyh0w95pgnsk4-lcms-1.19 qfi5pgds33kg6vlnxsmj0hyl74vcmyiz-libpng-1.5.10 scm6bj86s3qh3s3x0b9ayjp6755p4q86-mysql-5.1.54 sd23qspcyg385va0lr35xgz3hvlqphg6-libkipi-4.7.4 svmbrhc6kzfzakv20a7zrfl6kbr5mfpq-kdepimlibs-4.7.4 v7kh3h7xfwjz4hgffg3gwrfzjff9bw9d-bash-4.2-p24 vi17f22064djgpk0w248da348q8gxkww-libkdcraw-4.7.4 wkjdzmj3z4dcbsc9f833zs6krdgg2krk-phonon-4.6.0 xf3i3awqi0035ixy2qyb6hk4c92r3vrn-opencv-2.4.2 y1vr0nz8i59x59501020nh2k1dw3bhwq-libusb-0.1.12 yf3hin2hb6i08n7zrk8g3acy54rhg9bp-libXext-1.2.0
+Deriver: la77dr44phk5m5jnl4dvk01cwpykyw9s-digikam-2.6.0.drv
+System: i686-linux
+Sig: cache.nixos.org-1:92fl0i5q7EyegCj5Yf4L0bENkWuVAtgveiRcTEEUH0P6HvCE1xFcPbz/0Pf6Np+K1LPzHK+s5RHOmVoxRsvsDg==
+"#).expect("should parse");
+
+        assert!(parsed.flags.contains(Flags::COMPRESSION_DEFAULT));
+        assert_eq!(parsed.compression, Some("bzip2"));
+    }
+
+    #[test]
+    fn nar_hash_hex() {
+        let parsed = NarInfo::parse(r#"StorePath: /nix/store/0vpqfxbkx0ffrnhbws6g9qwhmliksz7f-perl-HTTP-Cookies-6.01
+URL: nar/1rv1m9inydm1r4krw8hmwg1hs86d0nxddd1pbhihx7l7fycjvfk3.nar.xz
+Compression: xz
+FileHash: sha256:1rv1m9inydm1r4krw8hmwg1hs86d0nxddd1pbhihx7l7fycjvfk3
+FileSize: 19912
+NarHash: sha256:60adfd293a4d81ad7cd7e47263cbb3fc846309ef91b154a08ba672b558f94ff3
+NarSize: 45840
+References: 0vpqfxbkx0ffrnhbws6g9qwhmliksz7f-perl-HTTP-Cookies-6.01 9vrhbib2lxd9pjlg6fnl5b82gblidrcr-perl-HTTP-Message-6.06 wy20zslqxzxxfpzzk0rajh41d7a6mlnf-perl-HTTP-Date-6.02
+Deriver: fb4ihlq3psnsjq95mvvs49rwpplpc8zj-perl-HTTP-Cookies-6.01.drv
+Sig: cache.nixos.org-1:HhaiY36Uk3XV1JGe9d9xHnzAapqJXprU1YZZzSzxE97jCuO5RR7vlG2kF7MSC5thwRyxAtdghdSz3AqFi+QSCw==
+"#).expect("should parse");
+
+        assert!(parsed.flags.contains(Flags::NAR_HASH_HEX));
+        assert_eq!(
+            hex!("60adfd293a4d81ad7cd7e47263cbb3fc846309ef91b154a08ba672b558f94ff3"),
+            parsed.nar_hash,
+        );
+    }
+}
diff --git a/tvix/nix-compat/src/narinfo/public_keys.rs b/tvix/nix-compat/src/narinfo/public_keys.rs
new file mode 100644
index 0000000000..27dd90e096
--- /dev/null
+++ b/tvix/nix-compat/src/narinfo/public_keys.rs
@@ -0,0 +1,152 @@
+//! This module defines data structures and parsers for the public key format
+//! used inside Nix to verify signatures on .narinfo files.
+
+use std::fmt::Display;
+
+use data_encoding::BASE64;
+use ed25519_dalek::{VerifyingKey, PUBLIC_KEY_LENGTH};
+
+use super::Signature;
+
+/// This represents a ed25519 public key and "name".
+/// These are normally passed in the `trusted-public-keys` Nix config option,
+/// and consist of a name and base64-encoded ed25519 pubkey, separated by a `:`.
+#[derive(Debug)]
+pub struct PubKey {
+    name: String,
+    verifying_key: VerifyingKey,
+}
+
+impl PubKey {
+    pub fn new(name: String, verifying_key: VerifyingKey) -> Self {
+        Self {
+            name,
+            verifying_key,
+        }
+    }
+
+    pub fn parse(input: &str) -> Result<Self, Error> {
+        let (name, bytes64) = input.split_once(':').ok_or(Error::MissingSeparator)?;
+
+        if name.is_empty()
+            || !name
+                .chars()
+                .all(|c| char::is_alphanumeric(c) || c == '-' || c == '.')
+        {
+            return Err(Error::InvalidName(name.to_string()));
+        }
+
+        if bytes64.len() != BASE64.encode_len(PUBLIC_KEY_LENGTH) {
+            return Err(Error::InvalidPubKeyLen(bytes64.len()));
+        }
+
+        let mut buf = [0; PUBLIC_KEY_LENGTH + 1];
+        let mut bytes = [0; PUBLIC_KEY_LENGTH];
+        match BASE64.decode_mut(bytes64.as_bytes(), &mut buf) {
+            Ok(PUBLIC_KEY_LENGTH) => {
+                bytes.copy_from_slice(&buf[..PUBLIC_KEY_LENGTH]);
+            }
+            Ok(_) => unreachable!(),
+            // keeping DecodePartial gets annoying lifetime-wise
+            Err(_) => return Err(Error::DecodeError(input.to_string())),
+        }
+
+        let verifying_key = VerifyingKey::from_bytes(&bytes).map_err(Error::InvalidVerifyingKey)?;
+
+        Ok(Self {
+            name: name.to_string(),
+            verifying_key,
+        })
+    }
+
+    pub fn name(&self) -> &str {
+        &self.name
+    }
+
+    /// Verify the passed in signature is a correct signature for the passed in fingerprint and is signed
+    /// by the key material referred to by [Self],
+    /// which means the name in the signature has to match,
+    /// and the signature bytes themselves need to be a valid signature made by
+    /// the signing key identified by [Self::verifying key].
+    pub fn verify(&self, fingerprint: &str, signature: &Signature) -> bool {
+        if self.name() != signature.name() {
+            return false;
+        }
+
+        return signature.verify(fingerprint.as_bytes(), &self.verifying_key);
+    }
+}
+
+#[derive(Debug, thiserror::Error)]
+pub enum Error {
+    #[error("Invalid name: {0}")]
+    InvalidName(String),
+    #[error("Missing separator")]
+    MissingSeparator,
+    #[error("Invalid pubkey len: {0}")]
+    InvalidPubKeyLen(usize),
+    #[error("VerifyingKey error: {0}")]
+    InvalidVerifyingKey(ed25519_dalek::SignatureError),
+    #[error("Unable to base64-decode pubkey: {0}")]
+    DecodeError(String),
+}
+
+impl Display for PubKey {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(
+            f,
+            "{}:{}",
+            self.name,
+            BASE64.encode(self.verifying_key.as_bytes())
+        )
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use data_encoding::BASE64;
+    use ed25519_dalek::PUBLIC_KEY_LENGTH;
+    use rstest::rstest;
+
+    use crate::narinfo::Signature;
+
+    use super::PubKey;
+    const FINGERPRINT: &str = "1;/nix/store/syd87l2rxw8cbsxmxl853h0r6pdwhwjr-curl-7.82.0-bin;sha256:1b4sb93wp679q4zx9k1ignby1yna3z7c4c2ri3wphylbc2dwsys0;196040;/nix/store/0jqd0rlxzra1rs38rdxl43yh6rxchgc6-curl-7.82.0,/nix/store/6w8g7njm4mck5dmjxws0z1xnrxvl81xa-glibc-2.34-115,/nix/store/j5jxw3iy7bbz4a57fh9g2xm2gxmyal8h-zlib-1.2.12,/nix/store/yxvjs9drzsphm9pcf42a4byzj1kb9m7k-openssl-1.1.1n";
+
+    #[rstest]
+    #[case::cache_nixos_org("cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=", "cache.nixos.org-1", &BASE64.decode(b"6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=").unwrap()[..].try_into().unwrap())]
+    #[case::cache_nixos_org_different_name("cheesecake:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=", "cheesecake", &BASE64.decode(b"6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=").unwrap()[..].try_into().unwrap())]
+    #[case::test_1("test1:tLAEn+EeaBUJYqEpTd2yeerr7Ic6+0vWe+aXL/vYUpE=", "test1", &BASE64.decode(b"tLAEn+EeaBUJYqEpTd2yeerr7Ic6+0vWe+aXL/vYUpE=").unwrap()[..].try_into().unwrap())]
+    fn parse(
+        #[case] input: &'static str,
+        #[case] exp_name: &'static str,
+        #[case] exp_verifying_key_bytes: &[u8; PUBLIC_KEY_LENGTH],
+    ) {
+        let pubkey = PubKey::parse(input).expect("must parse");
+        assert_eq!(exp_name, pubkey.name());
+        assert_eq!(exp_verifying_key_bytes, pubkey.verifying_key.as_bytes());
+    }
+
+    #[rstest]
+    #[case::empty_name("6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=")]
+    #[case::missing_padding("cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY")]
+    #[case::wrong_length("cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDS")]
+    fn parse_fail(#[case] input: &'static str) {
+        PubKey::parse(input).expect_err("must fail");
+    }
+
+    #[rstest]
+    #[case::correct_cache_nixos_org("cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=", FINGERPRINT, "cache.nixos.org-1:TsTTb3WGTZKphvYdBHXwo6weVILmTytUjLB+vcX89fOjjRicCHmKA4RCPMVLkj6TMJ4GMX3HPVWRdD1hkeKZBQ==", true)]
+    #[case::wrong_name_mismatch("cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=", FINGERPRINT, "cache.nixos.org:TsTTb3WGTZKphvYdBHXwo6weVILmTytUjLB+vcX89fOjjRicCHmKA4RCPMVLkj6TMJ4GMX3HPVWRdD1hkeKZBQ==", false)]
+    fn verify(
+        #[case] pubkey_str: &'static str,
+        #[case] fingerprint: &'static str,
+        #[case] signature_str: &'static str,
+        #[case] expected: bool,
+    ) {
+        let pubkey = PubKey::parse(pubkey_str).expect("must parse");
+        let signature = Signature::parse(signature_str).expect("must parse");
+
+        assert_eq!(expected, pubkey.verify(fingerprint, &signature));
+    }
+}
diff --git a/tvix/nix-compat/src/narinfo/signature.rs b/tvix/nix-compat/src/narinfo/signature.rs
new file mode 100644
index 0000000000..fd197e771d
--- /dev/null
+++ b/tvix/nix-compat/src/narinfo/signature.rs
@@ -0,0 +1,184 @@
+use std::fmt::{self, Display};
+
+use data_encoding::BASE64;
+use ed25519_dalek::SIGNATURE_LENGTH;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub struct Signature<'a> {
+    name: &'a str,
+    bytes: [u8; SIGNATURE_LENGTH],
+}
+
+impl<'a> Signature<'a> {
+    pub fn new(name: &'a str, bytes: [u8; SIGNATURE_LENGTH]) -> Self {
+        Self { name, bytes }
+    }
+
+    pub fn parse(input: &'a str) -> Result<Self, Error> {
+        let (name, bytes64) = input.split_once(':').ok_or(Error::MissingSeparator)?;
+
+        if name.is_empty()
+            || !name
+                .chars()
+                .all(|c| char::is_alphanumeric(c) || c == '-' || c == '.')
+        {
+            return Err(Error::InvalidName(name.to_string()));
+        }
+
+        if bytes64.len() != BASE64.encode_len(SIGNATURE_LENGTH) {
+            return Err(Error::InvalidSignatureLen(bytes64.len()));
+        }
+
+        let mut bytes = [0; SIGNATURE_LENGTH];
+        let mut buf = [0; SIGNATURE_LENGTH + 2];
+        match BASE64.decode_mut(bytes64.as_bytes(), &mut buf) {
+            Ok(SIGNATURE_LENGTH) => bytes.copy_from_slice(&buf[..SIGNATURE_LENGTH]),
+            Ok(_) => unreachable!(),
+            // keeping DecodePartial gets annoying lifetime-wise
+            Err(_) => return Err(Error::DecodeError(input.to_string())),
+        }
+
+        Ok(Signature { name, bytes })
+    }
+
+    pub fn name(&self) -> &'a str {
+        self.name
+    }
+
+    pub fn bytes(&self) -> &[u8; SIGNATURE_LENGTH] {
+        &self.bytes
+    }
+
+    /// For a given fingerprint and ed25519 verifying key, ensure if the signature is valid.
+    pub fn verify(&self, fingerprint: &[u8], verifying_key: &ed25519_dalek::VerifyingKey) -> bool {
+        let signature = ed25519_dalek::Signature::from_bytes(self.bytes());
+
+        verifying_key.verify_strict(fingerprint, &signature).is_ok()
+    }
+}
+
+impl<'de: 'a, 'a> Deserialize<'de> for Signature<'a> {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: serde::Deserializer<'de>,
+    {
+        let str: &'de str = Deserialize::deserialize(deserializer)?;
+        Self::parse(str).map_err(|_| {
+            serde::de::Error::invalid_value(serde::de::Unexpected::Str(str), &"Signature")
+        })
+    }
+}
+
+impl<'a> Serialize for Signature<'a> {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: serde::Serializer,
+    {
+        let string: String = self.to_string();
+
+        string.serialize(serializer)
+    }
+}
+
+#[derive(Debug, thiserror::Error)]
+pub enum Error {
+    #[error("Invalid name: {0}")]
+    InvalidName(String),
+    #[error("Missing separator")]
+    MissingSeparator,
+    #[error("Invalid signature len: (expected {} b64-encoded, got {}", BASE64.encode_len(SIGNATURE_LENGTH), .0)]
+    InvalidSignatureLen(usize),
+    #[error("Unable to base64-decode signature: {0}")]
+    DecodeError(String),
+}
+
+impl Display for Signature<'_> {
+    fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result {
+        write!(w, "{}:{}", self.name, BASE64.encode(&self.bytes))
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use data_encoding::BASE64;
+    use ed25519_dalek::VerifyingKey;
+    use hex_literal::hex;
+    use lazy_static::lazy_static;
+
+    use super::Signature;
+    use rstest::rstest;
+
+    const FINGERPRINT: &str = "1;/nix/store/syd87l2rxw8cbsxmxl853h0r6pdwhwjr-curl-7.82.0-bin;sha256:1b4sb93wp679q4zx9k1ignby1yna3z7c4c2ri3wphylbc2dwsys0;196040;/nix/store/0jqd0rlxzra1rs38rdxl43yh6rxchgc6-curl-7.82.0,/nix/store/6w8g7njm4mck5dmjxws0z1xnrxvl81xa-glibc-2.34-115,/nix/store/j5jxw3iy7bbz4a57fh9g2xm2gxmyal8h-zlib-1.2.12,/nix/store/yxvjs9drzsphm9pcf42a4byzj1kb9m7k-openssl-1.1.1n";
+
+    // The signing key labelled as `cache.nixos.org-1`,
+    lazy_static! {
+        static ref PUB_CACHE_NIXOS_ORG_1: VerifyingKey = ed25519_dalek::VerifyingKey::from_bytes(
+            BASE64
+                .decode(b"6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=")
+                .unwrap()[..]
+                .try_into()
+                .unwrap()
+        )
+        .unwrap();
+        static ref PUB_TEST_1: VerifyingKey = ed25519_dalek::VerifyingKey::from_bytes(
+            BASE64
+                .decode(b"tLAEn+EeaBUJYqEpTd2yeerr7Ic6+0vWe+aXL/vYUpE=")
+                .unwrap()[..]
+                .try_into()
+                .unwrap()
+        )
+        .unwrap();
+    }
+
+    #[rstest]
+    #[case::valid_cache_nixos_org_1(&PUB_CACHE_NIXOS_ORG_1, &"cache.nixos.org-1:TsTTb3WGTZKphvYdBHXwo6weVILmTytUjLB+vcX89fOjjRicCHmKA4RCPMVLkj6TMJ4GMX3HPVWRdD1hkeKZBQ==", FINGERPRINT, true)]
+    #[case::valid_test1(&PUB_CACHE_NIXOS_ORG_1, &"cache.nixos.org-1:TsTTb3WGTZKphvYdBHXwo6weVILmTytUjLB+vcX89fOjjRicCHmKA4RCPMVLkj6TMJ4GMX3HPVWRdD1hkeKZBQ==", FINGERPRINT, true)]
+    #[case::valid_cache_nixos_org_different_name(&PUB_CACHE_NIXOS_ORG_1, &"cache.nixos.org-2:TsTTb3WGTZKphvYdBHXwo6weVILmTytUjLB+vcX89fOjjRicCHmKA4RCPMVLkj6TMJ4GMX3HPVWRdD1hkeKZBQ==", FINGERPRINT, true)]
+    #[case::fail_invalid_cache_nixos_org_1_signature(&PUB_CACHE_NIXOS_ORG_1, &"cache.nixos.org-1:TsTTb000000000000000000000000ytUjLB+vcX89fOjjRicCHmKA4RCPMVLkj6TMJ4GMX3HPVWRdD1hkeKZBQ==", FINGERPRINT, false)]
+    #[case::fail_valid_sig_but_wrong_fp_cache_nixos_org_1(&PUB_CACHE_NIXOS_ORG_1, &"cache.nixos.org-1:TsTTb3WGTZKphvYdBHXwo6weVILmTytUjLB+vcX89fOjjRicCHmKA4RCPMVLkj6TMJ4GMX3HPVWRdD1hkeKZBQ==", &FINGERPRINT[0..5], false)]
+    fn verify_sigs(
+        #[case] verifying_key: &VerifyingKey,
+        #[case] sig_str: &'static str,
+        #[case] fp: &str,
+        #[case] expect_valid: bool,
+    ) {
+        let sig = Signature::parse(sig_str).expect("must parse");
+        assert_eq!(expect_valid, sig.verify(fp.as_bytes(), verifying_key));
+    }
+
+    #[rstest]
+    #[case::wrong_length("cache.nixos.org-1:o1DTsjCz0PofLJ216P2RBuSulI8BAb6zHxWE4N+tzlcELk5Uk/GO2SCxWTRN5wJutLZZ+cHTMdWqOHF8")]
+    #[case::wrong_name_newline("test\n:u01BybwQhyI5H1bW1EIWXssMDhDDIvXOG5uh8Qzgdyjz6U1qg6DHhMAvXZOUStIj6X5t4/ufFgR8i3fjf0bMAw==")]
+    #[case::wrong_name_space("test :u01BybwQhyI5H1bW1EIWXssMDhDDIvXOG5uh8Qzgdyjz6U1qg6DHhMAvXZOUStIj6X5t4/ufFgR8i3fjf0bMAw==")]
+    #[case::empty_name(
+        ":u01BybwQhyI5H1bW1EIWXssMDhDDIvXOG5uh8Qzgdyjz6U1qg6DHhMAvXZOUStIj6X5t4/ufFgR8i3fjf0bMAw=="
+    )]
+    #[case::b64_only(
+        "u01BybwQhyI5H1bW1EIWXssMDhDDIvXOG5uh8Qzgdyjz6U1qg6DHhMAvXZOUStIj6X5t4/ufFgR8i3fjf0bMAw=="
+    )]
+    fn parse_fail(#[case] input: &'static str) {
+        Signature::parse(input).expect_err("must fail");
+    }
+
+    #[test]
+    fn serialize_deserialize() {
+        let signature_actual = Signature {
+            name: "cache.nixos.org-1",
+            bytes: hex!(
+                r#"4e c4 d3 6f 75 86 4d 92  a9 86 f6 1d 04 75 f0 a3
+                   ac 1e 54 82 e6 4f 2b 54  8c b0 7e bd c5 fc f5 f3
+                   a3 8d 18 9c 08 79 8a 03  84 42 3c c5 4b 92 3e 93
+                   30 9e 06 31 7d c7 3d 55  91 74 3d 61 91 e2 99 05"#
+            ),
+        };
+        let signature_str_json = "\"cache.nixos.org-1:TsTTb3WGTZKphvYdBHXwo6weVILmTytUjLB+vcX89fOjjRicCHmKA4RCPMVLkj6TMJ4GMX3HPVWRdD1hkeKZBQ==\"";
+
+        let serialized = serde_json::to_string(&signature_actual).expect("must serialize");
+        assert_eq!(signature_str_json, &serialized);
+
+        let deserialized: Signature<'_> =
+            serde_json::from_str(signature_str_json).expect("must deserialize");
+        assert_eq!(&signature_actual, &deserialized);
+    }
+}
diff --git a/tvix/nix-compat/src/nix_daemon/mod.rs b/tvix/nix-compat/src/nix_daemon/mod.rs
new file mode 100644
index 0000000000..fe652377d1
--- /dev/null
+++ b/tvix/nix-compat/src/nix_daemon/mod.rs
@@ -0,0 +1,4 @@
+pub mod worker_protocol;
+
+mod protocol_version;
+pub use protocol_version::ProtocolVersion;
diff --git a/tvix/nix-compat/src/nix_daemon/protocol_version.rs b/tvix/nix-compat/src/nix_daemon/protocol_version.rs
new file mode 100644
index 0000000000..8fd2b085c9
--- /dev/null
+++ b/tvix/nix-compat/src/nix_daemon/protocol_version.rs
@@ -0,0 +1,123 @@
+/// Protocol versions are represented as a u16.
+/// The upper 8 bits are the major version, the lower bits the minor.
+/// This is not aware of any endianness, use [crate::wire::read_u64] to get an
+/// u64 first, and the try_from() impl from here if you're receiving over the
+/// Nix Worker protocol.
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub struct ProtocolVersion(u16);
+
+impl ProtocolVersion {
+    pub const fn from_parts(major: u8, minor: u8) -> Self {
+        Self(((major as u16) << 8) | minor as u16)
+    }
+
+    pub fn major(&self) -> u8 {
+        ((self.0 & 0xff00) >> 8) as u8
+    }
+
+    pub fn minor(&self) -> u8 {
+        (self.0 & 0x00ff) as u8
+    }
+}
+
+impl PartialOrd for ProtocolVersion {
+    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+        Some(self.cmp(other))
+    }
+}
+
+impl Ord for ProtocolVersion {
+    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+        match self.major().cmp(&other.major()) {
+            std::cmp::Ordering::Less => std::cmp::Ordering::Less,
+            std::cmp::Ordering::Greater => std::cmp::Ordering::Greater,
+            std::cmp::Ordering::Equal => {
+                // same major, compare minor
+                self.minor().cmp(&other.minor())
+            }
+        }
+    }
+}
+
+impl From<u16> for ProtocolVersion {
+    fn from(value: u16) -> Self {
+        Self::from_parts(((value & 0xff00) >> 8) as u8, (value & 0x00ff) as u8)
+    }
+}
+
+impl TryFrom<u64> for ProtocolVersion {
+    type Error = &'static str;
+
+    fn try_from(value: u64) -> Result<Self, Self::Error> {
+        if value & !0xffff != 0 {
+            return Err("only two least significant bits might be populated");
+        }
+
+        Ok((value as u16).into())
+    }
+}
+
+impl From<ProtocolVersion> for u16 {
+    fn from(value: ProtocolVersion) -> Self {
+        value.0
+    }
+}
+
+impl From<ProtocolVersion> for u64 {
+    fn from(value: ProtocolVersion) -> Self {
+        value.0 as u64
+    }
+}
+
+impl std::fmt::Display for ProtocolVersion {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "{}.{}", self.major(), self.minor())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::ProtocolVersion;
+
+    #[test]
+    fn from_parts() {
+        let version = ProtocolVersion::from_parts(1, 37);
+        assert_eq!(version.major(), 1, "correct major");
+        assert_eq!(version.minor(), 37, "correct minor");
+        assert_eq!("1.37", &version.to_string(), "to_string");
+
+        assert_eq!(0x0125, Into::<u16>::into(version));
+        assert_eq!(0x0125, Into::<u64>::into(version));
+    }
+
+    #[test]
+    fn from_u16() {
+        let version = ProtocolVersion::from(0x0125_u16);
+        assert_eq!("1.37", &version.to_string());
+    }
+
+    #[test]
+    fn from_u64() {
+        let version = ProtocolVersion::try_from(0x0125_u64).expect("must succeed");
+        assert_eq!("1.37", &version.to_string());
+    }
+
+    /// This contains data in higher bits, which should fail.
+    #[test]
+    fn from_u64_fail() {
+        ProtocolVersion::try_from(0xaa0125_u64).expect_err("must fail");
+    }
+
+    #[test]
+    fn ord() {
+        let v0_37 = ProtocolVersion::from_parts(0, 37);
+        let v1_37 = ProtocolVersion::from_parts(1, 37);
+        let v1_40 = ProtocolVersion::from_parts(1, 40);
+
+        assert!(v0_37 < v1_37);
+        assert!(v1_37 > v0_37);
+        assert!(v1_37 < v1_40);
+        assert!(v1_40 > v1_37);
+        assert!(v1_40 <= v1_40);
+    }
+}
diff --git a/tvix/nix-compat/src/nix_daemon/worker_protocol.rs b/tvix/nix-compat/src/nix_daemon/worker_protocol.rs
new file mode 100644
index 0000000000..7e3adc0db2
--- /dev/null
+++ b/tvix/nix-compat/src/nix_daemon/worker_protocol.rs
@@ -0,0 +1,434 @@
+use std::{
+    collections::HashMap,
+    io::{Error, ErrorKind},
+};
+
+use enum_primitive_derive::Primitive;
+use num_traits::{FromPrimitive, ToPrimitive};
+use tokio::io::{AsyncReadExt, AsyncWriteExt};
+
+use crate::wire;
+
+use super::ProtocolVersion;
+
+static WORKER_MAGIC_1: u64 = 0x6e697863; // "nixc"
+static WORKER_MAGIC_2: u64 = 0x6478696f; // "dxio"
+pub static STDERR_LAST: u64 = 0x616c7473; // "alts"
+
+/// | Nix version     | Protocol |
+/// |-----------------|----------|
+/// | 0.11            | 1.02     |
+/// | 0.12            | 1.04     |
+/// | 0.13            | 1.05     |
+/// | 0.14            | 1.05     |
+/// | 0.15            | 1.05     |
+/// | 0.16            | 1.06     |
+/// | 1.0             | 1.10     |
+/// | 1.1             | 1.11     |
+/// | 1.2             | 1.12     |
+/// | 1.3 - 1.5.3     | 1.13     |
+/// | 1.6 - 1.10      | 1.14     |
+/// | 1.11 - 1.11.16  | 1.15     |
+/// | 2.0 - 2.0.4     | 1.20     |
+/// | 2.1 - 2.3.18    | 1.21     |
+/// | 2.4 - 2.6.1     | 1.32     |
+/// | 2.7.0           | 1.33     |
+/// | 2.8.0 - 2.14.1  | 1.34     |
+/// | 2.15.0 - 2.19.4 | 1.35     |
+/// | 2.20.0 - 2.22.0 | 1.37     |
+static PROTOCOL_VERSION: ProtocolVersion = ProtocolVersion::from_parts(1, 37);
+
+/// Max length of a Nix setting name/value. In bytes.
+///
+/// This value has been arbitrarily choosen after looking the nix.conf
+/// manpage. Don't hesitate to increase it if it's too limiting.
+pub static MAX_SETTING_SIZE: usize = 1024;
+
+/// Worker Operation
+///
+/// These operations are encoded as unsigned 64 bits before being sent
+/// to the wire. See the [read_op] and
+/// [write_op] operations to serialize/deserialize the
+/// operation on the wire.
+///
+/// Note: for now, we're using the Nix 2.20 operation description. The
+/// operations marked as obsolete are obsolete for Nix 2.20, not
+/// necessarily for Nix 2.3. We'll revisit this later on.
+#[derive(Debug, PartialEq, Primitive)]
+pub enum Operation {
+    IsValidPath = 1,
+    HasSubstitutes = 3,
+    QueryPathHash = 4,   // obsolete
+    QueryReferences = 5, // obsolete
+    QueryReferrers = 6,
+    AddToStore = 7,
+    AddTextToStore = 8, // obsolete since 1.25, Nix 3.0. Use WorkerProto::Op::AddToStore
+    BuildPaths = 9,
+    EnsurePath = 10,
+    AddTempRoot = 11,
+    AddIndirectRoot = 12,
+    SyncWithGC = 13,
+    FindRoots = 14,
+    ExportPath = 16,   // obsolete
+    QueryDeriver = 18, // obsolete
+    SetOptions = 19,
+    CollectGarbage = 20,
+    QuerySubstitutablePathInfo = 21,
+    QueryDerivationOutputs = 22, // obsolete
+    QueryAllValidPaths = 23,
+    QueryFailedPaths = 24,
+    ClearFailedPaths = 25,
+    QueryPathInfo = 26,
+    ImportPaths = 27,                // obsolete
+    QueryDerivationOutputNames = 28, // obsolete
+    QueryPathFromHashPart = 29,
+    QuerySubstitutablePathInfos = 30,
+    QueryValidPaths = 31,
+    QuerySubstitutablePaths = 32,
+    QueryValidDerivers = 33,
+    OptimiseStore = 34,
+    VerifyStore = 35,
+    BuildDerivation = 36,
+    AddSignatures = 37,
+    NarFromPath = 38,
+    AddToStoreNar = 39,
+    QueryMissing = 40,
+    QueryDerivationOutputMap = 41,
+    RegisterDrvOutput = 42,
+    QueryRealisation = 43,
+    AddMultipleToStore = 44,
+    AddBuildLog = 45,
+    BuildPathsWithResults = 46,
+    AddPermRoot = 47,
+}
+
+/// Log verbosity. In the Nix wire protocol, the client requests a
+/// verbosity level to the daemon, which in turns does not produce any
+/// log below this verbosity.
+#[derive(Debug, PartialEq, Primitive)]
+pub enum Verbosity {
+    LvlError = 0,
+    LvlWarn = 1,
+    LvlNotice = 2,
+    LvlInfo = 3,
+    LvlTalkative = 4,
+    LvlChatty = 5,
+    LvlDebug = 6,
+    LvlVomit = 7,
+}
+
+/// Settings requested by the client. These settings are applied to a
+/// connection to between the daemon and a client.
+#[derive(Debug, PartialEq)]
+pub struct ClientSettings {
+    pub keep_failed: bool,
+    pub keep_going: bool,
+    pub try_fallback: bool,
+    pub verbosity: Verbosity,
+    pub max_build_jobs: u64,
+    pub max_silent_time: u64,
+    pub verbose_build: bool,
+    pub build_cores: u64,
+    pub use_substitutes: bool,
+    /// Key/Value dictionary in charge of overriding the settings set
+    /// by the Nix config file.
+    ///
+    /// Some settings can be safely overidden,
+    /// some other require the user running the Nix client to be part
+    /// of the trusted users group.
+    pub overrides: HashMap<String, String>,
+}
+
+/// Reads the client settings from the wire.
+///
+/// Note: this function **only** reads the settings. It does not
+/// manage the log state with the daemon. You'll have to do that on
+/// your own. A minimal log implementation will consist in sending
+/// back [STDERR_LAST] to the client after reading the client
+/// settings.
+///
+/// FUTUREWORK: write serialization.
+pub async fn read_client_settings<R: AsyncReadExt + Unpin>(
+    r: &mut R,
+    client_version: ProtocolVersion,
+) -> std::io::Result<ClientSettings> {
+    let keep_failed = r.read_u64_le().await? != 0;
+    let keep_going = r.read_u64_le().await? != 0;
+    let try_fallback = r.read_u64_le().await? != 0;
+    let verbosity_uint = r.read_u64_le().await?;
+    let verbosity = Verbosity::from_u64(verbosity_uint).ok_or_else(|| {
+        Error::new(
+            ErrorKind::InvalidData,
+            format!("Can't convert integer {} to verbosity", verbosity_uint),
+        )
+    })?;
+    let max_build_jobs = r.read_u64_le().await?;
+    let max_silent_time = r.read_u64_le().await?;
+    _ = r.read_u64_le().await?; // obsolete useBuildHook
+    let verbose_build = r.read_u64_le().await? != 0;
+    _ = r.read_u64_le().await?; // obsolete logType
+    _ = r.read_u64_le().await?; // obsolete printBuildTrace
+    let build_cores = r.read_u64_le().await?;
+    let use_substitutes = r.read_u64_le().await? != 0;
+    let mut overrides = HashMap::new();
+    if client_version.minor() >= 12 {
+        let num_overrides = r.read_u64_le().await?;
+        for _ in 0..num_overrides {
+            let name = wire::read_string(r, 0..=MAX_SETTING_SIZE).await?;
+            let value = wire::read_string(r, 0..=MAX_SETTING_SIZE).await?;
+            overrides.insert(name, value);
+        }
+    }
+    Ok(ClientSettings {
+        keep_failed,
+        keep_going,
+        try_fallback,
+        verbosity,
+        max_build_jobs,
+        max_silent_time,
+        verbose_build,
+        build_cores,
+        use_substitutes,
+        overrides,
+    })
+}
+
+/// Performs the initial handshake the server is sending to a connecting client.
+///
+/// During the handshake, the client first send a magic u64, to which
+/// the daemon needs to respond with another magic u64.
+/// Then, the daemon retrieves the client version, and discards a bunch of now
+/// obsolete data.
+///
+/// # Arguments
+///
+/// * conn: connection with the Nix client.
+/// * nix_version: semantic version of the Nix daemon. "2.18.2" for
+///   instance.
+/// * trusted: trust level of the Nix client.
+///
+/// # Return
+///
+/// The protocol version of the client.
+pub async fn server_handshake_client<'a, RW: 'a>(
+    mut conn: &'a mut RW,
+    nix_version: &str,
+    trusted: Trust,
+) -> std::io::Result<ProtocolVersion>
+where
+    &'a mut RW: AsyncReadExt + AsyncWriteExt + Unpin,
+{
+    let worker_magic_1 = conn.read_u64_le().await?;
+    if worker_magic_1 != WORKER_MAGIC_1 {
+        Err(std::io::Error::new(
+            ErrorKind::InvalidData,
+            format!("Incorrect worker magic number received: {}", worker_magic_1),
+        ))
+    } else {
+        conn.write_u64_le(WORKER_MAGIC_2).await?;
+        conn.write_u64_le(PROTOCOL_VERSION.into()).await?;
+        conn.flush().await?;
+        let client_version = conn.read_u64_le().await?;
+        // Parse into ProtocolVersion.
+        let client_version: ProtocolVersion = client_version
+            .try_into()
+            .map_err(|e| Error::new(ErrorKind::Unsupported, e))?;
+        if client_version < ProtocolVersion::from_parts(1, 10) {
+            return Err(Error::new(
+                ErrorKind::Unsupported,
+                format!("The nix client version {} is too old", client_version),
+            ));
+        }
+        if client_version.minor() >= 14 {
+            // Obsolete CPU affinity.
+            let read_affinity = conn.read_u64_le().await?;
+            if read_affinity != 0 {
+                let _cpu_affinity = conn.read_u64_le().await?;
+            };
+        }
+        if client_version.minor() >= 11 {
+            // Obsolete reserveSpace
+            let _reserve_space = conn.read_u64_le().await?;
+        }
+        if client_version.minor() >= 33 {
+            // Nix version. We're plain lying, we're not Nix, but eh…
+            // Setting it to the 2.3 lineage. Not 100% sure this is a
+            // good idea.
+            wire::write_bytes(&mut conn, nix_version).await?;
+            conn.flush().await?;
+        }
+        if client_version.minor() >= 35 {
+            write_worker_trust_level(&mut conn, trusted).await?;
+        }
+        Ok(client_version)
+    }
+}
+
+/// Read a worker [Operation] from the wire.
+pub async fn read_op<R: AsyncReadExt + Unpin>(r: &mut R) -> std::io::Result<Operation> {
+    let op_number = r.read_u64_le().await?;
+    Operation::from_u64(op_number).ok_or(Error::new(
+        ErrorKind::InvalidData,
+        format!("Invalid OP number {}", op_number),
+    ))
+}
+
+/// Write a worker [Operation] to the wire.
+pub async fn write_op<W: AsyncWriteExt + Unpin>(w: &mut W, op: &Operation) -> std::io::Result<()> {
+    let op = Operation::to_u64(op).ok_or(Error::new(
+        ErrorKind::Other,
+        format!("Can't convert the OP {:?} to u64", op),
+    ))?;
+    w.write_u64(op).await
+}
+
+#[derive(Debug, PartialEq)]
+pub enum Trust {
+    Trusted,
+    NotTrusted,
+}
+
+/// Write the worker [Trust] level to the wire.
+///
+/// Cpp Nix has a legacy third option: u8 0. This option is meant to
+/// be used as a backward compatible measure. Since we're not
+/// targetting protocol versions pre-dating the trust notion, we
+/// decided not to implement it here.
+pub async fn write_worker_trust_level<W>(conn: &mut W, t: Trust) -> std::io::Result<()>
+where
+    W: AsyncReadExt + AsyncWriteExt + Unpin,
+{
+    match t {
+        Trust::Trusted => conn.write_u64_le(1).await,
+        Trust::NotTrusted => conn.write_u64_le(2).await,
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use hex_literal::hex;
+    use tokio_test::io::Builder;
+
+    #[tokio::test]
+    async fn test_init_hanshake() {
+        let mut test_conn = tokio_test::io::Builder::new()
+            .read(&WORKER_MAGIC_1.to_le_bytes())
+            .write(&WORKER_MAGIC_2.to_le_bytes())
+            .write(&[37, 1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
+            // Let's say the client is in sync with the daemon
+            // protocol-wise
+            .read(&[37, 1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
+            // cpu affinity
+            .read(&[0; 8])
+            // reservespace
+            .read(&[0; 8])
+            // version (size)
+            .write(&[0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
+            // version (data == 2.18.2 + padding)
+            .write(&[50, 46, 49, 56, 46, 50, 0, 0])
+            // Trusted (1 == client trusted
+            .write(&[1, 0, 0, 0, 0, 0, 0, 0])
+            .build();
+        let client_version = server_handshake_client(&mut test_conn, "2.18.2", Trust::Trusted)
+            .await
+            .unwrap();
+
+        assert_eq!(client_version, PROTOCOL_VERSION)
+    }
+
+    #[tokio::test]
+    async fn test_read_client_settings_without_overrides() {
+        // Client settings bits captured from a Nix 2.3.17 run w/ sockdump (protocol version 21).
+        let wire_bits = hex!(
+            "00 00 00 00 00 00 00 00 \
+             00 00 00 00 00 00 00 00 \
+             00 00 00 00 00 00 00 00 \
+             02 00 00 00 00 00 00 00 \
+             10 00 00 00 00 00 00 00 \
+             00 00 00 00 00 00 00 00 \
+             01 00 00 00 00 00 00 00 \
+             00 00 00 00 00 00 00 00 \
+             00 00 00 00 00 00 00 00 \
+             00 00 00 00 00 00 00 00 \
+             00 00 00 00 00 00 00 00 \
+             01 00 00 00 00 00 00 00 \
+             00 00 00 00 00 00 00 00"
+        );
+        let mut mock = Builder::new().read(&wire_bits).build();
+        let settings = read_client_settings(&mut mock, ProtocolVersion::from_parts(1, 21))
+            .await
+            .expect("should parse");
+        let expected = ClientSettings {
+            keep_failed: false,
+            keep_going: false,
+            try_fallback: false,
+            verbosity: Verbosity::LvlNotice,
+            max_build_jobs: 16,
+            max_silent_time: 0,
+            verbose_build: false,
+            build_cores: 0,
+            use_substitutes: true,
+            overrides: HashMap::new(),
+        };
+        assert_eq!(settings, expected);
+    }
+
+    #[tokio::test]
+    async fn test_read_client_settings_with_overrides() {
+        // Client settings bits captured from a Nix 2.3.17 run w/ sockdump (protocol version 21).
+        let wire_bits = hex!(
+            "00 00 00 00 00 00 00 00 \
+             00 00 00 00 00 00 00 00 \
+             00 00 00 00 00 00 00 00 \
+             02 00 00 00 00 00 00 00 \
+             10 00 00 00 00 00 00 00 \
+             00 00 00 00 00 00 00 00 \
+             01 00 00 00 00 00 00 00 \
+             00 00 00 00 00 00 00 00 \
+             00 00 00 00 00 00 00 00 \
+             00 00 00 00 00 00 00 00 \
+             00 00 00 00 00 00 00 00 \
+             01 00 00 00 00 00 00 00 \
+             02 00 00 00 00 00 00 00 \
+             0c 00 00 00 00 00 00 00 \
+             61 6c 6c 6f 77 65 64 2d \
+             75 72 69 73 00 00 00 00 \
+             1e 00 00 00 00 00 00 00 \
+             68 74 74 70 73 3a 2f 2f \
+             62 6f 72 64 65 61 75 78 \
+             2e 67 75 69 78 2e 67 6e \
+             75 2e 6f 72 67 2f 00 00 \
+             0d 00 00 00 00 00 00 00 \
+             61 6c 6c 6f 77 65 64 2d \
+             75 73 65 72 73 00 00 00 \
+             0b 00 00 00 00 00 00 00 \
+             6a 65 61 6e 20 70 69 65 \
+             72 72 65 00 00 00 00 00"
+        );
+        let mut mock = Builder::new().read(&wire_bits).build();
+        let settings = read_client_settings(&mut mock, ProtocolVersion::from_parts(1, 21))
+            .await
+            .expect("should parse");
+        let overrides = HashMap::from([
+            (
+                String::from("allowed-uris"),
+                String::from("https://bordeaux.guix.gnu.org/"),
+            ),
+            (String::from("allowed-users"), String::from("jean pierre")),
+        ]);
+        let expected = ClientSettings {
+            keep_failed: false,
+            keep_going: false,
+            try_fallback: false,
+            verbosity: Verbosity::LvlNotice,
+            max_build_jobs: 16,
+            max_silent_time: 0,
+            verbose_build: false,
+            build_cores: 0,
+            use_substitutes: true,
+            overrides,
+        };
+        assert_eq!(settings, expected);
+    }
+}
diff --git a/tvix/nix-compat/src/nixbase32.rs b/tvix/nix-compat/src/nixbase32.rs
new file mode 100644
index 0000000000..b7ffc1dc2b
--- /dev/null
+++ b/tvix/nix-compat/src/nixbase32.rs
@@ -0,0 +1,206 @@
+//! Implements the slightly odd "base32" encoding that's used in Nix.
+//!
+//! Nix uses a custom alphabet. Contrary to other implementations (RFC4648),
+//! encoding to "nix base32" doesn't use any padding, and reads in characters
+//! in reverse order.
+//!
+//! This is also the main reason why we can't use `data_encoding::Encoding` -
+//! it gets things wrong if there normally would be a need for padding.
+
+use std::fmt::Write;
+
+use data_encoding::{DecodeError, DecodeKind};
+
+const ALPHABET: &[u8; 32] = b"0123456789abcdfghijklmnpqrsvwxyz";
+
+/// Returns encoded input
+pub fn encode(input: &[u8]) -> String {
+    let output_len = encode_len(input.len());
+    let mut output = String::with_capacity(output_len);
+
+    for n in (0..output_len).rev() {
+        let b = n * 5; // bit offset within the entire input
+        let i = b / 8; // input byte index
+        let j = b % 8; // bit offset within that input byte
+
+        // 5-bit words aren't aligned to bytes
+        // we can only read byte-aligned units
+        // read 16 bits then shift and mask to 5
+        let c = {
+            let mut word = input[i] as u16;
+            if let Some(&msb) = input.get(i + 1) {
+                word |= (msb as u16) << 8;
+            }
+            (word >> j) & 0x1f
+        };
+
+        output.write_char(ALPHABET[c as usize] as char).unwrap();
+    }
+
+    output
+}
+
+/// This maps a nixbase32-encoded character to its binary representation, which
+/// is also the index of the character in the alphabet. Invalid characters are
+/// mapped to 0xFF, which is itself an invalid value.
+const BASE32_ORD: [u8; 256] = {
+    let mut ord = [0xFF; 256];
+    let mut alphabet = ALPHABET.as_slice();
+    let mut i = 0;
+
+    while let &[c, ref tail @ ..] = alphabet {
+        ord[c as usize] = i;
+        alphabet = tail;
+        i += 1;
+    }
+
+    ord
+};
+
+/// Returns decoded input
+pub fn decode(input: impl AsRef<[u8]>) -> Result<Vec<u8>, DecodeError> {
+    let input = input.as_ref();
+
+    let output_len = decode_len(input.len());
+    let mut output: Vec<u8> = vec![0x00; output_len];
+
+    decode_inner(input, &mut output)?;
+    Ok(output)
+}
+
+pub fn decode_fixed<const K: usize>(input: impl AsRef<[u8]>) -> Result<[u8; K], DecodeError> {
+    let input = input.as_ref();
+
+    if input.len() != encode_len(K) {
+        return Err(DecodeError {
+            position: input.len().min(encode_len(K)),
+            kind: DecodeKind::Length,
+        });
+    }
+
+    let mut output = [0; K];
+    decode_inner(input, &mut output)?;
+    Ok(output)
+}
+
+fn decode_inner(input: &[u8], output: &mut [u8]) -> Result<(), DecodeError> {
+    // loop over all characters in reverse, and keep the iteration count in n.
+    let mut carry = 0;
+    let mut mask = 0;
+    for (n, &c) in input.iter().rev().enumerate() {
+        let b = n * 5;
+        let i = b / 8;
+        let j = b % 8;
+
+        let digit = BASE32_ORD[c as usize];
+        let value = (digit as u16) << j;
+        output[i] |= value as u8 | carry;
+        carry = (value >> 8) as u8;
+
+        mask |= digit;
+    }
+
+    if mask == 0xFF {
+        return Err(DecodeError {
+            position: find_invalid(input),
+            kind: DecodeKind::Symbol,
+        });
+    }
+
+    // if we're at the end, but have a nonzero carry, the encoding is invalid.
+    if carry != 0 {
+        return Err(DecodeError {
+            position: 0,
+            kind: DecodeKind::Trailing,
+        });
+    }
+
+    Ok(())
+}
+
+fn find_invalid(input: &[u8]) -> usize {
+    for (i, &c) in input.iter().enumerate() {
+        if !ALPHABET.contains(&c) {
+            return i;
+        }
+    }
+
+    unreachable!()
+}
+
+/// Returns the decoded length of an input of length len.
+pub const fn decode_len(len: usize) -> usize {
+    (len * 5) / 8
+}
+
+/// Returns the encoded length of an input of length len
+pub const fn encode_len(len: usize) -> usize {
+    (len * 8 + 4) / 5
+}
+
+#[cfg(test)]
+mod tests {
+    use hex_literal::hex;
+    use rstest::rstest;
+
+    #[rstest]
+    #[case::empty_bytes("", &[])]
+    #[case::one_byte("0z", &hex!("1f"))]
+    #[case::store_path("00bgd045z0d4icpbc2yyz4gx48ak44la", &hex!("8a12321522fd91efbd60ebb2481af88580f61600"))]
+    #[case::sha256("0c5b8vw40dy178xlpddw65q9gf1h2186jcc3p4swinwggbllv8mk", &hex!("b3a24de97a8fdbc835b9833169501030b8977031bcb54b3b3ac13740f846ab30"))]
+    #[test]
+    fn encode(#[case] enc: &str, #[case] dec: &[u8]) {
+        assert_eq!(enc, super::encode(dec));
+    }
+
+    #[rstest]
+    #[case::empty_bytes("", Some(&[][..]) )]
+    #[case::one_byte("0z", Some(&hex!("1f")[..]))]
+    #[case::store_path("00bgd045z0d4icpbc2yyz4gx48ak44la", Some(&hex!("8a12321522fd91efbd60ebb2481af88580f61600")[..]))]
+    #[case::sha256("0c5b8vw40dy178xlpddw65q9gf1h2186jcc3p4swinwggbllv8mk", Some(&hex!("b3a24de97a8fdbc835b9833169501030b8977031bcb54b3b3ac13740f846ab30")[..]))]
+    // this is invalid encoding, because it encodes 10 1-bits, so the carry
+    // would be 2 1-bits
+    #[case::invalid_encoding_1("zz", None)]
+    // this is an even more specific example - it'd decode as 00000000 11
+    #[case::invalid_encoding_2("c0", None)]
+    #[test]
+    fn decode(#[case] enc: &str, #[case] dec: Option<&[u8]>) {
+        match dec {
+            Some(dec) => {
+                // The decode needs to match what's passed in dec
+                assert_eq!(dec, super::decode(enc).unwrap());
+            }
+            None => {
+                // the decode needs to be an error
+                assert!(super::decode(enc).is_err());
+            }
+        }
+    }
+
+    #[test]
+    fn decode_fixed() {
+        assert_eq!(
+            super::decode_fixed("00bgd045z0d4icpbc2yyz4gx48ak44la").unwrap(),
+            hex!("8a12321522fd91efbd60ebb2481af88580f61600")
+        );
+        assert_eq!(
+            super::decode_fixed::<32>("00").unwrap_err(),
+            super::DecodeError {
+                position: 2,
+                kind: super::DecodeKind::Length
+            }
+        );
+    }
+
+    #[test]
+    fn encode_len() {
+        assert_eq!(super::encode_len(0), 0);
+        assert_eq!(super::encode_len(20), 32);
+    }
+
+    #[test]
+    fn decode_len() {
+        assert_eq!(super::decode_len(0), 0);
+        assert_eq!(super::decode_len(32), 20);
+    }
+}
diff --git a/tvix/nix-compat/src/nixhash/algos.rs b/tvix/nix-compat/src/nixhash/algos.rs
new file mode 100644
index 0000000000..ac8915314c
--- /dev/null
+++ b/tvix/nix-compat/src/nixhash/algos.rs
@@ -0,0 +1,75 @@
+use std::fmt::Display;
+
+use serde::{Deserialize, Serialize};
+
+use crate::nixhash::Error;
+
+/// This are the hash algorithms supported by cppnix.
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub enum HashAlgo {
+    Md5,
+    Sha1,
+    Sha256,
+    Sha512,
+}
+
+impl HashAlgo {
+    // return the number of bytes in the digest of the given hash algo.
+    pub fn digest_length(&self) -> usize {
+        match self {
+            HashAlgo::Sha1 => 20,
+            HashAlgo::Sha256 => 32,
+            HashAlgo::Sha512 => 64,
+            HashAlgo::Md5 => 16,
+        }
+    }
+}
+
+impl Display for HashAlgo {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match &self {
+            HashAlgo::Md5 => write!(f, "md5"),
+            HashAlgo::Sha1 => write!(f, "sha1"),
+            HashAlgo::Sha256 => write!(f, "sha256"),
+            HashAlgo::Sha512 => write!(f, "sha512"),
+        }
+    }
+}
+
+impl Serialize for HashAlgo {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: serde::Serializer,
+    {
+        serializer.collect_str(&self)
+    }
+}
+
+impl<'de> Deserialize<'de> for HashAlgo {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: serde::Deserializer<'de>,
+    {
+        let s: &str = Deserialize::deserialize(deserializer)?;
+        HashAlgo::try_from(s).map_err(serde::de::Error::custom)
+    }
+}
+
+/// TODO(Raito): this could be automated via macros, I suppose.
+/// But this may be more expensive than just doing it by hand
+/// and ensuring that is kept in sync.
+pub const SUPPORTED_ALGOS: [&str; 4] = ["md5", "sha1", "sha256", "sha512"];
+
+impl TryFrom<&str> for HashAlgo {
+    type Error = Error;
+
+    fn try_from(algo_str: &str) -> Result<Self, Self::Error> {
+        match algo_str {
+            "md5" => Ok(Self::Md5),
+            "sha1" => Ok(Self::Sha1),
+            "sha256" => Ok(Self::Sha256),
+            "sha512" => Ok(Self::Sha512),
+            _ => Err(Error::InvalidAlgo(algo_str.to_string())),
+        }
+    }
+}
diff --git a/tvix/nix-compat/src/nixhash/ca_hash.rs b/tvix/nix-compat/src/nixhash/ca_hash.rs
new file mode 100644
index 0000000000..2bf5f966ce
--- /dev/null
+++ b/tvix/nix-compat/src/nixhash/ca_hash.rs
@@ -0,0 +1,343 @@
+use crate::nixbase32;
+use crate::nixhash::{HashAlgo, NixHash};
+use serde::de::Unexpected;
+use serde::ser::SerializeMap;
+use serde::{Deserialize, Deserializer, Serialize, Serializer};
+use serde_json::{Map, Value};
+use std::borrow::Cow;
+
+use super::algos::SUPPORTED_ALGOS;
+use super::decode_digest;
+
+/// A Nix CAHash describes a content-addressed hash of a path.
+///
+/// The way Nix prints it as a string is a bit confusing, but there's essentially
+/// three modes, `Flat`, `Nar` and `Text`.
+/// `Flat` and `Nar` support all 4 algos that [NixHash] supports
+/// (sha1, md5, sha256, sha512), `Text` only supports sha256.
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub enum CAHash {
+    Flat(NixHash),  // "fixed flat"
+    Nar(NixHash),   // "fixed recursive"
+    Text([u8; 32]), // "text", only supports sha256
+}
+
+/// Representation for the supported hash modes.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum HashMode {
+    Flat,
+    Nar,
+    Text,
+}
+
+impl CAHash {
+    pub fn hash(&self) -> Cow<NixHash> {
+        match *self {
+            CAHash::Flat(ref digest) => Cow::Borrowed(digest),
+            CAHash::Nar(ref digest) => Cow::Borrowed(digest),
+            CAHash::Text(digest) => Cow::Owned(NixHash::Sha256(digest)),
+        }
+    }
+
+    pub fn mode(&self) -> HashMode {
+        match self {
+            CAHash::Flat(_) => HashMode::Flat,
+            CAHash::Nar(_) => HashMode::Nar,
+            CAHash::Text(_) => HashMode::Text,
+        }
+    }
+
+    /// Constructs a [CAHash] from the textual representation,
+    /// which is one of the three:
+    /// - `text:sha256:$nixbase32sha256digest`
+    /// - `fixed:r:$algo:$nixbase32digest`
+    /// - `fixed:$algo:$nixbase32digest`
+    /// which is the format that's used in the NARInfo for example.
+    pub fn from_nix_hex_str(s: &str) -> Option<Self> {
+        let (tag, s) = s.split_once(':')?;
+
+        match tag {
+            "text" => {
+                let digest = s.strip_prefix("sha256:")?;
+                let digest = nixbase32::decode_fixed(digest).ok()?;
+                Some(CAHash::Text(digest))
+            }
+            "fixed" => {
+                if let Some(s) = s.strip_prefix("r:") {
+                    NixHash::from_nix_hex_str(s).map(CAHash::Nar)
+                } else {
+                    NixHash::from_nix_hex_str(s).map(CAHash::Flat)
+                }
+            }
+            _ => None,
+        }
+    }
+
+    /// Formats a [CAHash] in the Nix default hash format, which is the format
+    /// that's used in NARInfos for example.
+    pub fn to_nix_nixbase32_string(&self) -> String {
+        match self {
+            CAHash::Flat(nh) => format!("fixed:{}", nh.to_nix_nixbase32_string()),
+            CAHash::Nar(nh) => format!("fixed:r:{}", nh.to_nix_nixbase32_string()),
+            CAHash::Text(digest) => {
+                format!("text:sha256:{}", nixbase32::encode(digest))
+            }
+        }
+    }
+
+    /// This takes a serde_json::Map and turns it into this structure. This is necessary to do such
+    /// shenigans because we have external consumers, like the Derivation parser, who would like to
+    /// know whether we have a invalid or a missing NixHashWithMode structure in another structure,
+    /// e.g. Output.
+    /// This means we have this combinatorial situation:
+    /// - no hash, no hashAlgo: no [CAHash] so we return Ok(None).
+    /// - present hash, missing hashAlgo: invalid, we will return missing_field
+    /// - missing hash, present hashAlgo: same
+    /// - present hash, present hashAlgo: either we return ourselves or a type/value validation
+    /// error.
+    /// This function is for internal consumption regarding those needs until we have a better
+    /// solution. Now this is said, let's explain how this works.
+    ///
+    /// We want to map the serde data model into a [CAHash].
+    ///
+    /// The serde data model has a `hash` field (containing a digest in nixbase32),
+    /// and a `hashAlgo` field, containing the stringified hash algo.
+    /// In case the hash is recursive, hashAlgo also has a `r:` prefix.
+    ///
+    /// This is to match how `nix show-derivation` command shows them in JSON
+    /// representation.
+    pub(crate) fn from_map<'de, D>(map: &Map<String, Value>) -> Result<Option<Self>, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        // If we don't have hash neither hashAlgo, let's just return None.
+        if !map.contains_key("hash") && !map.contains_key("hashAlgo") {
+            return Ok(None);
+        }
+
+        let hash_algo_v = map.get("hashAlgo").ok_or_else(|| {
+            serde::de::Error::missing_field(
+                "couldn't extract `hashAlgo` key, but `hash` key present",
+            )
+        })?;
+        let hash_algo = hash_algo_v.as_str().ok_or_else(|| {
+            serde::de::Error::invalid_type(Unexpected::Other(&hash_algo_v.to_string()), &"a string")
+        })?;
+        let (mode_is_nar, hash_algo) = if let Some(s) = hash_algo.strip_prefix("r:") {
+            (true, s)
+        } else {
+            (false, hash_algo)
+        };
+        let hash_algo = HashAlgo::try_from(hash_algo).map_err(|e| {
+            serde::de::Error::invalid_value(
+                Unexpected::Other(&e.to_string()),
+                &format!("one of {}", SUPPORTED_ALGOS.join(",")).as_str(),
+            )
+        })?;
+
+        let hash_v = map.get("hash").ok_or_else(|| {
+            serde::de::Error::missing_field(
+                "couldn't extract `hash` key but `hashAlgo` key present",
+            )
+        })?;
+        let hash = hash_v.as_str().ok_or_else(|| {
+            serde::de::Error::invalid_type(Unexpected::Other(&hash_v.to_string()), &"a string")
+        })?;
+        let hash = decode_digest(hash.as_bytes(), hash_algo)
+            .map_err(|e| serde::de::Error::custom(e.to_string()))?;
+        if mode_is_nar {
+            Ok(Some(Self::Nar(hash)))
+        } else {
+            Ok(Some(Self::Flat(hash)))
+        }
+    }
+}
+
+impl Serialize for CAHash {
+    /// map a CAHash into the serde data model.
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        let mut map = serializer.serialize_map(Some(2))?;
+        match self {
+            CAHash::Flat(h) => {
+                map.serialize_entry("hash", &nixbase32::encode(h.digest_as_bytes()))?;
+                map.serialize_entry("hashAlgo", &h.algo())?;
+            }
+            CAHash::Nar(h) => {
+                map.serialize_entry("hash", &nixbase32::encode(h.digest_as_bytes()))?;
+                map.serialize_entry("hashAlgo", &format!("r:{}", &h.algo()))?;
+            }
+            // It is not legal for derivations to use this (which is where
+            // we're currently exercising [Serialize] mostly,
+            // but it's still good to be able to serialize other CA hashes too.
+            CAHash::Text(h) => {
+                map.serialize_entry("hash", &nixbase32::encode(h.as_ref()))?;
+                map.serialize_entry("hashAlgo", "text")?;
+            }
+        };
+        map.end()
+    }
+}
+
+impl<'de> Deserialize<'de> for CAHash {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        let value = Self::from_map::<D>(&Map::deserialize(deserializer)?)?;
+
+        match value {
+            None => Err(serde::de::Error::custom("couldn't parse as map")),
+            Some(v) => Ok(v),
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::{derivation::CAHash, nixhash};
+
+    #[test]
+    fn serialize_flat() {
+        let json_bytes = r#"{
+  "hash": "1fnf2m46ya7r7afkcb8ba2j0sc4a85m749sh9jz64g4hx6z3r088",
+  "hashAlgo": "sha256"
+}"#;
+        let hash = CAHash::Flat(
+            nixhash::from_nix_str(
+                "sha256:08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba",
+            )
+            .unwrap(),
+        );
+        let serialized = serde_json::to_string_pretty(&hash).unwrap();
+        assert_eq!(serialized, json_bytes);
+    }
+
+    #[test]
+    fn serialize_nar() {
+        let json_bytes = r#"{
+  "hash": "1fnf2m46ya7r7afkcb8ba2j0sc4a85m749sh9jz64g4hx6z3r088",
+  "hashAlgo": "r:sha256"
+}"#;
+        let hash = CAHash::Nar(
+            nixhash::from_nix_str(
+                "sha256:08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba",
+            )
+            .unwrap(),
+        );
+        let serialized = serde_json::to_string_pretty(&hash).unwrap();
+        assert_eq!(serialized, json_bytes);
+    }
+
+    #[test]
+    fn deserialize_flat() {
+        let json_bytes = r#"
+        {
+            "hash": "08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba",
+            "hashAlgo": "sha256"
+        }"#;
+        let hash: CAHash = serde_json::from_str(json_bytes).expect("must parse");
+
+        assert_eq!(
+            hash,
+            CAHash::Flat(
+                nixhash::from_nix_str(
+                    "sha256:08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba"
+                )
+                .unwrap()
+            )
+        );
+    }
+
+    #[test]
+    fn deserialize_hex() {
+        let json_bytes = r#"
+        {
+            "hash": "08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba",
+            "hashAlgo": "r:sha256"
+        }"#;
+        let hash: CAHash = serde_json::from_str(json_bytes).expect("must parse");
+
+        assert_eq!(
+            hash,
+            CAHash::Nar(
+                nixhash::from_nix_str(
+                    "sha256:08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba"
+                )
+                .unwrap()
+            )
+        );
+    }
+
+    #[test]
+    fn deserialize_nixbase32() {
+        let json_bytes = r#"
+        {
+            "hash": "1fnf2m46ya7r7afkcb8ba2j0sc4a85m749sh9jz64g4hx6z3r088",
+            "hashAlgo": "r:sha256"
+        }"#;
+        let hash: CAHash = serde_json::from_str(json_bytes).expect("must parse");
+
+        assert_eq!(
+            hash,
+            CAHash::Nar(
+                nixhash::from_nix_str(
+                    "sha256:08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba"
+                )
+                .unwrap()
+            )
+        );
+    }
+
+    #[test]
+    fn deserialize_base64() {
+        let json_bytes = r#"
+        {
+            "hash": "CIE8vumQPGK+TFAncmpBijANpFALLTadOvkob0gVzro=",
+            "hashAlgo": "r:sha256"
+        }"#;
+        let hash: CAHash = serde_json::from_str(json_bytes).expect("must parse");
+
+        assert_eq!(
+            hash,
+            CAHash::Nar(
+                nixhash::from_nix_str(
+                    "sha256:08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba"
+                )
+                .unwrap()
+            )
+        );
+    }
+
+    #[test]
+    fn serialize_deserialize_nar() {
+        let json_bytes = r#"
+        {
+            "hash": "08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba",
+            "hashAlgo": "r:sha256"
+        }"#;
+        let hash: CAHash = serde_json::from_str(json_bytes).expect("must parse");
+
+        let serialized = serde_json::to_string(&hash).expect("Serialize");
+        let hash2: CAHash = serde_json::from_str(&serialized).expect("must parse again");
+
+        assert_eq!(hash, hash2);
+    }
+
+    #[test]
+    fn serialize_deserialize_flat() {
+        let json_bytes = r#"
+        {
+            "hash": "08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba",
+            "hashAlgo": "sha256"
+        }"#;
+        let hash: CAHash = serde_json::from_str(json_bytes).expect("must parse");
+
+        let serialized = serde_json::to_string(&hash).expect("Serialize");
+        let hash2: CAHash = serde_json::from_str(&serialized).expect("must parse again");
+
+        assert_eq!(hash, hash2);
+    }
+}
diff --git a/tvix/nix-compat/src/nixhash/mod.rs b/tvix/nix-compat/src/nixhash/mod.rs
new file mode 100644
index 0000000000..d86cb8b79f
--- /dev/null
+++ b/tvix/nix-compat/src/nixhash/mod.rs
@@ -0,0 +1,602 @@
+use crate::nixbase32;
+use bstr::ByteSlice;
+use data_encoding::{BASE64, BASE64_NOPAD, HEXLOWER};
+use serde::Deserialize;
+use serde::Serialize;
+use std::cmp::Ordering;
+use std::fmt::Display;
+use thiserror;
+
+mod algos;
+mod ca_hash;
+
+pub use algos::HashAlgo;
+pub use ca_hash::CAHash;
+pub use ca_hash::HashMode as CAHashMode;
+
+/// NixHash represents hashes known by Nix.
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub enum NixHash {
+    Md5([u8; 16]),
+    Sha1([u8; 20]),
+    Sha256([u8; 32]),
+    Sha512(Box<[u8; 64]>),
+}
+
+/// Same order as sorting the corresponding nixbase32 strings.
+///
+/// This order is used in the ATerm serialization of a derivation
+/// and thus affects the calculated output hash.
+impl Ord for NixHash {
+    fn cmp(&self, other: &NixHash) -> Ordering {
+        self.digest_as_bytes().cmp(other.digest_as_bytes())
+    }
+}
+
+// See Ord for reason to implement this manually.
+impl PartialOrd for NixHash {
+    fn partial_cmp(&self, other: &NixHash) -> Option<Ordering> {
+        Some(self.cmp(other))
+    }
+}
+
+impl Display for NixHash {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
+        write!(
+            f,
+            "{}-{}",
+            self.algo(),
+            nixbase32::encode(self.digest_as_bytes())
+        )
+    }
+}
+
+/// convenience Result type for all nixhash parsing Results.
+pub type NixHashResult<V> = std::result::Result<V, Error>;
+
+impl NixHash {
+    /// returns the algo as [HashAlgo].
+    pub fn algo(&self) -> HashAlgo {
+        match self {
+            NixHash::Md5(_) => HashAlgo::Md5,
+            NixHash::Sha1(_) => HashAlgo::Sha1,
+            NixHash::Sha256(_) => HashAlgo::Sha256,
+            NixHash::Sha512(_) => HashAlgo::Sha512,
+        }
+    }
+
+    /// returns the digest as variable-length byte slice.
+    pub fn digest_as_bytes(&self) -> &[u8] {
+        match self {
+            NixHash::Md5(digest) => digest,
+            NixHash::Sha1(digest) => digest,
+            NixHash::Sha256(digest) => digest,
+            NixHash::Sha512(digest) => digest.as_ref(),
+        }
+    }
+
+    /// Constructs a [NixHash] from the Nix default hash format,
+    /// the inverse of [Self::to_nix_hex_string].
+    pub fn from_nix_hex_str(s: &str) -> Option<Self> {
+        let (tag, digest) = s.split_once(':')?;
+
+        (match tag {
+            "md5" => nixbase32::decode_fixed(digest).map(NixHash::Md5),
+            "sha1" => nixbase32::decode_fixed(digest).map(NixHash::Sha1),
+            "sha256" => nixbase32::decode_fixed(digest).map(NixHash::Sha256),
+            "sha512" => nixbase32::decode_fixed(digest)
+                .map(Box::new)
+                .map(NixHash::Sha512),
+            _ => return None,
+        })
+        .ok()
+    }
+
+    /// Formats a [NixHash] in the Nix default hash format,
+    /// which is the algo, followed by a colon, then the lower hex encoded digest.
+    pub fn to_nix_hex_string(&self) -> String {
+        format!("{}:{}", self.algo(), self.to_plain_hex_string())
+    }
+
+    /// Formats a [NixHash] in the format that's used inside CAHash,
+    /// which is the algo, followed by a colon, then the nixbase32-encoded digest.
+    pub(crate) fn to_nix_nixbase32_string(&self) -> String {
+        format!(
+            "{}:{}",
+            self.algo(),
+            nixbase32::encode(self.digest_as_bytes())
+        )
+    }
+
+    /// Returns the digest as a hex string -- without any algorithm prefix.
+    pub fn to_plain_hex_string(&self) -> String {
+        HEXLOWER.encode(self.digest_as_bytes())
+    }
+}
+
+impl TryFrom<(HashAlgo, &[u8])> for NixHash {
+    type Error = Error;
+
+    /// Constructs a new [NixHash] by specifying [HashAlgo] and digest.
+    /// It can fail if the passed digest length doesn't match what's expected for
+    /// the passed algo.
+    fn try_from(value: (HashAlgo, &[u8])) -> NixHashResult<Self> {
+        let (algo, digest) = value;
+        from_algo_and_digest(algo, digest)
+    }
+}
+
+impl<'de> Deserialize<'de> for NixHash {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: serde::Deserializer<'de>,
+    {
+        let str: &'de str = Deserialize::deserialize(deserializer)?;
+        from_str(str, None).map_err(|_| {
+            serde::de::Error::invalid_value(serde::de::Unexpected::Str(str), &"NixHash")
+        })
+    }
+}
+
+impl Serialize for NixHash {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: serde::Serializer,
+    {
+        // encode as SRI
+        let string = format!("{}-{}", self.algo(), BASE64.encode(self.digest_as_bytes()));
+        string.serialize(serializer)
+    }
+}
+
+/// Constructs a new [NixHash] by specifying [HashAlgo] and digest.
+/// It can fail if the passed digest length doesn't match what's expected for
+/// the passed algo.
+pub fn from_algo_and_digest(algo: HashAlgo, digest: &[u8]) -> NixHashResult<NixHash> {
+    if digest.len() != algo.digest_length() {
+        return Err(Error::InvalidEncodedDigestLength(digest.len(), algo));
+    }
+
+    Ok(match algo {
+        HashAlgo::Md5 => NixHash::Md5(digest.try_into().unwrap()),
+        HashAlgo::Sha1 => NixHash::Sha1(digest.try_into().unwrap()),
+        HashAlgo::Sha256 => NixHash::Sha256(digest.try_into().unwrap()),
+        HashAlgo::Sha512 => NixHash::Sha512(Box::new(digest.try_into().unwrap())),
+    })
+}
+
+/// Errors related to NixHash construction.
+#[derive(Debug, Eq, PartialEq, thiserror::Error)]
+pub enum Error {
+    #[error("invalid hash algo: {0}")]
+    InvalidAlgo(String),
+    #[error("invalid SRI string: {0}")]
+    InvalidSRI(String),
+    #[error("invalid encoded digest length '{0}' for algo {1}")]
+    InvalidEncodedDigestLength(usize, HashAlgo),
+    #[error("invalid base16 encoding: {0}")]
+    InvalidBase16Encoding(data_encoding::DecodeError),
+    #[error("invalid base32 encoding: {0}")]
+    InvalidBase32Encoding(data_encoding::DecodeError),
+    #[error("invalid base64 encoding: {0}")]
+    InvalidBase64Encoding(data_encoding::DecodeError),
+    #[error("conflicting hash algo: {0} (hash_algo) vs {1} (inline)")]
+    ConflictingHashAlgos(HashAlgo, HashAlgo),
+    #[error("missing inline hash algo, but no externally-specified algo: {0}")]
+    MissingInlineHashAlgo(String),
+}
+
+/// Nix allows specifying hashes in various encodings, and magically just
+/// derives the encoding.
+/// This function parses strings to a NixHash.
+///
+/// Hashes can be:
+/// - Nix hash strings
+/// - SRI hashes
+/// - bare digests
+///
+/// Encoding for Nix hash strings or bare digests can be:
+/// - base16 (lowerhex),
+/// - nixbase32,
+/// - base64 (StdEncoding)
+/// - sri string
+///
+/// The encoding is derived from the length of the string and the hash type.
+/// The hash is communicated out-of-band, but might also be in-band (in the
+/// case of a nix hash string or SRI), in which it needs to be consistent with the
+/// one communicated out-of-band.
+pub fn from_str(s: &str, algo_str: Option<&str>) -> NixHashResult<NixHash> {
+    // if algo_str is some, parse or bail out
+    let algo: Option<HashAlgo> = if let Some(algo_str) = algo_str {
+        Some(algo_str.try_into()?)
+    } else {
+        None
+    };
+
+    // Peek at the beginning of the string to detect SRI hashes.
+    if s.starts_with("sha1-")
+        || s.starts_with("sha256-")
+        || s.starts_with("sha512-")
+        || s.starts_with("md5-")
+    {
+        let parsed_nixhash = from_sri_str(s)?;
+
+        // ensure the algo matches with what has been passed externally, if so.
+        if let Some(algo) = algo {
+            if algo != parsed_nixhash.algo() {
+                return Err(Error::ConflictingHashAlgos(algo, parsed_nixhash.algo()));
+            }
+        }
+        return Ok(parsed_nixhash);
+    }
+
+    // Peek at the beginning again to see if it's a Nix Hash
+    if s.starts_with("sha1:")
+        || s.starts_with("sha256:")
+        || s.starts_with("sha512:")
+        || s.starts_with("md5:")
+    {
+        let parsed_nixhash = from_nix_str(s)?;
+        // ensure the algo matches with what has been passed externally, if so.
+        if let Some(algo) = algo {
+            if algo != parsed_nixhash.algo() {
+                return Err(Error::ConflictingHashAlgos(algo, parsed_nixhash.algo()));
+            }
+        }
+        return Ok(parsed_nixhash);
+    }
+
+    // Neither of these, assume a bare digest, so there MUST be an externally-passed algo.
+    match algo {
+        // Fail if there isn't.
+        None => Err(Error::MissingInlineHashAlgo(s.to_string())),
+        Some(algo) => decode_digest(s.as_bytes(), algo),
+    }
+}
+
+/// Parses a Nix hash string ($algo:$digest) to a NixHash.
+pub fn from_nix_str(s: &str) -> NixHashResult<NixHash> {
+    if let Some(rest) = s.strip_prefix("sha1:") {
+        decode_digest(rest.as_bytes(), HashAlgo::Sha1)
+    } else if let Some(rest) = s.strip_prefix("sha256:") {
+        decode_digest(rest.as_bytes(), HashAlgo::Sha256)
+    } else if let Some(rest) = s.strip_prefix("sha512:") {
+        decode_digest(rest.as_bytes(), HashAlgo::Sha512)
+    } else if let Some(rest) = s.strip_prefix("md5:") {
+        decode_digest(rest.as_bytes(), HashAlgo::Md5)
+    } else {
+        Err(Error::InvalidAlgo(s.to_string()))
+    }
+}
+
+/// Parses a Nix SRI string to a NixHash.
+/// Contrary to the SRI spec, Nix doesn't have an understanding of passing
+/// multiple hashes (with different algos) in SRI hashes.
+/// It instead simply cuts everything off after the expected length for the
+/// specified algo, and tries to parse the rest in permissive base64 (allowing
+/// missing padding).
+pub fn from_sri_str(s: &str) -> NixHashResult<NixHash> {
+    // split at the first occurence of "-"
+    let (algo_str, digest_str) = s
+        .split_once('-')
+        .ok_or_else(|| Error::InvalidSRI(s.to_string()))?;
+
+    // try to map the part before that `-` to a supported hash algo:
+    let algo: HashAlgo = algo_str.try_into()?;
+
+    // For the digest string, Nix ignores everything after the expected BASE64
+    // (with padding) length, to account for the fact SRI allows specifying more
+    // than one checksum, so shorten it.
+    let digest_str = {
+        let encoded_max_len = BASE64.encode_len(algo.digest_length());
+        if digest_str.len() > encoded_max_len {
+            digest_str[..encoded_max_len].as_bytes()
+        } else {
+            digest_str.as_bytes()
+        }
+    };
+
+    // if the digest string is too small to fit even the BASE64_NOPAD version, bail out.
+    if digest_str.len() < BASE64_NOPAD.encode_len(algo.digest_length()) {
+        return Err(Error::InvalidEncodedDigestLength(digest_str.len(), algo));
+    }
+
+    // trim potential padding, and use a version that does not do trailing bit
+    // checking.
+    let mut spec = BASE64_NOPAD.specification();
+    spec.check_trailing_bits = false;
+    let encoding = spec
+        .encoding()
+        .expect("Tvix bug: failed to get the special base64 encoder for Nix SRI hashes");
+
+    let digest = encoding
+        .decode(digest_str.trim_end_with(|c| c == '='))
+        .map_err(Error::InvalidBase64Encoding)?;
+
+    from_algo_and_digest(algo, &digest)
+}
+
+/// Decode a plain digest depending on the hash algo specified externally.
+/// hexlower, nixbase32 and base64 encodings are supported - the encoding is
+/// inferred from the input length.
+fn decode_digest(s: &[u8], algo: HashAlgo) -> NixHashResult<NixHash> {
+    // for the chosen hash algo, calculate the expected (decoded) digest length
+    // (as bytes)
+    let digest = if s.len() == HEXLOWER.encode_len(algo.digest_length()) {
+        HEXLOWER
+            .decode(s.as_ref())
+            .map_err(Error::InvalidBase16Encoding)?
+    } else if s.len() == nixbase32::encode_len(algo.digest_length()) {
+        nixbase32::decode(s).map_err(Error::InvalidBase32Encoding)?
+    } else if s.len() == BASE64.encode_len(algo.digest_length()) {
+        BASE64
+            .decode(s.as_ref())
+            .map_err(Error::InvalidBase64Encoding)?
+    } else {
+        Err(Error::InvalidEncodedDigestLength(s.len(), algo))?
+    };
+
+    Ok(from_algo_and_digest(algo, &digest).unwrap())
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::{
+        nixbase32,
+        nixhash::{self, HashAlgo, NixHash},
+    };
+    use data_encoding::{BASE64, BASE64_NOPAD, HEXLOWER};
+    use hex_literal::hex;
+    use rstest::rstest;
+
+    const DIGEST_SHA1: [u8; 20] = hex!("6016777997c30ab02413cf5095622cd7924283ac");
+    const DIGEST_SHA256: [u8; 32] =
+        hex!("a5ce9c155ed09397614646c9717fc7cd94b1023d7b76b618d409e4fefd6e9d39");
+    const DIGEST_SHA512: [u8; 64] = hex!("ab40d0be3541f0774bba7815d13d10b03252e96e95f7dbb4ee99a3b431c21662fd6971a020160e39848aa5f305b9be0f78727b2b0789e39f124d21e92b8f39ef");
+    const DIGEST_MD5: [u8; 16] = hex!("c4874a8897440b393d862d8fd459073f");
+
+    fn to_base16(digest: &[u8]) -> String {
+        HEXLOWER.encode(digest)
+    }
+
+    fn to_nixbase32(digest: &[u8]) -> String {
+        nixbase32::encode(digest)
+    }
+
+    fn to_base64(digest: &[u8]) -> String {
+        BASE64.encode(digest)
+    }
+
+    fn to_base64_nopad(digest: &[u8]) -> String {
+        BASE64_NOPAD.encode(digest)
+    }
+
+    // TODO
+    fn make_nixhash(algo: &HashAlgo, digest_encoded: String) -> String {
+        format!("{}:{}", algo, digest_encoded)
+    }
+    fn make_sri_string(algo: &HashAlgo, digest_encoded: String) -> String {
+        format!("{}-{}", algo, digest_encoded)
+    }
+
+    /// Test parsing a hash string in various formats, and also when/how the out-of-band algo is needed.
+    #[rstest]
+    #[case::sha1(&NixHash::Sha1(DIGEST_SHA1))]
+    #[case::sha256(&NixHash::Sha256(DIGEST_SHA256))]
+    #[case::sha512(&NixHash::Sha512(Box::new(DIGEST_SHA512)))]
+    #[case::md5(&NixHash::Md5(DIGEST_MD5))]
+    fn from_str(#[case] expected_hash: &NixHash) {
+        let algo = &expected_hash.algo();
+        let digest = expected_hash.digest_as_bytes();
+        // parse SRI
+        {
+            // base64 without out-of-band algo
+            let s = make_sri_string(algo, to_base64(digest));
+            let h = nixhash::from_str(&s, None).expect("must succeed");
+            assert_eq!(expected_hash, &h);
+
+            // base64 with out-of-band-algo
+            let s = make_sri_string(algo, to_base64(digest));
+            let h = nixhash::from_str(&s, Some(&expected_hash.algo().to_string()))
+                .expect("must succeed");
+            assert_eq!(expected_hash, &h);
+
+            // base64_nopad without out-of-band algo
+            let s = make_sri_string(algo, to_base64_nopad(digest));
+            let h = nixhash::from_str(&s, None).expect("must succeed");
+            assert_eq!(expected_hash, &h);
+
+            // base64_nopad with out-of-band-algo
+            let s = make_sri_string(algo, to_base64_nopad(digest));
+            let h = nixhash::from_str(&s, Some(&algo.to_string())).expect("must succeed");
+            assert_eq!(expected_hash, &h);
+        }
+
+        // parse plain base16. should succeed with algo out-of-band, but fail without.
+        {
+            let s = to_base16(digest);
+            nixhash::from_str(&s, None).expect_err("must fail");
+            let h = nixhash::from_str(&s, Some(&algo.to_string())).expect("must succeed");
+            assert_eq!(expected_hash, &h);
+        }
+
+        // parse plain nixbase32. should succeed with algo out-of-band, but fail without.
+        {
+            let s = to_nixbase32(digest);
+            nixhash::from_str(&s, None).expect_err("must fail");
+            let h = nixhash::from_str(&s, Some(&algo.to_string())).expect("must succeed");
+            assert_eq!(expected_hash, &h);
+        }
+
+        // parse plain base64. should succeed with algo out-of-band, but fail without.
+        {
+            let s = to_base64(digest);
+            nixhash::from_str(&s, None).expect_err("must fail");
+            let h = nixhash::from_str(&s, Some(&algo.to_string())).expect("must succeed");
+            assert_eq!(expected_hash, &h);
+        }
+
+        // parse Nix hash strings
+        {
+            // base16. should succeed with both algo out-of-band and in-band.
+            {
+                let s = make_nixhash(algo, to_base16(digest));
+                assert_eq!(
+                    expected_hash,
+                    &nixhash::from_str(&s, None).expect("must succeed")
+                );
+                assert_eq!(
+                    expected_hash,
+                    &nixhash::from_str(&s, Some(&algo.to_string())).expect("must succeed")
+                );
+            }
+            // nixbase32. should succeed with both algo out-of-band and in-band.
+            {
+                let s = make_nixhash(algo, to_nixbase32(digest));
+                assert_eq!(
+                    expected_hash,
+                    &nixhash::from_str(&s, None).expect("must succeed")
+                );
+                assert_eq!(
+                    expected_hash,
+                    &nixhash::from_str(&s, Some(&algo.to_string())).expect("must succeed")
+                );
+            }
+            // base64. should succeed with both algo out-of-band and in-band.
+            {
+                let s = make_nixhash(algo, to_base64(digest));
+                assert_eq!(
+                    expected_hash,
+                    &nixhash::from_str(&s, None).expect("must succeed")
+                );
+                assert_eq!(
+                    expected_hash,
+                    &nixhash::from_str(&s, Some(&algo.to_string())).expect("must succeed")
+                );
+            }
+        }
+    }
+
+    /// Test parsing an SRI hash via the [nixhash::from_sri_str] method.
+    #[test]
+    fn from_sri_str() {
+        let nix_hash = nixhash::from_sri_str("sha256-pc6cFV7Qk5dhRkbJcX/HzZSxAj17drYY1Ank/v1unTk=")
+            .expect("must succeed");
+
+        assert_eq!(HashAlgo::Sha256, nix_hash.algo());
+        assert_eq!(
+            &hex!("a5ce9c155ed09397614646c9717fc7cd94b1023d7b76b618d409e4fefd6e9d39"),
+            nix_hash.digest_as_bytes()
+        )
+    }
+
+    /// Test parsing sha512 SRI hash with various paddings, Nix accepts all of them.
+    #[rstest]
+    #[case::no_padding("sha512-7g91TBvYoYQorRTqo+rYD/i5YnWvUBLnqDhPHxBJDaBW7smuPMeRp6E6JOFuVN9bzN0QnH1ToUU0u9c2CjALEQ")]
+    #[case::too_little_padding("sha512-7g91TBvYoYQorRTqo+rYD/i5YnWvUBLnqDhPHxBJDaBW7smuPMeRp6E6JOFuVN9bzN0QnH1ToUU0u9c2CjALEQ=")]
+    #[case::correct_padding("sha512-7g91TBvYoYQorRTqo+rYD/i5YnWvUBLnqDhPHxBJDaBW7smuPMeRp6E6JOFuVN9bzN0QnH1ToUU0u9c2CjALEQ==")]
+    #[case::too_much_padding("sha512-7g91TBvYoYQorRTqo+rYD/i5YnWvUBLnqDhPHxBJDaBW7smuPMeRp6E6JOFuVN9bzN0QnH1ToUU0u9c2CjALEQ===")]
+    #[case::additional_suffix_ignored("sha512-7g91TBvYoYQorRTqo+rYD/i5YnWvUBLnqDhPHxBJDaBW7smuPMeRp6E6JOFuVN9bzN0QnH1ToUU0u9c2CjALEQ== cheesecake")]
+    fn from_sri_str_sha512_paddings(#[case] sri_str: &str) {
+        let nix_hash = nixhash::from_sri_str(sri_str).expect("must succeed");
+
+        assert_eq!(HashAlgo::Sha512, nix_hash.algo());
+        assert_eq!(
+            &hex!("ee0f754c1bd8a18428ad14eaa3ead80ff8b96275af5012e7a8384f1f10490da056eec9ae3cc791a7a13a24e16e54df5bccdd109c7d53a14534bbd7360a300b11"),
+            nix_hash.digest_as_bytes()
+        )
+    }
+
+    /// Ensure we detect truncated base64 digests, where the digest size
+    /// doesn't match what's expected from that hash function.
+    #[test]
+    fn from_sri_str_truncated() {
+        nixhash::from_sri_str("sha256-pc6cFV7Qk5dhRkbJcX/HzZSxAj17drYY1Ank")
+            .expect_err("must fail");
+    }
+
+    /// Ensure we fail on SRI hashes that Nix doesn't support.
+    #[test]
+    fn from_sri_str_unsupported() {
+        nixhash::from_sri_str(
+            "sha384-o4UVSl89mIB0sFUK+3jQbG+C9Zc9dRlV/Xd3KAvXEbhqxu0J5OAdg6b6VHKHwQ7U",
+        )
+        .expect_err("must fail");
+    }
+
+    /// Ensure we reject invalid base64 encoding
+    #[test]
+    fn from_sri_str_invalid_base64() {
+        nixhash::from_sri_str("sha256-invalid=base64").expect_err("must fail");
+    }
+
+    /// Nix also accepts SRI strings with missing padding, but only in case the
+    /// string is expressed as SRI, so it still needs to have a `sha256-` prefix.
+    ///
+    /// This both seems to work if it is passed with and without specifying the
+    /// hash algo out-of-band (hash = "sha256-…" or sha256 = "sha256-…")
+    ///
+    /// Passing the same broken base64 string, but not as SRI, while passing
+    /// the hash algo out-of-band does not work.
+    #[test]
+    fn sha256_broken_padding() {
+        let broken_base64 = "fgIr3TyFGDAXP5+qoAaiMKDg/a1MlT6Fv/S/DaA24S8";
+        // if padded with a trailing '='
+        let expected_digest =
+            hex!("7e022bdd3c851830173f9faaa006a230a0e0fdad4c953e85bff4bf0da036e12f");
+
+        // passing hash algo out of band should succeed
+        let nix_hash = nixhash::from_str(&format!("sha256-{}", &broken_base64), Some("sha256"))
+            .expect("must succeed");
+        assert_eq!(&expected_digest, &nix_hash.digest_as_bytes());
+
+        // not passing hash algo out of band should succeed
+        let nix_hash =
+            nixhash::from_str(&format!("sha256-{}", &broken_base64), None).expect("must succeed");
+        assert_eq!(&expected_digest, &nix_hash.digest_as_bytes());
+
+        // not passing SRI, but hash algo out of band should fail
+        nixhash::from_str(broken_base64, Some("sha256")).expect_err("must fail");
+    }
+
+    /// As we decided to pass our hashes by trimming `=` completely,
+    /// we need to take into account hashes with padding requirements which
+    /// contains trailing bits which would be checked by `BASE64_NOPAD` and would
+    /// make the verification crash.
+    ///
+    /// This base64 has a trailing non-zero bit at bit 42.
+    #[test]
+    fn sha256_weird_base64() {
+        let weird_base64 = "syceJMUEknBDCHK8eGs6rUU3IQn+HnQfURfCrDxYPa9=";
+        let expected_digest =
+            hex!("b3271e24c5049270430872bc786b3aad45372109fe1e741f5117c2ac3c583daf");
+
+        let nix_hash = nixhash::from_str(&format!("sha256-{}", &weird_base64), Some("sha256"))
+            .expect("must succeed");
+        assert_eq!(&expected_digest, &nix_hash.digest_as_bytes());
+
+        // not passing hash algo out of band should succeed
+        let nix_hash =
+            nixhash::from_str(&format!("sha256-{}", &weird_base64), None).expect("must succeed");
+        assert_eq!(&expected_digest, &nix_hash.digest_as_bytes());
+
+        // not passing SRI, but hash algo out of band should fail
+        nixhash::from_str(weird_base64, Some("sha256")).expect_err("must fail");
+    }
+
+    #[test]
+    fn serialize_deserialize() {
+        let nixhash_actual = NixHash::Sha256(hex!(
+            "b3271e24c5049270430872bc786b3aad45372109fe1e741f5117c2ac3c583daf"
+        ));
+        let nixhash_str_json = "\"sha256-syceJMUEknBDCHK8eGs6rUU3IQn+HnQfURfCrDxYPa8=\"";
+
+        let serialized = serde_json::to_string(&nixhash_actual).expect("can serialize");
+
+        assert_eq!(nixhash_str_json, &serialized);
+
+        let deserialized: NixHash =
+            serde_json::from_str(nixhash_str_json).expect("must deserialize");
+        assert_eq!(&nixhash_actual, &deserialized);
+    }
+}
diff --git a/tvix/nix-compat/src/path_info.rs b/tvix/nix-compat/src/path_info.rs
new file mode 100644
index 0000000000..f289ebde33
--- /dev/null
+++ b/tvix/nix-compat/src/path_info.rs
@@ -0,0 +1,121 @@
+use crate::{nixbase32, nixhash::NixHash, store_path::StorePathRef};
+use serde::{Deserialize, Serialize};
+use std::collections::BTreeSet;
+
+/// Represents information about a Store Path that Nix provides inside the build
+/// if the exportReferencesGraph feature is used.
+/// This is not to be confused with the format Nix uses in its `nix path-info` command.
+/// It includes some more fields, like `registrationTime`, `signatures` and `ultimate`,
+/// does not include the `closureSize` and encodes `narHash` as SRI.
+#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
+pub struct ExportedPathInfo<'a> {
+    #[serde(rename = "closureSize")]
+    pub closure_size: u64,
+
+    #[serde(
+        rename = "narHash",
+        serialize_with = "to_nix_nixbase32_string",
+        deserialize_with = "from_nix_nixbase32_string"
+    )]
+    pub nar_sha256: [u8; 32],
+
+    #[serde(rename = "narSize")]
+    pub nar_size: u64,
+
+    #[serde(borrow)]
+    pub path: StorePathRef<'a>,
+
+    /// The list of other Store Paths this Store Path refers to.
+    /// StorePathRef does Ord by the nixbase32-encoded string repr, so this is correct.
+    pub references: BTreeSet<StorePathRef<'a>>,
+    // more recent versions of Nix also have a `valid: true` field here, Nix 2.3 doesn't,
+    // and nothing seems to use it.
+}
+
+/// ExportedPathInfo are ordered by their `path` field.
+impl Ord for ExportedPathInfo<'_> {
+    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+        self.path.cmp(&other.path)
+    }
+}
+
+impl PartialOrd for ExportedPathInfo<'_> {
+    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+        Some(self.cmp(other))
+    }
+}
+
+fn to_nix_nixbase32_string<S>(v: &[u8; 32], serializer: S) -> Result<S::Ok, S::Error>
+where
+    S: serde::Serializer,
+{
+    let string = NixHash::Sha256(*v).to_nix_nixbase32_string();
+    string.serialize(serializer)
+}
+
+/// The length of a sha256 digest, nixbase32-encoded.
+const NIXBASE32_SHA256_ENCODE_LEN: usize = nixbase32::encode_len(32);
+
+fn from_nix_nixbase32_string<'de, D>(deserializer: D) -> Result<[u8; 32], D::Error>
+where
+    D: serde::Deserializer<'de>,
+{
+    let str: &'de str = Deserialize::deserialize(deserializer)?;
+
+    let digest_str = str.strip_prefix("sha256:").ok_or_else(|| {
+        serde::de::Error::invalid_value(serde::de::Unexpected::Str(str), &"sha256:…")
+    })?;
+
+    let digest_str: [u8; NIXBASE32_SHA256_ENCODE_LEN] =
+        digest_str.as_bytes().try_into().map_err(|_| {
+            serde::de::Error::invalid_value(serde::de::Unexpected::Str(str), &"valid digest len")
+        })?;
+
+    let digest: [u8; 32] = nixbase32::decode_fixed(digest_str).map_err(|_| {
+        serde::de::Error::invalid_value(serde::de::Unexpected::Str(str), &"valid nixbase32")
+    })?;
+
+    Ok(digest)
+}
+
+#[cfg(test)]
+mod tests {
+    use hex_literal::hex;
+
+    use super::*;
+
+    /// Ensure we can create the same JSON as the exportReferencesGraph feature
+    #[test]
+    fn serialize_deserialize() {
+        // JSON extracted from a build of
+        // stdenv.mkDerivation { name = "hello"; __structuredAttrs = true; exportReferencesGraph.blub = [ pkgs.hello ]; nativeBuildInputs = [pkgs.jq]; buildCommand = "jq -rc .blub $NIX_ATTRS_JSON_FILE > $out"; }
+        let pathinfos_str_json = r#"[{"closureSize":1828984,"narHash":"sha256:11vm2x1ajhzsrzw7lsyss51mmr3b6yll9wdjn51bh7liwkpc8ila","narSize":1828984,"path":"/nix/store/7n0mbqydcipkpbxm24fab066lxk68aqk-libunistring-1.1","references":["/nix/store/7n0mbqydcipkpbxm24fab066lxk68aqk-libunistring-1.1"]},{"closureSize":32696176,"narHash":"sha256:0alzbhjxdcsmr1pk7z0bdh46r2xpq3xs3k9y82bi4bx5pklcvw5x","narSize":226560,"path":"/nix/store/dbghhbq1x39yxgkv3vkgfwbxrmw9nfzi-hello-2.12.1","references":["/nix/store/dbghhbq1x39yxgkv3vkgfwbxrmw9nfzi-hello-2.12.1","/nix/store/ddwyrxif62r8n6xclvskjyy6szdhvj60-glibc-2.39-5"]},{"closureSize":32469616,"narHash":"sha256:1zw5p05fh0k836ybfxkskv8apcv2m3pm2wa6y90wqn5w5kjyj13c","narSize":30119936,"path":"/nix/store/ddwyrxif62r8n6xclvskjyy6szdhvj60-glibc-2.39-5","references":["/nix/store/ddwyrxif62r8n6xclvskjyy6szdhvj60-glibc-2.39-5","/nix/store/rxganm4ibf31qngal3j3psp20mak37yy-xgcc-13.2.0-libgcc","/nix/store/s32cldbh9pfzd9z82izi12mdlrw0yf8q-libidn2-2.3.7"]},{"closureSize":159560,"narHash":"sha256:10q8iyvfmpfck3yiisnj1j8vp6lq3km17r26sr95zpdf9mgmk69s","narSize":159560,"path":"/nix/store/rxganm4ibf31qngal3j3psp20mak37yy-xgcc-13.2.0-libgcc","references":[]},{"closureSize":2190120,"narHash":"sha256:1cv997nzxbd91jhmzwnhxa1ahlzp5ffli8m4a5npcq8zg0vb1kwg","narSize":361136,"path":"/nix/store/s32cldbh9pfzd9z82izi12mdlrw0yf8q-libidn2-2.3.7","references":["/nix/store/7n0mbqydcipkpbxm24fab066lxk68aqk-libunistring-1.1","/nix/store/s32cldbh9pfzd9z82izi12mdlrw0yf8q-libidn2-2.3.7"]}]"#;
+
+        // We ensure it roundtrips (to check the sorting is correct)
+        let deserialized: BTreeSet<ExportedPathInfo> =
+            serde_json::from_str(pathinfos_str_json).expect("must serialize");
+
+        let serialized_again = serde_json::to_string(&deserialized).expect("must deserialize");
+        assert_eq!(pathinfos_str_json, serialized_again);
+
+        // Also compare one specific item to be populated as expected.
+        assert_eq!(
+            &ExportedPathInfo {
+                closure_size: 1828984,
+                nar_sha256: hex!(
+                    "8a46c4eee4911eb842b1b2f144a9376be45a43d1da6b7af8cffa43a942177587"
+                ),
+                nar_size: 1828984,
+                path: StorePathRef::from_bytes(
+                    b"7n0mbqydcipkpbxm24fab066lxk68aqk-libunistring-1.1"
+                )
+                .expect("must parse"),
+                references: BTreeSet::from_iter([StorePathRef::from_bytes(
+                    b"7n0mbqydcipkpbxm24fab066lxk68aqk-libunistring-1.1"
+                )
+                .unwrap()]),
+            },
+            deserialized.first().unwrap()
+        );
+    }
+}
diff --git a/tvix/nix-compat/src/store_path/mod.rs b/tvix/nix-compat/src/store_path/mod.rs
new file mode 100644
index 0000000000..ff7ede77e1
--- /dev/null
+++ b/tvix/nix-compat/src/store_path/mod.rs
@@ -0,0 +1,635 @@
+use crate::nixbase32;
+use data_encoding::{DecodeError, BASE64};
+use serde::{Deserialize, Serialize};
+use std::{
+    fmt,
+    path::PathBuf,
+    str::{self, FromStr},
+};
+use thiserror;
+
+#[cfg(target_family = "unix")]
+use std::os::unix::ffi::OsStringExt;
+
+mod utils;
+
+pub use utils::*;
+
+pub const DIGEST_SIZE: usize = 20;
+pub const ENCODED_DIGEST_SIZE: usize = nixbase32::encode_len(DIGEST_SIZE);
+
+// The store dir prefix, without trailing slash.
+// That's usually where the Nix store is mounted at.
+pub const STORE_DIR: &str = "/nix/store";
+pub const STORE_DIR_WITH_SLASH: &str = "/nix/store/";
+
+/// Errors that can occur when parsing a literal store path
+#[derive(Debug, PartialEq, Eq, thiserror::Error)]
+pub enum Error {
+    #[error("Dash is missing between hash and name")]
+    MissingDash,
+    #[error("Hash encoding is invalid: {0}")]
+    InvalidHashEncoding(#[from] DecodeError),
+    #[error("Invalid length")]
+    InvalidLength,
+    #[error(
+        "Invalid name: \"{}\", character at position {} is invalid",
+        std::str::from_utf8(.0).unwrap_or(&BASE64.encode(.0)),
+        .1,
+    )]
+    InvalidName(Vec<u8>, u8),
+    #[error("Tried to parse an absolute path which was missing the store dir prefix.")]
+    MissingStoreDir,
+}
+
+/// Represents a path in the Nix store (a direct child of [STORE_DIR]).
+///
+/// It consists of a digest (20 bytes), and a name, which is a string.
+/// The name may only contain ASCII alphanumeric, or one of the following
+/// characters: `-`, `_`, `.`, `+`, `?`, `=`.
+/// The name is usually used to describe the pname and version of a package.
+/// Derivation paths can also be represented as store paths, their names just
+/// end with the `.drv` prefix.
+///
+/// A [StorePath] does not encode any additional subpath "inside" the store
+/// path.
+#[derive(Clone, Debug, PartialEq, Eq, Hash)]
+pub struct StorePath {
+    digest: [u8; DIGEST_SIZE],
+    name: String,
+}
+
+impl StorePath {
+    pub fn digest(&self) -> &[u8; DIGEST_SIZE] {
+        &self.digest
+    }
+
+    pub fn name(&self) -> &str {
+        self.name.as_ref()
+    }
+
+    pub fn as_ref(&self) -> StorePathRef<'_> {
+        StorePathRef {
+            digest: self.digest,
+            name: &self.name,
+        }
+    }
+}
+
+impl PartialOrd for StorePath {
+    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+        Some(self.cmp(other))
+    }
+}
+
+/// `StorePath`s are sorted by their reverse digest to match the sorting order
+/// of the nixbase32-encoded string.
+impl Ord for StorePath {
+    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+        self.as_ref().cmp(&other.as_ref())
+    }
+}
+
+impl FromStr for StorePath {
+    type Err = Error;
+
+    /// Construct a [StorePath] by passing the `$digest-$name` string
+    /// that comes after [STORE_DIR_WITH_SLASH].
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        Self::from_bytes(s.as_bytes())
+    }
+}
+
+impl StorePath {
+    /// Construct a [StorePath] by passing the `$digest-$name` string
+    /// that comes after [STORE_DIR_WITH_SLASH].
+    pub fn from_bytes(s: &[u8]) -> Result<StorePath, Error> {
+        Ok(StorePathRef::from_bytes(s)?.to_owned())
+    }
+
+    /// Decompose a string into a [StorePath] and a [PathBuf] containing the
+    /// rest of the path, or an error.
+    #[cfg(target_family = "unix")]
+    pub fn from_absolute_path_full(s: &str) -> Result<(StorePath, PathBuf), Error> {
+        // strip [STORE_DIR_WITH_SLASH] from s
+        match s.strip_prefix(STORE_DIR_WITH_SLASH) {
+            None => Err(Error::MissingStoreDir),
+            Some(rest) => {
+                // put rest in a PathBuf
+                let mut p = PathBuf::new();
+                p.push(rest);
+
+                let mut it = p.components();
+
+                // The first component of the rest must be parse-able as a [StorePath]
+                if let Some(first_component) = it.next() {
+                    // convert first component to StorePath
+                    let first_component_bytes = first_component.as_os_str().to_owned().into_vec();
+                    let store_path = StorePath::from_bytes(&first_component_bytes)?;
+                    // collect rest
+                    let rest_buf: PathBuf = it.collect();
+                    Ok((store_path, rest_buf))
+                } else {
+                    Err(Error::InvalidLength) // Well, or missing "/"?
+                }
+            }
+        }
+    }
+
+    /// Returns an absolute store path string.
+    /// That is just the string representation, prefixed with the store prefix
+    /// ([STORE_DIR_WITH_SLASH]),
+    pub fn to_absolute_path(&self) -> String {
+        let sp_ref: StorePathRef = self.into();
+        sp_ref.to_absolute_path()
+    }
+}
+
+impl<'de> Deserialize<'de> for StorePath {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: serde::Deserializer<'de>,
+    {
+        let r = <StorePathRef<'de> as Deserialize<'de>>::deserialize(deserializer)?;
+        Ok(r.to_owned())
+    }
+}
+
+impl Serialize for StorePath {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: serde::Serializer,
+    {
+        let r: StorePathRef = self.into();
+        r.serialize(serializer)
+    }
+}
+
+/// Like [StorePath], but without a heap allocation for the name.
+/// Used by [StorePath] for parsing.
+///
+#[derive(Debug, Eq, PartialEq, Clone, Copy, Hash)]
+pub struct StorePathRef<'a> {
+    digest: [u8; DIGEST_SIZE],
+    name: &'a str,
+}
+
+impl<'a> From<&'a StorePath> for StorePathRef<'a> {
+    fn from(&StorePath { digest, ref name }: &'a StorePath) -> Self {
+        StorePathRef {
+            digest,
+            name: name.as_ref(),
+        }
+    }
+}
+
+impl<'a> PartialOrd for StorePathRef<'a> {
+    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+        Some(self.cmp(other))
+    }
+}
+
+/// `StorePathRef`s are sorted by their reverse digest to match the sorting order
+/// of the nixbase32-encoded string.
+impl<'a> Ord for StorePathRef<'a> {
+    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+        self.digest.iter().rev().cmp(other.digest.iter().rev())
+    }
+}
+
+impl<'a> StorePathRef<'a> {
+    pub fn digest(&self) -> &[u8; DIGEST_SIZE] {
+        &self.digest
+    }
+
+    pub fn name(&self) -> &'a str {
+        self.name
+    }
+
+    pub fn to_owned(&self) -> StorePath {
+        StorePath {
+            digest: self.digest,
+            name: self.name.to_owned(),
+        }
+    }
+
+    /// Construct a [StorePathRef] from a name and digest.
+    /// The name is validated, and the digest checked for size.
+    pub fn from_name_and_digest(name: &'a str, digest: &[u8]) -> Result<Self, Error> {
+        let digest_fixed = digest.try_into().map_err(|_| Error::InvalidLength)?;
+        Self::from_name_and_digest_fixed(name, digest_fixed)
+    }
+
+    /// Construct a [StorePathRef] from a name and digest of correct length.
+    /// The name is validated.
+    pub fn from_name_and_digest_fixed(
+        name: &'a str,
+        digest: [u8; DIGEST_SIZE],
+    ) -> Result<Self, Error> {
+        Ok(Self {
+            name: validate_name(name.as_bytes())?,
+            digest,
+        })
+    }
+
+    /// Construct a [StorePathRef] from an absolute store path string.
+    /// This is equivalent to calling [StorePathRef::from_bytes], but stripping
+    /// the [STORE_DIR_WITH_SLASH] prefix before.
+    pub fn from_absolute_path(s: &'a [u8]) -> Result<Self, Error> {
+        match s.strip_prefix(STORE_DIR_WITH_SLASH.as_bytes()) {
+            Some(s_stripped) => Self::from_bytes(s_stripped),
+            None => Err(Error::MissingStoreDir),
+        }
+    }
+
+    /// Construct a [StorePathRef] by passing the `$digest-$name` string
+    /// that comes after [STORE_DIR_WITH_SLASH].
+    pub fn from_bytes(s: &'a [u8]) -> Result<Self, Error> {
+        // the whole string needs to be at least:
+        //
+        // - 32 characters (encoded hash)
+        // - 1 dash
+        // - 1 character for the name
+        if s.len() < ENCODED_DIGEST_SIZE + 2 {
+            Err(Error::InvalidLength)?
+        }
+
+        let digest = nixbase32::decode_fixed(&s[..ENCODED_DIGEST_SIZE])?;
+
+        if s[ENCODED_DIGEST_SIZE] != b'-' {
+            return Err(Error::MissingDash);
+        }
+
+        Ok(StorePathRef {
+            digest,
+            name: validate_name(&s[ENCODED_DIGEST_SIZE + 1..])?,
+        })
+    }
+
+    /// Returns an absolute store path string.
+    /// That is just the string representation, prefixed with the store prefix
+    /// ([STORE_DIR_WITH_SLASH]),
+    pub fn to_absolute_path(&self) -> String {
+        format!("{}{}", STORE_DIR_WITH_SLASH, self)
+    }
+}
+
+impl<'de: 'a, 'a> Deserialize<'de> for StorePathRef<'a> {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: serde::Deserializer<'de>,
+    {
+        let string: &'de str = Deserialize::deserialize(deserializer)?;
+        let stripped: Option<&str> = string.strip_prefix(STORE_DIR_WITH_SLASH);
+        let stripped: &str = stripped.ok_or_else(|| {
+            serde::de::Error::invalid_value(
+                serde::de::Unexpected::Str(string),
+                &"store path prefix",
+            )
+        })?;
+        StorePathRef::from_bytes(stripped.as_bytes()).map_err(|_| {
+            serde::de::Error::invalid_value(serde::de::Unexpected::Str(string), &"StorePath")
+        })
+    }
+}
+
+impl Serialize for StorePathRef<'_> {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: serde::Serializer,
+    {
+        let string: String = self.to_absolute_path();
+        string.serialize(serializer)
+    }
+}
+
+/// NAME_CHARS contains `true` for bytes that are valid in store path names.
+static NAME_CHARS: [bool; 256] = {
+    let mut tbl = [false; 256];
+    let mut c = 0;
+
+    loop {
+        tbl[c as usize] = matches!(c, b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'+' | b'-' | b'_' | b'?' | b'=' | b'.');
+
+        if c == u8::MAX {
+            break;
+        }
+
+        c += 1;
+    }
+
+    tbl
+};
+
+/// Checks a given &[u8] to match the restrictions for [StorePath::name], and
+/// returns the name as string if successful.
+pub(crate) fn validate_name(s: &(impl AsRef<[u8]> + ?Sized)) -> Result<&str, Error> {
+    let s = s.as_ref();
+
+    // Empty or excessively long names are not allowed.
+    if s.is_empty() || s.len() > 211 {
+        return Err(Error::InvalidLength);
+    }
+
+    let mut valid = true;
+    for &c in s {
+        valid = valid && NAME_CHARS[c as usize];
+    }
+
+    if !valid {
+        for (i, &c) in s.iter().enumerate() {
+            if !NAME_CHARS[c as usize] {
+                return Err(Error::InvalidName(s.to_vec(), i as u8));
+            }
+        }
+
+        unreachable!();
+    }
+
+    // SAFETY: We permit a subset of ASCII, which guarantees valid UTF-8.
+    Ok(unsafe { str::from_utf8_unchecked(s) })
+}
+
+impl fmt::Display for StorePath {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        StorePathRef::from(self).fmt(f)
+    }
+}
+
+impl fmt::Display for StorePathRef<'_> {
+    /// The string representation of a store path starts with a digest (20
+    /// bytes), [crate::nixbase32]-encoded, followed by a `-`,
+    /// and ends with the name.
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "{}-{}", nixbase32::encode(&self.digest), self.name)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::Error;
+    use std::cmp::Ordering;
+    use std::path::PathBuf;
+
+    use crate::store_path::{StorePath, StorePathRef, DIGEST_SIZE};
+    use hex_literal::hex;
+    use pretty_assertions::assert_eq;
+    use rstest::rstest;
+    use serde::Deserialize;
+
+    #[derive(Deserialize)]
+    /// An example struct, holding a StorePathRef.
+    /// Used to test deserializing StorePathRef.
+    struct Container<'a> {
+        #[serde(borrow)]
+        store_path: StorePathRef<'a>,
+    }
+
+    #[test]
+    fn happy_path() {
+        let example_nix_path_str =
+            "00bgd045z0d4icpbc2yyz4gx48ak44la-net-tools-1.60_p20170221182432";
+        let nixpath = StorePath::from_bytes(example_nix_path_str.as_bytes())
+            .expect("Error parsing example string");
+
+        let expected_digest: [u8; DIGEST_SIZE] = hex!("8a12321522fd91efbd60ebb2481af88580f61600");
+
+        assert_eq!("net-tools-1.60_p20170221182432", nixpath.name);
+        assert_eq!(nixpath.digest, expected_digest);
+
+        assert_eq!(example_nix_path_str, nixpath.to_string())
+    }
+
+    #[test]
+    fn store_path_ordering() {
+        let store_paths = [
+            "/nix/store/0lk5dgi01r933abzfj9c9wlndg82yd3g-psutil-5.9.6.tar.gz.drv",
+            "/nix/store/1xj43bva89f9qmwm37zl7r3d7m67i9ck-shorttoc-1.3-tex.drv",
+            "/nix/store/2gb633czchi20jq1kqv70rx2yvvgins8-lifted-base-0.2.3.12.tar.gz.drv",
+            "/nix/store/2vksym3r3zqhp15q3fpvw2mnvffv11b9-docbook-xml-4.5.zip.drv",
+            "/nix/store/5q918awszjcz5720xvpc2czbg1sdqsf0-rust_renaming-0.1.0-lib",
+            "/nix/store/7jw30i342sr2p1fmz5xcfnch65h4zbd9-dbus-1.14.10.tar.xz.drv",
+            "/nix/store/96yqwqhnp3qya4rf4n0rcl0lwvrylp6k-eap8021x-222.40.1.tar.gz.drv",
+            "/nix/store/9gjqg36a1v0axyprbya1hkaylmnffixg-virtualenv-20.24.5.tar.gz.drv",
+            "/nix/store/a4i5mci2g9ada6ff7ks38g11dg6iqyb8-perl-5.32.1.drv",
+            "/nix/store/a5g76ljava4h5pxlggz3aqdhs3a4fk6p-ToolchainInfo.plist.drv",
+            "/nix/store/db46l7d6nswgz4ffp1mmd56vjf9g51v6-version.plist.drv",
+            "/nix/store/g6f7w20sd7vwy0rc1r4bfsw4ciclrm4q-crates-io-num_cpus-1.12.0.drv",
+            "/nix/store/iw82n1wwssb8g6772yddn8c3vafgv9np-bootstrap-stage1-sysctl-stdenv-darwin.drv",
+            "/nix/store/lp78d1y5wxpcn32d5c4r7xgbjwiw0cgf-logo.svg.drv",
+            "/nix/store/mf00ank13scv1f9l1zypqdpaawjhfr3s-python3.11-psutil-5.9.6.drv",
+            "/nix/store/mpfml61ra7pz90124jx9r3av0kvkz2w1-perl5.36.0-Encode-Locale-1.05",
+            "/nix/store/qhsvwx4h87skk7c4mx0xljgiy3z93i23-source.drv",
+            "/nix/store/riv7d73adim8hq7i04pr8kd0jnj93nav-fdk-aac-2.0.2.tar.gz.drv",
+            "/nix/store/s64b9031wga7vmpvgk16xwxjr0z9ln65-human-signals-5.0.0.tgz-extracted",
+            "/nix/store/w6svg3m2xdh6dhx0gl1nwa48g57d3hxh-thiserror-1.0.49",
+        ];
+
+        for w in store_paths.windows(2) {
+            if w.len() < 2 {
+                continue;
+            }
+            let (pa, _) = StorePath::from_absolute_path_full(w[0]).expect("parseable");
+            let (pb, _) = StorePath::from_absolute_path_full(w[1]).expect("parseable");
+            assert_eq!(
+                Ordering::Less,
+                pa.cmp(&pb),
+                "{:?} not less than {:?}",
+                w[0],
+                w[1]
+            );
+        }
+    }
+
+    /// This is the store path *accepted* when `nix-store --add`'ing an
+    /// empty `.gitignore` file.
+    ///
+    /// Nix 2.4 accidentally permitted this behaviour, but the revert came
+    /// too late to beat Hyrum's law. It is now considered permissible.
+    ///
+    /// https://github.com/NixOS/nix/pull/9095 (revert)
+    /// https://github.com/NixOS/nix/pull/9867 (revert-of-revert)
+    #[test]
+    fn starts_with_dot() {
+        StorePath::from_bytes(b"fli4bwscgna7lpm7v5xgnjxrxh0yc7ra-.gitignore")
+            .expect("must succeed");
+    }
+
+    #[test]
+    fn empty_name() {
+        StorePath::from_bytes(b"00bgd045z0d4icpbc2yy-").expect_err("must fail");
+    }
+
+    #[test]
+    fn excessive_length() {
+        StorePath::from_bytes(b"00bgd045z0d4icpbc2yy-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
+            .expect_err("must fail");
+    }
+
+    #[test]
+    fn invalid_hash_length() {
+        StorePath::from_bytes(b"00bgd045z0d4icpbc2yy-net-tools-1.60_p20170221182432")
+            .expect_err("must fail");
+    }
+
+    #[test]
+    fn invalid_encoding_hash() {
+        StorePath::from_bytes(b"00bgd045z0d4icpbc2yyz4gx48aku4la-net-tools-1.60_p20170221182432")
+            .expect_err("must fail");
+    }
+
+    #[test]
+    fn more_than_just_the_bare_nix_store_path() {
+        StorePath::from_bytes(
+            b"00bgd045z0d4icpbc2yyz4gx48aku4la-net-tools-1.60_p20170221182432/bin/arp",
+        )
+        .expect_err("must fail");
+    }
+
+    #[test]
+    fn no_dash_between_hash_and_name() {
+        StorePath::from_bytes(b"00bgd045z0d4icpbc2yyz4gx48ak44lanet-tools-1.60_p20170221182432")
+            .expect_err("must fail");
+    }
+
+    #[test]
+    fn absolute_path() {
+        let example_nix_path_str =
+            "00bgd045z0d4icpbc2yyz4gx48ak44la-net-tools-1.60_p20170221182432";
+        let nixpath_expected =
+            StorePathRef::from_bytes(example_nix_path_str.as_bytes()).expect("must parse");
+
+        let nixpath_actual = StorePathRef::from_absolute_path(
+            "/nix/store/00bgd045z0d4icpbc2yyz4gx48ak44la-net-tools-1.60_p20170221182432".as_bytes(),
+        )
+        .expect("must parse");
+
+        assert_eq!(nixpath_expected, nixpath_actual);
+
+        assert_eq!(
+            "/nix/store/00bgd045z0d4icpbc2yyz4gx48ak44la-net-tools-1.60_p20170221182432",
+            nixpath_actual.to_absolute_path(),
+        );
+    }
+
+    #[test]
+    fn absolute_path_missing_prefix() {
+        assert_eq!(
+            Error::MissingStoreDir,
+            StorePathRef::from_absolute_path(b"foobar-123").expect_err("must fail")
+        );
+    }
+
+    #[test]
+    fn serialize_ref() {
+        let nixpath_actual = StorePathRef::from_bytes(
+            b"00bgd045z0d4icpbc2yyz4gx48ak44la-net-tools-1.60_p20170221182432",
+        )
+        .expect("can parse");
+
+        let serialized = serde_json::to_string(&nixpath_actual).expect("can serialize");
+
+        assert_eq!(
+            "\"/nix/store/00bgd045z0d4icpbc2yyz4gx48ak44la-net-tools-1.60_p20170221182432\"",
+            &serialized
+        );
+    }
+
+    #[test]
+    fn serialize_owned() {
+        let nixpath_actual = StorePath::from_bytes(
+            b"00bgd045z0d4icpbc2yyz4gx48ak44la-net-tools-1.60_p20170221182432",
+        )
+        .expect("can parse");
+
+        let serialized = serde_json::to_string(&nixpath_actual).expect("can serialize");
+
+        assert_eq!(
+            "\"/nix/store/00bgd045z0d4icpbc2yyz4gx48ak44la-net-tools-1.60_p20170221182432\"",
+            &serialized
+        );
+    }
+
+    #[test]
+    fn deserialize_ref() {
+        let store_path_str_json =
+            "\"/nix/store/00bgd045z0d4icpbc2yyz4gx48ak44la-net-tools-1.60_p20170221182432\"";
+
+        let store_path: StorePathRef<'_> =
+            serde_json::from_str(store_path_str_json).expect("valid json");
+
+        assert_eq!(
+            "/nix/store/00bgd045z0d4icpbc2yyz4gx48ak44la-net-tools-1.60_p20170221182432",
+            store_path.to_absolute_path()
+        );
+    }
+
+    #[test]
+    fn deserialize_ref_container() {
+        let str_json = "{\"store_path\":\"/nix/store/00bgd045z0d4icpbc2yyz4gx48ak44la-net-tools-1.60_p20170221182432\"}";
+
+        let container: Container<'_> = serde_json::from_str(str_json).expect("must deserialize");
+
+        assert_eq!(
+            "/nix/store/00bgd045z0d4icpbc2yyz4gx48ak44la-net-tools-1.60_p20170221182432",
+            container.store_path.to_absolute_path()
+        );
+    }
+
+    #[test]
+    fn deserialize_owned() {
+        let store_path_str_json =
+            "\"/nix/store/00bgd045z0d4icpbc2yyz4gx48ak44la-net-tools-1.60_p20170221182432\"";
+
+        let store_path: StorePath = serde_json::from_str(store_path_str_json).expect("valid json");
+
+        assert_eq!(
+            "/nix/store/00bgd045z0d4icpbc2yyz4gx48ak44la-net-tools-1.60_p20170221182432",
+            store_path.to_absolute_path()
+        );
+    }
+
+    #[rstest]
+    #[case::without_prefix(
+        "/nix/store/00bgd045z0d4icpbc2yyz4gx48ak44la-net-tools-1.60_p20170221182432",
+        StorePath::from_bytes(b"00bgd045z0d4icpbc2yyz4gx48ak44la-net-tools-1.60_p20170221182432").unwrap(), PathBuf::new())]
+    #[case::without_prefix_but_trailing_slash(
+        "/nix/store/00bgd045z0d4icpbc2yyz4gx48ak44la-net-tools-1.60_p20170221182432/",
+        StorePath::from_bytes(b"00bgd045z0d4icpbc2yyz4gx48ak44la-net-tools-1.60_p20170221182432").unwrap(), PathBuf::new())]
+    #[case::with_prefix(
+        "/nix/store/00bgd045z0d4icpbc2yyz4gx48ak44la-net-tools-1.60_p20170221182432/bin/arp",
+        StorePath::from_bytes(b"00bgd045z0d4icpbc2yyz4gx48ak44la-net-tools-1.60_p20170221182432").unwrap(), PathBuf::from("bin/arp"))]
+    #[case::with_prefix_and_trailing_slash(
+        "/nix/store/00bgd045z0d4icpbc2yyz4gx48ak44la-net-tools-1.60_p20170221182432/bin/arp/",
+        StorePath::from_bytes(b"00bgd045z0d4icpbc2yyz4gx48ak44la-net-tools-1.60_p20170221182432").unwrap(), PathBuf::from("bin/arp/"))]
+    fn from_absolute_path_full(
+        #[case] s: &str,
+        #[case] exp_store_path: StorePath,
+        #[case] exp_path: PathBuf,
+    ) {
+        let (actual_store_path, actual_path) =
+            StorePath::from_absolute_path_full(s).expect("must succeed");
+
+        assert_eq!(exp_store_path, actual_store_path);
+        assert_eq!(exp_path, actual_path);
+    }
+
+    #[test]
+    fn from_absolute_path_errors() {
+        assert_eq!(
+            Error::InvalidLength,
+            StorePath::from_absolute_path_full("/nix/store/").expect_err("must fail")
+        );
+        assert_eq!(
+            Error::InvalidLength,
+            StorePath::from_absolute_path_full("/nix/store/foo").expect_err("must fail")
+        );
+        assert_eq!(
+            Error::MissingStoreDir,
+            StorePath::from_absolute_path_full(
+                "00bgd045z0d4icpbc2yyz4gx48ak44la-net-tools-1.60_p20170221182432"
+            )
+            .expect_err("must fail")
+        );
+    }
+}
diff --git a/tvix/nix-compat/src/store_path/utils.rs b/tvix/nix-compat/src/store_path/utils.rs
new file mode 100644
index 0000000000..d6f390db85
--- /dev/null
+++ b/tvix/nix-compat/src/store_path/utils.rs
@@ -0,0 +1,293 @@
+use crate::nixbase32;
+use crate::nixhash::{CAHash, NixHash};
+use crate::store_path::{Error, StorePathRef, STORE_DIR};
+use data_encoding::HEXLOWER;
+use sha2::{Digest, Sha256};
+use thiserror;
+
+/// Errors that can occur when creating a content-addressed store path.
+///
+/// This wraps the main [crate::store_path::Error]..
+#[derive(Debug, PartialEq, Eq, thiserror::Error)]
+pub enum BuildStorePathError {
+    #[error("Invalid Store Path: {0}")]
+    InvalidStorePath(Error),
+    /// This error occurs when we have references outside the SHA-256 +
+    /// Recursive case. The restriction comes from upstream Nix. It may be
+    /// lifted at some point but there isn't a pressing need to anticipate that.
+    #[error("References were not supported as much as requested")]
+    InvalidReference(),
+}
+
+/// compress_hash takes an arbitrarily long sequence of bytes (usually
+/// a hash digest), and returns a sequence of bytes of length
+/// OUTPUT_SIZE.
+///
+/// It's calculated by rotating through the bytes in the output buffer
+/// (zero- initialized), and XOR'ing with each byte of the passed
+/// input. It consumes 1 byte at a time, and XOR's it with the current
+/// value in the output buffer.
+///
+/// This mimics equivalent functionality in C++ Nix.
+pub fn compress_hash<const OUTPUT_SIZE: usize>(input: &[u8]) -> [u8; OUTPUT_SIZE] {
+    let mut output = [0; OUTPUT_SIZE];
+
+    for (ii, ch) in input.iter().enumerate() {
+        output[ii % OUTPUT_SIZE] ^= ch;
+    }
+
+    output
+}
+
+/// This builds a store path, by calculating the text_hash_string of either a
+/// derivation or a literal text file that may contain references.
+/// If you don't want to have to pass the entire contents, you might want to use
+/// [build_ca_path] instead.
+pub fn build_text_path<S: AsRef<str>, I: IntoIterator<Item = S>, C: AsRef<[u8]>>(
+    name: &str,
+    content: C,
+    references: I,
+) -> Result<StorePathRef<'_>, BuildStorePathError> {
+    // produce the sha256 digest of the contents
+    let content_digest = Sha256::new_with_prefix(content).finalize().into();
+
+    build_ca_path(name, &CAHash::Text(content_digest), references, false)
+}
+
+/// This builds a store path from a [CAHash] and a list of references.
+pub fn build_ca_path<'a, S: AsRef<str>, I: IntoIterator<Item = S>>(
+    name: &'a str,
+    ca_hash: &CAHash,
+    references: I,
+    self_reference: bool,
+) -> Result<StorePathRef<'a>, BuildStorePathError> {
+    // self references are only allowed for CAHash::Nar(NixHash::Sha256(_)).
+    if self_reference && matches!(ca_hash, CAHash::Nar(NixHash::Sha256(_))) {
+        return Err(BuildStorePathError::InvalidReference());
+    }
+
+    /// Helper function, used for the non-sha256 [CAHash::Nar] and all [CAHash::Flat].
+    fn fixed_out_digest(prefix: &str, hash: &NixHash) -> [u8; 32] {
+        Sha256::new_with_prefix(format!("{}:{}:", prefix, hash.to_nix_hex_string()))
+            .finalize()
+            .into()
+    }
+
+    let (ty, inner_digest) = match &ca_hash {
+        CAHash::Text(ref digest) => (make_references_string("text", references, false), *digest),
+        CAHash::Nar(NixHash::Sha256(ref digest)) => (
+            make_references_string("source", references, self_reference),
+            *digest,
+        ),
+
+        // for all other CAHash::Nar, another custom scheme is used.
+        CAHash::Nar(ref hash) => {
+            if references.into_iter().next().is_some() {
+                return Err(BuildStorePathError::InvalidReference());
+            }
+
+            (
+                "output:out".to_string(),
+                fixed_out_digest("fixed:out:r", hash),
+            )
+        }
+        // CaHash::Flat is using something very similar, except the `r:` prefix.
+        CAHash::Flat(ref hash) => {
+            if references.into_iter().next().is_some() {
+                return Err(BuildStorePathError::InvalidReference());
+            }
+
+            (
+                "output:out".to_string(),
+                fixed_out_digest("fixed:out", hash),
+            )
+        }
+    };
+
+    build_store_path_from_fingerprint_parts(&ty, &inner_digest, name)
+        .map_err(BuildStorePathError::InvalidStorePath)
+}
+
+/// For given NAR sha256 digest and name, return the new [StorePathRef] this
+/// would have, or an error, in case the name is invalid.
+pub fn build_nar_based_store_path<'a>(
+    nar_sha256_digest: &[u8; 32],
+    name: &'a str,
+) -> Result<StorePathRef<'a>, BuildStorePathError> {
+    let nar_hash_with_mode = CAHash::Nar(NixHash::Sha256(nar_sha256_digest.to_owned()));
+
+    build_ca_path(name, &nar_hash_with_mode, Vec::<String>::new(), false)
+}
+
+/// This builds an input-addressed store path.
+///
+/// Input-addresed store paths are always derivation outputs, the "input" in question is the
+/// derivation and its closure.
+pub fn build_output_path<'a>(
+    drv_sha256: &[u8; 32],
+    output_name: &str,
+    output_path_name: &'a str,
+) -> Result<StorePathRef<'a>, Error> {
+    build_store_path_from_fingerprint_parts(
+        &(String::from("output:") + output_name),
+        drv_sha256,
+        output_path_name,
+    )
+}
+
+/// This builds a store path from fingerprint parts.
+/// Usually, that function is used from [build_text_path] and
+/// passed a "text hash string" (starting with "text:" as fingerprint),
+/// but other fingerprints starting with "output:" are also used in Derivation
+/// output path calculation.
+///
+/// The fingerprint is hashed with sha256, and its digest is compressed to 20
+/// bytes.
+/// Inside a StorePath, that digest is printed nixbase32-encoded
+/// (32 characters).
+fn build_store_path_from_fingerprint_parts<'a>(
+    ty: &str,
+    inner_digest: &[u8; 32],
+    name: &'a str,
+) -> Result<StorePathRef<'a>, Error> {
+    let fingerprint = format!(
+        "{ty}:sha256:{}:{STORE_DIR}:{name}",
+        HEXLOWER.encode(inner_digest)
+    );
+    // name validation happens in here.
+    StorePathRef::from_name_and_digest_fixed(
+        name,
+        compress_hash(&Sha256::new_with_prefix(fingerprint).finalize()),
+    )
+}
+
+/// This contains the Nix logic to create "text hash strings", which are used
+/// in `builtins.toFile`, as well as in Derivation Path calculation.
+///
+/// A text hash is calculated by concatenating the following fields, separated by a `:`:
+///
+///  - text
+///  - references, individually joined by `:`
+///  - the nix_hash_string representation of the sha256 digest of some contents
+///  - the value of `storeDir`
+///  - the name
+fn make_references_string<S: AsRef<str>, I: IntoIterator<Item = S>>(
+    ty: &str,
+    references: I,
+    self_ref: bool,
+) -> String {
+    let mut s = String::from(ty);
+
+    for reference in references {
+        s.push(':');
+        s.push_str(reference.as_ref());
+    }
+
+    if self_ref {
+        s.push_str(":self");
+    }
+
+    s
+}
+
+/// Nix placeholders (i.e. values returned by `builtins.placeholder`)
+/// are used to populate outputs with paths that must be
+/// string-replaced with the actual placeholders later, at runtime.
+///
+/// The actual placeholder is basically just a SHA256 hash encoded in
+/// cppnix format.
+pub fn hash_placeholder(name: &str) -> String {
+    let digest = Sha256::new_with_prefix(format!("nix-output:{}", name)).finalize();
+
+    format!("/{}", nixbase32::encode(&digest))
+}
+
+#[cfg(test)]
+mod test {
+    use hex_literal::hex;
+
+    use super::*;
+    use crate::nixhash::{CAHash, NixHash};
+
+    #[test]
+    fn build_text_path_with_zero_references() {
+        // This hash should match `builtins.toFile`, e.g.:
+        //
+        // nix-repl> builtins.toFile "foo" "bar"
+        // "/nix/store/vxjiwkjkn7x4079qvh1jkl5pn05j2aw0-foo"
+
+        let store_path = build_text_path("foo", "bar", Vec::<String>::new())
+            .expect("build_store_path() should succeed");
+
+        assert_eq!(
+            store_path.to_absolute_path().as_str(),
+            "/nix/store/vxjiwkjkn7x4079qvh1jkl5pn05j2aw0-foo"
+        );
+    }
+
+    #[test]
+    fn build_text_path_with_non_zero_references() {
+        // This hash should match:
+        //
+        // nix-repl> builtins.toFile "baz" "${builtins.toFile "foo" "bar"}"
+        // "/nix/store/5xd714cbfnkz02h2vbsj4fm03x3f15nf-baz"
+
+        let inner = build_text_path("foo", "bar", Vec::<String>::new())
+            .expect("path_with_references() should succeed");
+        let inner_path = inner.to_absolute_path();
+
+        let outer = build_text_path("baz", &inner_path, vec![inner_path.as_str()])
+            .expect("path_with_references() should succeed");
+
+        assert_eq!(
+            outer.to_absolute_path().as_str(),
+            "/nix/store/5xd714cbfnkz02h2vbsj4fm03x3f15nf-baz"
+        );
+    }
+
+    #[test]
+    fn build_sha1_path() {
+        let outer = build_ca_path(
+            "bar",
+            &CAHash::Nar(NixHash::Sha1(hex!(
+                "0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33"
+            ))),
+            Vec::<String>::new(),
+            false,
+        )
+        .expect("path_with_references() should succeed");
+
+        assert_eq!(
+            outer.to_absolute_path().as_str(),
+            "/nix/store/mp57d33657rf34lzvlbpfa1gjfv5gmpg-bar"
+        );
+    }
+
+    #[test]
+    fn build_store_path_with_non_zero_references() {
+        // This hash should match:
+        //
+        // nix-repl> builtins.toFile "baz" "${builtins.toFile "foo" "bar"}"
+        // "/nix/store/5xd714cbfnkz02h2vbsj4fm03x3f15nf-baz"
+        //
+        // $ nix store make-content-addressed /nix/store/5xd714cbfnkz02h2vbsj4fm03x3f15nf-baz
+        // rewrote '/nix/store/5xd714cbfnkz02h2vbsj4fm03x3f15nf-baz' to '/nix/store/s89y431zzhmdn3k8r96rvakryddkpv2v-baz'
+        let outer = build_ca_path(
+            "baz",
+            &CAHash::Nar(NixHash::Sha256(
+                nixbase32::decode(b"1xqkzcb3909fp07qngljr4wcdnrh1gdam1m2n29i6hhrxlmkgkv1")
+                    .expect("nixbase32 should decode")
+                    .try_into()
+                    .expect("should have right len"),
+            )),
+            vec!["/nix/store/dxwkwjzdaq7ka55pkk252gh32bgpmql4-foo"],
+            false,
+        )
+        .expect("path_with_references() should succeed");
+
+        assert_eq!(
+            outer.to_absolute_path().as_str(),
+            "/nix/store/s89y431zzhmdn3k8r96rvakryddkpv2v-baz"
+        );
+    }
+}
diff --git a/tvix/nix-compat/src/wire/bytes/mod.rs b/tvix/nix-compat/src/wire/bytes/mod.rs
new file mode 100644
index 0000000000..5ed5e15a64
--- /dev/null
+++ b/tvix/nix-compat/src/wire/bytes/mod.rs
@@ -0,0 +1,229 @@
+use std::{
+    io::{Error, ErrorKind},
+    ops::RangeInclusive,
+};
+use tokio::io::{AsyncReadExt, AsyncWriteExt};
+
+pub(crate) mod reader;
+pub use reader::BytesReader;
+mod writer;
+pub use writer::BytesWriter;
+
+/// 8 null bytes, used to write out padding.
+const EMPTY_BYTES: &[u8; 8] = &[0u8; 8];
+
+/// The length of the size field, in bytes is always 8.
+const LEN_SIZE: usize = 8;
+
+#[allow(dead_code)]
+/// Read a "bytes wire packet" from the AsyncRead.
+/// Rejects reading more than `allowed_size` bytes of payload.
+///
+/// The packet is made up of three parts:
+/// - a length header, u64, LE-encoded
+/// - the payload itself
+/// - null bytes to the next 8 byte boundary
+///
+/// Ensures the payload size fits into the `allowed_size` passed,
+/// and that the padding is actual null bytes.
+///
+/// On success, the returned `Vec<u8>` only contains the payload itself.
+/// On failure (for example if a too large byte packet was sent), the reader
+/// becomes unusable.
+///
+/// This buffers the entire payload into memory,
+/// a streaming version is available at [crate::wire::bytes::BytesReader].
+pub async fn read_bytes<R: ?Sized>(
+    r: &mut R,
+    allowed_size: RangeInclusive<usize>,
+) -> std::io::Result<Vec<u8>>
+where
+    R: AsyncReadExt + Unpin,
+{
+    // read the length field
+    let len = r.read_u64_le().await?;
+    let len: usize = len
+        .try_into()
+        .ok()
+        .filter(|len| allowed_size.contains(len))
+        .ok_or_else(|| {
+            std::io::Error::new(
+                std::io::ErrorKind::InvalidData,
+                "signalled package size not in allowed range",
+            )
+        })?;
+
+    // calculate the total length, including padding.
+    // byte packets are padded to 8 byte blocks each.
+    let padded_len = padding_len(len as u64) as u64 + (len as u64);
+    let mut limited_reader = r.take(padded_len);
+
+    let mut buf = Vec::new();
+
+    let s = limited_reader.read_to_end(&mut buf).await?;
+
+    // make sure we got exactly the number of bytes, and not less.
+    if s as u64 != padded_len {
+        return Err(std::io::ErrorKind::UnexpectedEof.into());
+    }
+
+    let (_content, padding) = buf.split_at(len);
+
+    // ensure the padding is all zeroes.
+    if !padding.iter().all(|e| *e == b'\0') {
+        return Err(std::io::Error::new(
+            std::io::ErrorKind::InvalidData,
+            "padding is not all zeroes",
+        ));
+    }
+
+    // return the data without the padding
+    buf.truncate(len);
+    Ok(buf)
+}
+
+/// Read a "bytes wire packet" of from the AsyncRead and tries to parse as string.
+/// Internally uses [read_bytes].
+/// Rejects reading more than `allowed_size` bytes of payload.
+pub async fn read_string<R>(
+    r: &mut R,
+    allowed_size: RangeInclusive<usize>,
+) -> std::io::Result<String>
+where
+    R: AsyncReadExt + Unpin,
+{
+    let bytes = read_bytes(r, allowed_size).await?;
+    String::from_utf8(bytes).map_err(|e| Error::new(ErrorKind::InvalidData, e))
+}
+
+/// Writes a "bytes wire packet" to a (hopefully buffered) [AsyncWriteExt].
+///
+/// Accepts anything implementing AsRef<[u8]> as payload.
+///
+/// See [read_bytes] for a description of the format.
+///
+/// Note: if performance matters to you, make sure your
+/// [AsyncWriteExt] handle is buffered. This function is quite
+/// write-intesive.
+pub async fn write_bytes<W: AsyncWriteExt + Unpin, B: AsRef<[u8]>>(
+    w: &mut W,
+    b: B,
+) -> std::io::Result<()> {
+    // write the size packet.
+    w.write_u64_le(b.as_ref().len() as u64).await?;
+
+    // write the payload
+    w.write_all(b.as_ref()).await?;
+
+    // write padding if needed
+    let padding_len = padding_len(b.as_ref().len() as u64) as usize;
+    if padding_len != 0 {
+        w.write_all(&EMPTY_BYTES[..padding_len]).await?;
+    }
+    Ok(())
+}
+
+/// Computes the number of bytes we should add to len (a length in
+/// bytes) to be aligned on 64 bits (8 bytes).
+fn padding_len(len: u64) -> u8 {
+    let aligned = len.wrapping_add(7) & !7;
+    aligned.wrapping_sub(len) as u8
+}
+
+#[cfg(test)]
+mod tests {
+    use tokio_test::{assert_ok, io::Builder};
+
+    use super::*;
+    use hex_literal::hex;
+
+    /// The maximum length of bytes packets we're willing to accept in the test
+    /// cases.
+    const MAX_LEN: usize = 1024;
+
+    #[tokio::test]
+    async fn test_read_8_bytes() {
+        let mut mock = Builder::new()
+            .read(&8u64.to_le_bytes())
+            .read(&12345678u64.to_le_bytes())
+            .build();
+
+        assert_eq!(
+            &12345678u64.to_le_bytes(),
+            read_bytes(&mut mock, 0..=MAX_LEN).await.unwrap().as_slice()
+        );
+    }
+
+    #[tokio::test]
+    async fn test_read_9_bytes() {
+        let mut mock = Builder::new()
+            .read(&9u64.to_le_bytes())
+            .read(&hex!("01020304050607080900000000000000"))
+            .build();
+
+        assert_eq!(
+            hex!("010203040506070809"),
+            read_bytes(&mut mock, 0..=MAX_LEN).await.unwrap().as_slice()
+        );
+    }
+
+    #[tokio::test]
+    async fn test_read_0_bytes() {
+        // A empty byte packet is essentially just the 0 length field.
+        // No data is read, and there's zero padding.
+        let mut mock = Builder::new().read(&0u64.to_le_bytes()).build();
+
+        assert_eq!(
+            hex!(""),
+            read_bytes(&mut mock, 0..=MAX_LEN).await.unwrap().as_slice()
+        );
+    }
+
+    #[tokio::test]
+    /// Ensure we don't read any further than the size field if the length
+    /// doesn't match the range we want to accept.
+    async fn test_read_reject_too_large() {
+        let mut mock = Builder::new().read(&100u64.to_le_bytes()).build();
+
+        read_bytes(&mut mock, 10..=10)
+            .await
+            .expect_err("expect this to fail");
+    }
+
+    #[tokio::test]
+    async fn test_write_bytes_no_padding() {
+        let input = hex!("6478696f34657661");
+        let len = input.len() as u64;
+        let mut mock = Builder::new()
+            .write(&len.to_le_bytes())
+            .write(&input)
+            .build();
+        assert_ok!(write_bytes(&mut mock, &input).await)
+    }
+    #[tokio::test]
+    async fn test_write_bytes_with_padding() {
+        let input = hex!("322e332e3137");
+        let len = input.len() as u64;
+        let mut mock = Builder::new()
+            .write(&len.to_le_bytes())
+            .write(&hex!("322e332e31370000"))
+            .build();
+        assert_ok!(write_bytes(&mut mock, &input).await)
+    }
+
+    #[tokio::test]
+    async fn test_write_string() {
+        let input = "Hello, World!";
+        let len = input.len() as u64;
+        let mut mock = Builder::new()
+            .write(&len.to_le_bytes())
+            .write(&hex!("48656c6c6f2c20576f726c6421000000"))
+            .build();
+        assert_ok!(write_bytes(&mut mock, &input).await)
+    }
+
+    #[test]
+    fn padding_len_u64_max() {
+        assert_eq!(padding_len(u64::MAX), 1);
+    }
+}
diff --git a/tvix/nix-compat/src/wire/bytes/reader/mod.rs b/tvix/nix-compat/src/wire/bytes/reader/mod.rs
new file mode 100644
index 0000000000..cd45f78a0c
--- /dev/null
+++ b/tvix/nix-compat/src/wire/bytes/reader/mod.rs
@@ -0,0 +1,447 @@
+use std::{
+    future::Future,
+    io,
+    ops::RangeBounds,
+    pin::Pin,
+    task::{self, ready, Poll},
+};
+use tokio::io::{AsyncRead, AsyncReadExt, ReadBuf};
+
+use trailer::{read_trailer, ReadTrailer, Trailer};
+
+#[doc(hidden)]
+pub use self::trailer::Pad;
+pub(crate) use self::trailer::Tag;
+mod trailer;
+
+/// Reads a "bytes wire packet" from the underlying reader.
+/// The format is the same as in [crate::wire::bytes::read_bytes],
+/// however this structure provides a [AsyncRead] interface,
+/// allowing to not having to pass around the entire payload in memory.
+///
+/// It is constructed by reading a size with [BytesReader::new],
+/// and yields payload data until the end of the packet is reached.
+///
+/// It will not return the final bytes before all padding has been successfully
+/// consumed as well, but the full length of the reader must be consumed.
+///
+/// If the data is not read all the way to the end, or an error is encountered,
+/// the underlying reader is no longer usable and might return garbage.
+#[derive(Debug)]
+#[allow(private_bounds)]
+pub struct BytesReader<R, T: Tag = Pad> {
+    state: State<R, T>,
+}
+
+#[derive(Debug)]
+enum State<R, T: Tag> {
+    /// Full 8-byte blocks are being read and released to the caller.
+    Body {
+        reader: Option<R>,
+        consumed: u64,
+        /// The total length of all user data contained in both the body and trailer.
+        user_len: u64,
+    },
+    /// The trailer is in the process of being read.
+    ReadTrailer(ReadTrailer<R, T>),
+    /// The trailer has been fully read and validated,
+    /// and data can now be released to the caller.
+    ReleaseTrailer { consumed: u8, data: Trailer },
+}
+
+impl<R> BytesReader<R>
+where
+    R: AsyncRead + Unpin,
+{
+    /// Constructs a new BytesReader, using the underlying passed reader.
+    pub async fn new<S: RangeBounds<u64>>(reader: R, allowed_size: S) -> io::Result<Self> {
+        BytesReader::new_internal(reader, allowed_size).await
+    }
+}
+
+#[allow(private_bounds)]
+impl<R, T: Tag> BytesReader<R, T>
+where
+    R: AsyncRead + Unpin,
+{
+    /// Constructs a new BytesReader, using the underlying passed reader.
+    pub(crate) async fn new_internal<S: RangeBounds<u64>>(
+        mut reader: R,
+        allowed_size: S,
+    ) -> io::Result<Self> {
+        let size = reader.read_u64_le().await?;
+
+        if !allowed_size.contains(&size) {
+            return Err(io::Error::new(io::ErrorKind::InvalidData, "invalid size"));
+        }
+
+        Ok(Self {
+            state: State::Body {
+                reader: Some(reader),
+                consumed: 0,
+                user_len: size,
+            },
+        })
+    }
+
+    /// Returns whether there is any remaining data to be read.
+    pub fn is_empty(&self) -> bool {
+        self.len() == 0
+    }
+
+    /// Remaining data length, ie not including data already read.
+    ///
+    /// If the size has not been read yet, this is [None].
+    pub fn len(&self) -> u64 {
+        match self.state {
+            State::Body {
+                consumed, user_len, ..
+            } => user_len - consumed,
+            State::ReadTrailer(ref fut) => fut.len() as u64,
+            State::ReleaseTrailer { consumed, ref data } => data.len() as u64 - consumed as u64,
+        }
+    }
+}
+
+#[allow(private_bounds)]
+impl<R: AsyncRead + Unpin, T: Tag> AsyncRead for BytesReader<R, T> {
+    fn poll_read(
+        mut self: Pin<&mut Self>,
+        cx: &mut task::Context,
+        buf: &mut ReadBuf,
+    ) -> Poll<io::Result<()>> {
+        let this = &mut self.state;
+
+        loop {
+            match this {
+                State::Body {
+                    reader,
+                    consumed,
+                    user_len,
+                } => {
+                    let body_len = *user_len & !7;
+                    let remaining = body_len - *consumed;
+
+                    let reader = if remaining == 0 {
+                        let reader = reader.take().unwrap();
+                        let user_len = (*user_len & 7) as u8;
+                        *this = State::ReadTrailer(read_trailer(reader, user_len));
+                        continue;
+                    } else {
+                        reader.as_mut().unwrap()
+                    };
+
+                    let mut bytes_read = 0;
+                    ready!(with_limited(buf, remaining, |buf| {
+                        let ret = Pin::new(reader).poll_read(cx, buf);
+                        bytes_read = buf.initialized().len();
+                        ret
+                    }))?;
+
+                    *consumed += bytes_read as u64;
+
+                    return if bytes_read != 0 {
+                        Ok(())
+                    } else {
+                        Err(io::ErrorKind::UnexpectedEof.into())
+                    }
+                    .into();
+                }
+                State::ReadTrailer(fut) => {
+                    *this = State::ReleaseTrailer {
+                        consumed: 0,
+                        data: ready!(Pin::new(fut).poll(cx))?,
+                    };
+                }
+                State::ReleaseTrailer { consumed, data } => {
+                    let data = &data[*consumed as usize..];
+                    let data = &data[..usize::min(data.len(), buf.remaining())];
+
+                    buf.put_slice(data);
+                    *consumed += data.len() as u8;
+
+                    return Ok(()).into();
+                }
+            }
+        }
+    }
+}
+
+/// Make a limited version of `buf`, consisting only of up to `n` bytes of the unfilled section, and call `f` with it.
+/// After `f` returns, we propagate the filled cursor advancement back to `buf`.
+fn with_limited<R>(buf: &mut ReadBuf, n: u64, f: impl FnOnce(&mut ReadBuf) -> R) -> R {
+    let mut nbuf = buf.take(n.try_into().unwrap_or(usize::MAX));
+    let ptr = nbuf.initialized().as_ptr();
+    let ret = f(&mut nbuf);
+
+    // SAFETY: `ReadBuf::take` only returns the *unfilled* section of `buf`,
+    // so anything filled is new, initialized data.
+    //
+    // We verify that `nbuf` still points to the same buffer,
+    // so we're sure it hasn't been swapped out.
+    unsafe {
+        // ensure our buffer hasn't been swapped out
+        assert_eq!(nbuf.initialized().as_ptr(), ptr);
+
+        let n = nbuf.filled().len();
+        buf.assume_init(n);
+        buf.advance(n);
+    }
+
+    ret
+}
+
+#[cfg(test)]
+mod tests {
+    use std::time::Duration;
+
+    use crate::wire::bytes::{padding_len, write_bytes};
+    use hex_literal::hex;
+    use lazy_static::lazy_static;
+    use rstest::rstest;
+    use tokio::io::AsyncReadExt;
+    use tokio_test::io::Builder;
+
+    use super::*;
+
+    /// The maximum length of bytes packets we're willing to accept in the test
+    /// cases.
+    const MAX_LEN: u64 = 1024;
+
+    lazy_static! {
+        pub static ref LARGE_PAYLOAD: Vec<u8> = (0..255).collect::<Vec<u8>>().repeat(4 * 1024);
+    }
+
+    /// Helper function, calling the (simpler) write_bytes with the payload.
+    /// We use this to create data we want to read from the wire.
+    async fn produce_packet_bytes(payload: &[u8]) -> Vec<u8> {
+        let mut exp = vec![];
+        write_bytes(&mut exp, payload).await.unwrap();
+        exp
+    }
+
+    /// Read bytes packets of various length, and ensure read_to_end returns the
+    /// expected payload.
+    #[rstest]
+    #[case::empty(&[])] // empty bytes packet
+    #[case::size_1b(&[0xff])] // 1 bytes payload
+    #[case::size_8b(&hex!("0001020304050607"))] // 8 bytes payload (no padding)
+    #[case::size_9b(&hex!("000102030405060708"))] // 9 bytes payload (7 bytes padding)
+    #[case::size_1m(LARGE_PAYLOAD.as_slice())] // larger bytes packet
+    #[tokio::test]
+    async fn read_payload_correct(#[case] payload: &[u8]) {
+        let mut mock = Builder::new()
+            .read(&produce_packet_bytes(payload).await)
+            .build();
+
+        let mut r = BytesReader::new(&mut mock, ..=LARGE_PAYLOAD.len() as u64)
+            .await
+            .unwrap();
+        let mut buf = Vec::new();
+        r.read_to_end(&mut buf).await.expect("must succeed");
+
+        assert_eq!(payload, &buf[..]);
+    }
+
+    /// Fail if the bytes packet is larger than allowed
+    #[tokio::test]
+    async fn read_bigger_than_allowed_fail() {
+        let payload = LARGE_PAYLOAD.as_slice();
+        let mut mock = Builder::new()
+            .read(&produce_packet_bytes(payload).await[0..8]) // We stop reading after the size packet
+            .build();
+
+        assert_eq!(
+            BytesReader::new(&mut mock, ..2048)
+                .await
+                .unwrap_err()
+                .kind(),
+            io::ErrorKind::InvalidData
+        );
+    }
+
+    /// Fail if the bytes packet is smaller than allowed
+    #[tokio::test]
+    async fn read_smaller_than_allowed_fail() {
+        let payload = &[0x00, 0x01, 0x02];
+        let mut mock = Builder::new()
+            .read(&produce_packet_bytes(payload).await[0..8]) // We stop reading after the size packet
+            .build();
+
+        assert_eq!(
+            BytesReader::new(&mut mock, 1024..2048)
+                .await
+                .unwrap_err()
+                .kind(),
+            io::ErrorKind::InvalidData
+        );
+    }
+
+    /// Fail if the padding is not all zeroes
+    #[tokio::test]
+    async fn read_fail_if_nonzero_padding() {
+        let payload = &[0x00, 0x01, 0x02];
+        let mut packet_bytes = produce_packet_bytes(payload).await;
+        // Flip some bits in the padding
+        packet_bytes[12] = 0xff;
+        let mut mock = Builder::new().read(&packet_bytes).build(); // We stop reading after the faulty bit
+
+        let mut r = BytesReader::new(&mut mock, ..MAX_LEN).await.unwrap();
+        let mut buf = Vec::new();
+
+        r.read_to_end(&mut buf).await.expect_err("must fail");
+    }
+
+    /// Start a 9 bytes payload packet, but have the underlying reader return
+    /// EOF in the middle of the size packet (after 4 bytes).
+    /// We should get an unexpected EOF error, already when trying to read the
+    /// first byte (of payload)
+    #[tokio::test]
+    async fn read_9b_eof_during_size() {
+        let payload = &hex!("FF0102030405060708");
+        let mut mock = Builder::new()
+            .read(&produce_packet_bytes(payload).await[..4])
+            .build();
+
+        assert_eq!(
+            BytesReader::new(&mut mock, ..MAX_LEN)
+                .await
+                .expect_err("must fail")
+                .kind(),
+            io::ErrorKind::UnexpectedEof
+        );
+    }
+
+    /// Start a 9 bytes payload packet, but have the underlying reader return
+    /// EOF in the middle of the payload (4 bytes into the payload).
+    /// We should get an unexpected EOF error, after reading the first 4 bytes
+    /// (successfully).
+    #[tokio::test]
+    async fn read_9b_eof_during_payload() {
+        let payload = &hex!("FF0102030405060708");
+        let mut mock = Builder::new()
+            .read(&produce_packet_bytes(payload).await[..8 + 4])
+            .build();
+
+        let mut r = BytesReader::new(&mut mock, ..MAX_LEN).await.unwrap();
+        let mut buf = [0; 9];
+
+        r.read_exact(&mut buf[..4]).await.expect("must succeed");
+
+        assert_eq!(
+            r.read_exact(&mut buf[4..=4])
+                .await
+                .expect_err("must fail")
+                .kind(),
+            std::io::ErrorKind::UnexpectedEof
+        );
+    }
+
+    /// Start a 9 bytes payload packet, but don't supply the necessary padding.
+    /// This is expected to always fail before returning the final data.
+    #[rstest]
+    #[case::before_padding(8 + 9)]
+    #[case::during_padding(8 + 9 + 2)]
+    #[case::after_padding(8 + 9 + padding_len(9) as usize - 1)]
+    #[tokio::test]
+    async fn read_9b_eof_after_payload(#[case] offset: usize) {
+        let payload = &hex!("FF0102030405060708");
+        let mut mock = Builder::new()
+            .read(&produce_packet_bytes(payload).await[..offset])
+            .build();
+
+        let mut r = BytesReader::new(&mut mock, ..MAX_LEN).await.unwrap();
+
+        // read_exact of the payload *body* will succeed, but a subsequent read will
+        // return UnexpectedEof error.
+        assert_eq!(r.read_exact(&mut [0; 8]).await.unwrap(), 8);
+        assert_eq!(
+            r.read_exact(&mut [0]).await.unwrap_err().kind(),
+            std::io::ErrorKind::UnexpectedEof
+        );
+    }
+
+    /// Start a 9 bytes payload packet, but return an error after a certain position.
+    /// Ensure that error is propagated.
+    #[rstest]
+    #[case::during_size(4)]
+    #[case::before_payload(8)]
+    #[case::during_payload(8 + 4)]
+    #[case::before_padding(8 + 4)]
+    #[case::during_padding(8 + 9 + 2)]
+    #[tokio::test]
+    async fn propagate_error_from_reader(#[case] offset: usize) {
+        let payload = &hex!("FF0102030405060708");
+        let mut mock = Builder::new()
+            .read(&produce_packet_bytes(payload).await[..offset])
+            .read_error(std::io::Error::new(std::io::ErrorKind::Other, "foo"))
+            .build();
+
+        // Either length reading or data reading can fail, depending on which test case we're in.
+        let err: io::Error = async {
+            let mut r = BytesReader::new(&mut mock, ..MAX_LEN).await?;
+            let mut buf = Vec::new();
+
+            r.read_to_end(&mut buf).await?;
+
+            Ok(())
+        }
+        .await
+        .expect_err("must fail");
+
+        assert_eq!(
+            err.kind(),
+            std::io::ErrorKind::Other,
+            "error kind must match"
+        );
+
+        assert_eq!(
+            err.into_inner().unwrap().to_string(),
+            "foo",
+            "error payload must contain foo"
+        );
+    }
+
+    /// If there's an error right after the padding, we don't propagate it, as
+    /// we're done reading. We just return EOF.
+    #[tokio::test]
+    async fn no_error_after_eof() {
+        let payload = &hex!("FF0102030405060708");
+        let mut mock = Builder::new()
+            .read(&produce_packet_bytes(payload).await)
+            .read_error(std::io::Error::new(std::io::ErrorKind::Other, "foo"))
+            .build();
+
+        let mut r = BytesReader::new(&mut mock, ..MAX_LEN).await.unwrap();
+        let mut buf = Vec::new();
+
+        r.read_to_end(&mut buf).await.expect("must succeed");
+        assert_eq!(buf.as_slice(), payload);
+    }
+
+    /// Introduce various stalls in various places of the packet, to ensure we
+    /// handle these cases properly, too.
+    #[rstest]
+    #[case::beginning(0)]
+    #[case::before_payload(8)]
+    #[case::during_payload(8 + 4)]
+    #[case::before_padding(8 + 4)]
+    #[case::during_padding(8 + 9 + 2)]
+    #[tokio::test]
+    async fn read_payload_correct_pending(#[case] offset: usize) {
+        let payload = &hex!("FF0102030405060708");
+        let mut mock = Builder::new()
+            .read(&produce_packet_bytes(payload).await[..offset])
+            .wait(Duration::from_nanos(0))
+            .read(&produce_packet_bytes(payload).await[offset..])
+            .build();
+
+        let mut r = BytesReader::new(&mut mock, ..=LARGE_PAYLOAD.len() as u64)
+            .await
+            .unwrap();
+        let mut buf = Vec::new();
+        r.read_to_end(&mut buf).await.expect("must succeed");
+
+        assert_eq!(payload, &buf[..]);
+    }
+}
diff --git a/tvix/nix-compat/src/wire/bytes/reader/trailer.rs b/tvix/nix-compat/src/wire/bytes/reader/trailer.rs
new file mode 100644
index 0000000000..0b0c7b1355
--- /dev/null
+++ b/tvix/nix-compat/src/wire/bytes/reader/trailer.rs
@@ -0,0 +1,198 @@
+use std::{
+    fmt::Debug,
+    future::Future,
+    marker::PhantomData,
+    ops::Deref,
+    pin::Pin,
+    task::{self, ready, Poll},
+};
+
+use tokio::io::{self, AsyncRead, ReadBuf};
+
+/// Trailer represents up to 7 bytes of data read as part of the trailer block(s)
+#[derive(Debug)]
+pub(crate) struct Trailer {
+    data_len: u8,
+    buf: [u8; 7],
+}
+
+impl Deref for Trailer {
+    type Target = [u8];
+
+    fn deref(&self) -> &Self::Target {
+        &self.buf[..self.data_len as usize]
+    }
+}
+
+/// Tag defines a "trailer tag": specific, fixed bytes that must follow wire data.
+pub(crate) trait Tag {
+    /// The expected suffix
+    ///
+    /// The first 7 bytes may be ignored, and it must be an 8-byte aligned size.
+    const PATTERN: &'static [u8];
+
+    /// Suitably sized buffer for reading [Self::PATTERN]
+    ///
+    /// HACK: This is a workaround for const generics limitations.
+    type Buf: AsRef<[u8]> + AsMut<[u8]> + Debug + Unpin;
+
+    /// Make an instance of [Self::Buf]
+    fn make_buf() -> Self::Buf;
+}
+
+#[derive(Debug)]
+pub enum Pad {}
+
+impl Tag for Pad {
+    const PATTERN: &'static [u8] = &[0; 8];
+
+    type Buf = [u8; 8];
+
+    fn make_buf() -> Self::Buf {
+        [0; 8]
+    }
+}
+
+#[derive(Debug)]
+pub(crate) struct ReadTrailer<R, T: Tag> {
+    reader: R,
+    data_len: u8,
+    filled: u8,
+    buf: T::Buf,
+    _phantom: PhantomData<fn(T) -> T>,
+}
+
+/// read_trailer returns a [Future] that reads a trailer with a given [Tag] from `reader`
+pub(crate) fn read_trailer<R: AsyncRead + Unpin, T: Tag>(
+    reader: R,
+    data_len: u8,
+) -> ReadTrailer<R, T> {
+    assert!(data_len < 8, "payload in trailer must be less than 8 bytes");
+
+    let buf = T::make_buf();
+    assert_eq!(buf.as_ref().len(), T::PATTERN.len());
+    assert_eq!(T::PATTERN.len() % 8, 0);
+
+    ReadTrailer {
+        reader,
+        data_len,
+        filled: if data_len != 0 { 0 } else { 8 },
+        buf,
+        _phantom: PhantomData,
+    }
+}
+
+impl<R, T: Tag> ReadTrailer<R, T> {
+    pub fn len(&self) -> u8 {
+        self.data_len
+    }
+}
+
+impl<R: AsyncRead + Unpin, T: Tag> Future for ReadTrailer<R, T> {
+    type Output = io::Result<Trailer>;
+
+    fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context) -> Poll<Self::Output> {
+        let this = &mut *self;
+
+        loop {
+            if this.filled >= this.data_len {
+                let check_range = || this.data_len as usize..this.filled as usize;
+
+                if this.buf.as_ref()[check_range()] != T::PATTERN[check_range()] {
+                    return Err(io::Error::new(
+                        io::ErrorKind::InvalidData,
+                        "invalid trailer",
+                    ))
+                    .into();
+                }
+            }
+
+            if this.filled as usize == T::PATTERN.len() {
+                let mut buf = [0; 7];
+                buf.copy_from_slice(&this.buf.as_ref()[..7]);
+
+                return Ok(Trailer {
+                    data_len: this.data_len,
+                    buf,
+                })
+                .into();
+            }
+
+            let mut buf = ReadBuf::new(this.buf.as_mut());
+            buf.advance(this.filled as usize);
+
+            ready!(Pin::new(&mut this.reader).poll_read(cx, &mut buf))?;
+
+            this.filled = {
+                let prev_filled = this.filled;
+                let filled = buf.filled().len() as u8;
+
+                if filled == prev_filled {
+                    return Err(io::ErrorKind::UnexpectedEof.into()).into();
+                }
+
+                filled
+            };
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use std::time::Duration;
+
+    use super::*;
+
+    #[tokio::test]
+    async fn unexpected_eof() {
+        let reader = tokio_test::io::Builder::new()
+            .read(&[0xed])
+            .wait(Duration::ZERO)
+            .read(&[0xef, 0x00])
+            .build();
+
+        assert_eq!(
+            read_trailer::<_, Pad>(reader, 2).await.unwrap_err().kind(),
+            io::ErrorKind::UnexpectedEof
+        );
+    }
+
+    #[tokio::test]
+    async fn invalid_padding() {
+        let reader = tokio_test::io::Builder::new()
+            .read(&[0xed])
+            .wait(Duration::ZERO)
+            .read(&[0xef, 0x01, 0x00])
+            .wait(Duration::ZERO)
+            .build();
+
+        assert_eq!(
+            read_trailer::<_, Pad>(reader, 2).await.unwrap_err().kind(),
+            io::ErrorKind::InvalidData
+        );
+    }
+
+    #[tokio::test]
+    async fn success() {
+        let reader = tokio_test::io::Builder::new()
+            .read(&[0xed])
+            .wait(Duration::ZERO)
+            .read(&[0xef, 0x00])
+            .wait(Duration::ZERO)
+            .read(&[0x00, 0x00, 0x00, 0x00, 0x00])
+            .build();
+
+        assert_eq!(
+            &*read_trailer::<_, Pad>(reader, 2).await.unwrap(),
+            &[0xed, 0xef]
+        );
+    }
+
+    #[tokio::test]
+    async fn no_padding() {
+        assert!(read_trailer::<_, Pad>(io::empty(), 0)
+            .await
+            .unwrap()
+            .is_empty());
+    }
+}
diff --git a/tvix/nix-compat/src/wire/bytes/writer.rs b/tvix/nix-compat/src/wire/bytes/writer.rs
new file mode 100644
index 0000000000..f5632771e9
--- /dev/null
+++ b/tvix/nix-compat/src/wire/bytes/writer.rs
@@ -0,0 +1,538 @@
+use pin_project_lite::pin_project;
+use std::task::{ready, Poll};
+
+use tokio::io::AsyncWrite;
+
+use super::{padding_len, EMPTY_BYTES, LEN_SIZE};
+
+pin_project! {
+    /// Writes a "bytes wire packet" to the underlying writer.
+    /// The format is the same as in [crate::wire::bytes::write_bytes],
+    /// however this structure provides a [AsyncWrite] interface,
+    /// allowing to not having to pass around the entire payload in memory.
+    ///
+    /// It internally takes care of writing (non-payload) framing (size and
+    /// padding).
+    ///
+    /// During construction, the expected payload size needs to be provided.
+    ///
+    /// After writing the payload to it, the user MUST call flush (or shutdown),
+    /// which will validate the written payload size to match, and write the
+    /// necessary padding.
+    ///
+    /// In case flush is not called at the end, invalid data might be sent
+    /// silently.
+    ///
+    /// The underlying writer returning `Ok(0)` is considered an EOF situation,
+    /// which is stronger than the "typically means the underlying object is no
+    /// longer able to accept bytes" interpretation from the docs. If such a
+    /// situation occurs, an error is returned.
+    ///
+    /// The struct holds three fields, the underlying writer, the (expected)
+    /// payload length, and an enum, tracking the state.
+    pub struct BytesWriter<W>
+    where
+        W: AsyncWrite,
+    {
+        #[pin]
+        inner: W,
+        payload_len: u64,
+        state: BytesPacketPosition,
+    }
+}
+
+/// Models the position inside a "bytes wire packet" that the writer is in.
+/// It can be in three different stages, inside size, payload or padding fields.
+/// The number tracks the number of bytes written inside the specific field.
+/// There shall be no ambiguous states, at the end of a stage we immediately
+/// move to the beginning of the next one:
+/// - Size(LEN_SIZE) must be expressed as Payload(0)
+/// - Payload(self.payload_len) must be expressed as Padding(0)
+///
+/// Padding(padding_len) means we're at the end of the bytes wire packet.
+#[derive(Clone, Debug, PartialEq, Eq)]
+enum BytesPacketPosition {
+    Size(usize),
+    Payload(u64),
+    Padding(usize),
+}
+
+impl<W> BytesWriter<W>
+where
+    W: AsyncWrite,
+{
+    /// Constructs a new BytesWriter, using the underlying passed writer.
+    pub fn new(w: W, payload_len: u64) -> Self {
+        Self {
+            inner: w,
+            payload_len,
+            state: BytesPacketPosition::Size(0),
+        }
+    }
+}
+
+/// Returns an error if the passed usize is 0.
+#[inline]
+fn ensure_nonzero_bytes_written(bytes_written: usize) -> Result<usize, std::io::Error> {
+    if bytes_written == 0 {
+        Err(std::io::Error::new(
+            std::io::ErrorKind::WriteZero,
+            "underlying writer accepted 0 bytes",
+        ))
+    } else {
+        Ok(bytes_written)
+    }
+}
+
+impl<W> AsyncWrite for BytesWriter<W>
+where
+    W: AsyncWrite,
+{
+    fn poll_write(
+        self: std::pin::Pin<&mut Self>,
+        cx: &mut std::task::Context<'_>,
+        buf: &[u8],
+    ) -> Poll<Result<usize, std::io::Error>> {
+        // Use a loop, so we can deal with (multiple) state transitions.
+        let mut this = self.project();
+
+        loop {
+            match *this.state {
+                BytesPacketPosition::Size(LEN_SIZE) => unreachable!(),
+                BytesPacketPosition::Size(pos) => {
+                    let size_field = &this.payload_len.to_le_bytes();
+
+                    let bytes_written = ensure_nonzero_bytes_written(ready!(this
+                        .inner
+                        .as_mut()
+                        .poll_write(cx, &size_field[pos..]))?)?;
+
+                    let new_pos = pos + bytes_written;
+                    if new_pos == LEN_SIZE {
+                        *this.state = BytesPacketPosition::Payload(0);
+                    } else {
+                        *this.state = BytesPacketPosition::Size(new_pos);
+                    }
+                }
+                BytesPacketPosition::Payload(pos) => {
+                    // Ensure we still have space for more payload
+                    if pos + (buf.len() as u64) > *this.payload_len {
+                        return Poll::Ready(Err(std::io::Error::new(
+                            std::io::ErrorKind::InvalidData,
+                            "tried to write excess bytes",
+                        )));
+                    }
+                    let bytes_written = ready!(this.inner.as_mut().poll_write(cx, buf))?;
+                    ensure_nonzero_bytes_written(bytes_written)?;
+                    let new_pos = pos + (bytes_written as u64);
+                    if new_pos == *this.payload_len {
+                        *this.state = BytesPacketPosition::Padding(0)
+                    } else {
+                        *this.state = BytesPacketPosition::Payload(new_pos)
+                    }
+
+                    return Poll::Ready(Ok(bytes_written));
+                }
+                // If we're already in padding state, there should be no more payload left to write!
+                BytesPacketPosition::Padding(_pos) => {
+                    return Poll::Ready(Err(std::io::Error::new(
+                        std::io::ErrorKind::InvalidData,
+                        "tried to write excess bytes",
+                    )))
+                }
+            }
+        }
+    }
+
+    fn poll_flush(
+        self: std::pin::Pin<&mut Self>,
+        cx: &mut std::task::Context<'_>,
+    ) -> Poll<Result<(), std::io::Error>> {
+        let mut this = self.project();
+
+        loop {
+            match *this.state {
+                BytesPacketPosition::Size(LEN_SIZE) => unreachable!(),
+                BytesPacketPosition::Size(pos) => {
+                    // More bytes to write in the size field
+                    let size_field = &this.payload_len.to_le_bytes()[..];
+                    let bytes_written = ensure_nonzero_bytes_written(ready!(this
+                        .inner
+                        .as_mut()
+                        .poll_write(cx, &size_field[pos..]))?)?;
+                    let new_pos = pos + bytes_written;
+                    if new_pos == LEN_SIZE {
+                        // Size field written, now ready to receive payload
+                        *this.state = BytesPacketPosition::Payload(0);
+                    } else {
+                        *this.state = BytesPacketPosition::Size(new_pos);
+                    }
+                }
+                BytesPacketPosition::Payload(_pos) => {
+                    // If we're at position 0 and want to write 0 bytes of payload
+                    // in total, we can transition to padding.
+                    // Otherwise, break, as we're expecting more payload to
+                    // be written.
+                    if *this.payload_len == 0 {
+                        *this.state = BytesPacketPosition::Padding(0);
+                    } else {
+                        break;
+                    }
+                }
+                BytesPacketPosition::Padding(pos) => {
+                    // Write remaining padding, if there is padding to write.
+                    let total_padding_len = padding_len(*this.payload_len) as usize;
+
+                    if pos != total_padding_len {
+                        let bytes_written = ensure_nonzero_bytes_written(ready!(this
+                            .inner
+                            .as_mut()
+                            .poll_write(cx, &EMPTY_BYTES[pos..total_padding_len]))?)?;
+                        *this.state = BytesPacketPosition::Padding(pos + bytes_written);
+                    } else {
+                        // everything written, break
+                        break;
+                    }
+                }
+            }
+        }
+        // Flush the underlying writer.
+        this.inner.as_mut().poll_flush(cx)
+    }
+
+    fn poll_shutdown(
+        mut self: std::pin::Pin<&mut Self>,
+        cx: &mut std::task::Context<'_>,
+    ) -> Poll<Result<(), std::io::Error>> {
+        // Call flush.
+        ready!(self.as_mut().poll_flush(cx))?;
+
+        let this = self.project();
+
+        // After a flush, being inside the padding state, and at the end of the padding
+        // is the only way to prevent a dirty shutdown.
+        if let BytesPacketPosition::Padding(pos) = *this.state {
+            let padding_len = padding_len(*this.payload_len) as usize;
+            if padding_len == pos {
+                // Shutdown the underlying writer
+                return this.inner.poll_shutdown(cx);
+            }
+        }
+
+        // Shutdown the underlying writer, bubbling up any errors.
+        ready!(this.inner.poll_shutdown(cx))?;
+
+        // return an error about unclean shutdown
+        Poll::Ready(Err(std::io::Error::new(
+            std::io::ErrorKind::BrokenPipe,
+            "unclean shutdown",
+        )))
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use std::time::Duration;
+
+    use crate::wire::bytes::write_bytes;
+    use hex_literal::hex;
+    use lazy_static::lazy_static;
+    use tokio::io::AsyncWriteExt;
+    use tokio_test::{assert_err, assert_ok, io::Builder};
+
+    use super::*;
+
+    lazy_static! {
+        pub static ref LARGE_PAYLOAD: Vec<u8> = (0..255).collect::<Vec<u8>>().repeat(4 * 1024);
+    }
+
+    /// Helper function, calling the (simpler) write_bytes with the payload.
+    /// We use this to create data we want to see on the wire.
+    async fn produce_exp_bytes(payload: &[u8]) -> Vec<u8> {
+        let mut exp = vec![];
+        write_bytes(&mut exp, payload).await.unwrap();
+        exp
+    }
+
+    /// Write an empty bytes packet.
+    #[tokio::test]
+    async fn write_empty() {
+        let payload = &[];
+        let mut mock = Builder::new()
+            .write(&produce_exp_bytes(payload).await)
+            .build();
+
+        let mut w = BytesWriter::new(&mut mock, 0);
+        assert_ok!(w.write_all(&[]).await, "write all data");
+        assert_ok!(w.flush().await, "flush");
+    }
+
+    /// Write an empty bytes packet, not calling write.
+    #[tokio::test]
+    async fn write_empty_only_flush() {
+        let payload = &[];
+        let mut mock = Builder::new()
+            .write(&produce_exp_bytes(payload).await)
+            .build();
+
+        let mut w = BytesWriter::new(&mut mock, 0);
+        assert_ok!(w.flush().await, "flush");
+    }
+
+    /// Write an empty bytes packet, not calling write or flush, only shutdown.
+    #[tokio::test]
+    async fn write_empty_only_shutdown() {
+        let payload = &[];
+        let mut mock = Builder::new()
+            .write(&produce_exp_bytes(payload).await)
+            .build();
+
+        let mut w = BytesWriter::new(&mut mock, 0);
+        assert_ok!(w.shutdown().await, "shutdown");
+    }
+
+    /// Write a 1 bytes packet
+    #[tokio::test]
+    async fn write_1b() {
+        let payload = &[0xff];
+
+        let mut mock = Builder::new()
+            .write(&produce_exp_bytes(payload).await)
+            .build();
+
+        let mut w = BytesWriter::new(&mut mock, payload.len() as u64);
+        assert_ok!(w.write_all(payload).await);
+        assert_ok!(w.flush().await, "flush");
+    }
+
+    /// Write a 8 bytes payload (no padding)
+    #[tokio::test]
+    async fn write_8b() {
+        let payload = &hex!("0001020304050607");
+
+        let mut mock = Builder::new()
+            .write(&produce_exp_bytes(payload).await)
+            .build();
+
+        let mut w = BytesWriter::new(&mut mock, payload.len() as u64);
+        assert_ok!(w.write_all(payload).await);
+        assert_ok!(w.flush().await, "flush");
+    }
+
+    /// Write a 9 bytes payload (7 bytes padding)
+    #[tokio::test]
+    async fn write_9b() {
+        let payload = &hex!("000102030405060708");
+
+        let mut mock = Builder::new()
+            .write(&produce_exp_bytes(payload).await)
+            .build();
+
+        let mut w = BytesWriter::new(&mut mock, payload.len() as u64);
+        assert_ok!(w.write_all(payload).await);
+        assert_ok!(w.flush().await, "flush");
+    }
+
+    /// Write a 9 bytes packet very granularly, with a lot of flushing in between,
+    /// and a shutdown at the end.
+    #[tokio::test]
+    async fn write_9b_flush() {
+        let payload = &hex!("000102030405060708");
+        let exp_bytes = produce_exp_bytes(payload).await;
+
+        let mut mock = Builder::new().write(&exp_bytes).build();
+
+        let mut w = BytesWriter::new(&mut mock, payload.len() as u64);
+        assert_ok!(w.flush().await);
+
+        assert_ok!(w.write_all(&payload[..4]).await);
+        assert_ok!(w.flush().await);
+
+        // empty write, cause why not
+        assert_ok!(w.write_all(&[]).await);
+        assert_ok!(w.flush().await);
+
+        assert_ok!(w.write_all(&payload[4..]).await);
+        assert_ok!(w.flush().await);
+        assert_ok!(w.shutdown().await);
+    }
+
+    /// Write a 9 bytes packet, but cause the sink to only accept half of the
+    /// padding, ensuring we correctly write (only) the rest of the padding later.
+    /// We write another 2 bytes of "bait", where a faulty implementation (pre
+    /// cl/11384) would put too many null bytes.
+    #[tokio::test]
+    async fn write_9b_write_padding_2steps() {
+        let payload = &hex!("000102030405060708");
+        let exp_bytes = produce_exp_bytes(payload).await;
+
+        let mut mock = Builder::new()
+            .write(&exp_bytes[0..8]) // size
+            .write(&exp_bytes[8..17]) // payload
+            .write(&exp_bytes[17..19]) // padding (2 of 7 bytes)
+            // insert a wait to prevent Mock from merging the two writes into one
+            .wait(Duration::from_nanos(1))
+            .write(&hex!("0000000000ffff")) // padding (5 of 7 bytes, plus 2 bytes of "bait")
+            .build();
+
+        let mut w = BytesWriter::new(&mut mock, payload.len() as u64);
+        assert_ok!(w.write_all(&payload[..]).await);
+        assert_ok!(w.flush().await);
+        // Write bait
+        assert_ok!(mock.write_all(&hex!("ffff")).await);
+    }
+
+    /// Write a larger bytes packet
+    #[tokio::test]
+    async fn write_1m() {
+        let payload = LARGE_PAYLOAD.as_slice();
+        let exp_bytes = produce_exp_bytes(payload).await;
+
+        let mut mock = Builder::new().write(&exp_bytes).build();
+        let mut w = BytesWriter::new(&mut mock, payload.len() as u64);
+
+        assert_ok!(w.write_all(payload).await);
+        assert_ok!(w.flush().await, "flush");
+    }
+
+    /// Not calling flush at the end, but shutdown is also ok if we wrote all
+    /// bytes we promised to write (as shutdown implies flush)
+    #[tokio::test]
+    async fn write_shutdown_without_flush_end() {
+        let payload = &[0xf0, 0xff];
+        let exp_bytes = produce_exp_bytes(payload).await;
+
+        let mut mock = Builder::new().write(&exp_bytes).build();
+        let mut w = BytesWriter::new(&mut mock, payload.len() as u64);
+
+        // call flush to write the size field
+        assert_ok!(w.flush().await);
+
+        // write payload
+        assert_ok!(w.write_all(payload).await);
+
+        // call shutdown
+        assert_ok!(w.shutdown().await);
+    }
+
+    /// Writing more bytes than previously signalled should fail.
+    #[tokio::test]
+    async fn write_more_than_signalled_fail() {
+        let mut buf = Vec::new();
+        let mut w = BytesWriter::new(&mut buf, 2);
+
+        assert_err!(w.write_all(&hex!("000102")).await);
+    }
+    /// Writing more bytes than previously signalled, but in two parts
+    #[tokio::test]
+    async fn write_more_than_signalled_split_fail() {
+        let mut buf = Vec::new();
+        let mut w = BytesWriter::new(&mut buf, 2);
+
+        // write two bytes
+        assert_ok!(w.write_all(&hex!("0001")).await);
+
+        // write the excess byte.
+        assert_err!(w.write_all(&hex!("02")).await);
+    }
+
+    /// Writing more bytes than previously signalled, but flushing after the
+    /// signalled amount should fail.
+    #[tokio::test]
+    async fn write_more_than_signalled_flush_fail() {
+        let mut buf = Vec::new();
+        let mut w = BytesWriter::new(&mut buf, 2);
+
+        // write two bytes, then flush
+        assert_ok!(w.write_all(&hex!("0001")).await);
+        assert_ok!(w.flush().await);
+
+        // write the excess byte.
+        assert_err!(w.write_all(&hex!("02")).await);
+    }
+
+    /// Calling shutdown while not having written all bytes that were promised
+    /// returns an error.
+    /// Note there's still cases of silent corruption if the user doesn't call
+    /// shutdown explicitly (only drops).
+    #[tokio::test]
+    async fn premature_shutdown() {
+        let payload = &[0xf0, 0xff];
+        let mut buf = Vec::new();
+        let mut w = BytesWriter::new(&mut buf, payload.len() as u64);
+
+        // call flush to write the size field
+        assert_ok!(w.flush().await);
+
+        // write half of the payload (!)
+        assert_ok!(w.write_all(&payload[0..1]).await);
+
+        // call shutdown, ensure it fails
+        assert_err!(w.shutdown().await);
+    }
+
+    /// Write to a Writer that fails to write during the size packet (after 4 bytes).
+    /// Ensure this error gets propagated on the first call to write.
+    #[tokio::test]
+    async fn inner_writer_fail_during_size_firstwrite() {
+        let payload = &[0xf0];
+
+        let mut mock = Builder::new()
+            .write(&1u32.to_le_bytes())
+            .write_error(std::io::Error::new(std::io::ErrorKind::Other, "🍿"))
+            .build();
+        let mut w = BytesWriter::new(&mut mock, payload.len() as u64);
+
+        assert_err!(w.write_all(payload).await);
+    }
+
+    /// Write to a Writer that fails to write during the size packet (after 4 bytes).
+    /// Ensure this error gets propagated during an initial flush
+    #[tokio::test]
+    async fn inner_writer_fail_during_size_initial_flush() {
+        let payload = &[0xf0];
+
+        let mut mock = Builder::new()
+            .write(&1u32.to_le_bytes())
+            .write_error(std::io::Error::new(std::io::ErrorKind::Other, "🍿"))
+            .build();
+        let mut w = BytesWriter::new(&mut mock, payload.len() as u64);
+
+        assert_err!(w.flush().await);
+    }
+
+    /// Write to a writer that fails to write during the payload (after 9 bytes).
+    /// Ensure this error gets propagated when we're writing this byte.
+    #[tokio::test]
+    async fn inner_writer_fail_during_write() {
+        let payload = &hex!("f0ff");
+
+        let mut mock = Builder::new()
+            .write(&2u64.to_le_bytes())
+            .write(&hex!("f0"))
+            .write_error(std::io::Error::new(std::io::ErrorKind::Other, "🍿"))
+            .build();
+        let mut w = BytesWriter::new(&mut mock, payload.len() as u64);
+
+        assert_ok!(w.write(&hex!("f0")).await);
+        assert_err!(w.write(&hex!("ff")).await);
+    }
+
+    /// Write to a writer that fails to write during the padding (after 10 bytes).
+    /// Ensure this error gets propagated during a flush.
+    #[tokio::test]
+    async fn inner_writer_fail_during_padding_flush() {
+        let payload = &hex!("f0");
+
+        let mut mock = Builder::new()
+            .write(&1u64.to_le_bytes())
+            .write(&hex!("f0"))
+            .write(&hex!("00"))
+            .write_error(std::io::Error::new(std::io::ErrorKind::Other, "🍿"))
+            .build();
+        let mut w = BytesWriter::new(&mut mock, payload.len() as u64);
+
+        assert_ok!(w.write(&hex!("f0")).await);
+        assert_err!(w.flush().await);
+    }
+}
diff --git a/tvix/nix-compat/src/wire/mod.rs b/tvix/nix-compat/src/wire/mod.rs
new file mode 100644
index 0000000000..a197e3a1f4
--- /dev/null
+++ b/tvix/nix-compat/src/wire/mod.rs
@@ -0,0 +1,5 @@
+//! Module parsing and emitting the wire format used by Nix, both in the
+//! nix-daemon protocol as well as in the NAR format.
+
+mod bytes;
+pub use bytes::*;
diff --git a/tvix/nix-compat/testdata/narinfo.zst b/tvix/nix-compat/testdata/narinfo.zst
new file mode 100644
index 0000000000..361a422da8
--- /dev/null
+++ b/tvix/nix-compat/testdata/narinfo.zst
Binary files differdiff --git a/tvix/nix-lang-test-suite/README.md b/tvix/nix-lang-test-suite/README.md
new file mode 100644
index 0000000000..68f87f20f5
--- /dev/null
+++ b/tvix/nix-lang-test-suite/README.md
@@ -0,0 +1,140 @@
+# The Implementation Independent Nix Language Test Suite
+
+## Design Notes
+
+### Requirements
+
+- It should work with potentially any Nix implementation and with all serious
+  currently available ones (C++ Nix, hnix, Tvix, …). How much of it the
+  implementations pass, is of course an orthogonal question.
+- It should be easy to add test cases, independent of any specific
+  implementation.
+- It should be simple to ignore test cases and mark know failures
+  (similar to the notyetpassing mechanism in the Tvix test suite).
+
+### Test Case Types
+
+This is a summary of relevant kinds of test cases that can be found in the wild,
+usually testing some kind of concrete implementation, but also doubling up as a
+potential test case for _any_ Nix implementation. For the most part, this is the
+`lang` test suite of C++ Nix which is also used by Tvix and hnix.
+
+- **parse** test cases: Parsing the given expression should either *succeed* or
+  *fail*.
+
+  - C++ Nix doesn't have any expected output for the success cases while
+    `rnix-parser` checks them against its own textual AST representation.
+  - For the failure cases, `rnix-parser` and C++ Nix (as of recently) have
+    expected error messages/representations.
+
+  Both error and failure cases probably are hard to implement against expected
+  output/error messages for a generic test suite. Even if standardized error
+  codes are implemented (see below), it is doubtful whether it'd be useful
+  to have a dedicated code for every kind of parse/lex failure.
+- (strict) **eval** test cases: Evaluating the given expression should either
+  *fail* or *succeed* and yield a given result.
+
+  - **eval-okay** (success) tests currently require three things:
+
+    1. Successful evaluation after deeply forcing and printing the evaluation
+       result (i.e. `nix-instantiate --eval --strict`)
+    2. That the output matches an expected output exactly (string equality).
+       For this the output of `nix-instantiate(1)` is used, sometimes with
+       the addition of the `--xml --no-location` or `--json` flags.
+    3. Optionally, stderr may need to be equal to an expected string exactly
+       which would test e.g. `builtins.trace` messages or deprecation warnings
+       (C++ Nix).
+
+       This extra check is currently not supported by the Tvix test suite.
+
+  - **eval-fail** tests require that the given expression fails to evaluate. C++
+    Nix has recently started to also check the error messages via the stderr
+    mechanism described above. This is not supported by Tvix at the moment.
+- _lazy_ eval test cases: This is currently only supported by the `nix_oracle`
+  test suite in Tvix which compares the evaluation result of expressions to the
+  output of `nix-instantiate(1)` without `--strict`. By relying on the fact
+  that the resulting value is not forced deeply before printing, it can be
+  observed whether certain expressions are thunked or not.
+
+  This is somewhat fragile as permissible optimizations may prevent a thunk from
+  being created. However, this should not be an issue if the cases are chosen
+  carefully. Empirically, this test suite was useful for catching some instances
+  of overzealous evaluation early in development of Tvix.
+
+- **identity** test cases require that the given expression evaluates to a
+  value whose printed representation is the same (string equal to) the original
+  expression. Such test cases only exist in the Tvix test suite.
+
+  Of course only a limited number of expression satisfy this, but it is
+  useful for testing `nix-instantiate(1)` style value printing. Consequently,
+  it is kind of on the edge of what you can call a language test.
+
+### Extra Dependencies of Some Test Cases
+
+- **Filesystem**: Some test cases `import` other files or use `builtins.readFile`,
+  `builtins.readDir` and friends.
+- **Working and Home Directory**: Tests involving relative and home relative paths
+  need knowledge of the current and home directory to correctly interpret the output.
+  C++ Nix does a [search and replace on the test output for this purpose][cpp-nix-pwd-sed]
+- **Nix Store**: Some tests add files to the store, either via path interpolation,
+  `builtins.toFile` or `builtins.derivation`.
+
+  Additionally, it should be considered that Import-from-Derivation may be
+  interesting to test in the future. Currently, the Tvix and C++ Nix test
+  suites all pass with Import-from-Derivation disabled, i.e. a dummy store
+  implementation is enough.
+
+  Note that the absence of a store dependency ideally also influences the test
+  execution: In Tvix, for example, store independent tests can be executed
+  with a store backend that immediately errors out, verifying that the test
+  is, in fact, store independent.
+- **Environment**: The C++ Nix test suite sets a single environment variable,
+  `TEST_VAR=foo`. Additionally, `NIX_PATH` and `HOME` are sometimes set (the
+  latter is probably not a great idea, since it is not terribly reliable).
+- **Nix Path**: A predetermined Nix Path (via `NIX_PATH` and/or command line
+  arguments) needs to be set for some test cases.
+- **Nix flags**: Some tests need to have extra flags passed to `nix-instantiate(1)`
+  in order to work. This is done using a `.flags` file
+
+### Expected Output Considerations
+
+#### Success
+
+The expected output of `eval-okay` test cases (which are the majority of test
+cases) uses the standard strict output of `nix-instantiate(1)` in most cases
+which is nice to read and easy to work with. However, some more obscure aspects
+of this output inevitably leak into the test cases, namely the cycle detection
+and printing and (in the case of Tvix) the printing of thunks. Unfortunately,
+the output has been changed after Nix 2.3, bringing it closer to the output of
+`nix eval`, but in an inconsistent manner (e.g. `<CYCLE>` was changed to
+`«repeated»`, but `<LAMBDA>` remained). As a consequence, it is not always
+possible to write C++ Nix version independent test cases.
+
+It is unclear whether a satisfying solution (for a common test suite) can
+be achieved here as it has become a somewhat contentious [issue whether
+or not nix-instantiate should have a stable output](cpp-nix-attr-elision-printing-pr).
+
+A solution may be to use the XML output, specifically the `--xml --no-location`
+flags to `nix-instantiate(1)` for some of these instances. As it (hopefully)
+corresponds to `builtins.toXML`, there should be a greater incentive to keep it
+stable. It does support (only via `nix-instantiate(1)`, though) printing
+unevaluated thunks, but has no kind of cycle detection (which is fair enough for
+its intended purpose).
+
+#### Failure
+
+C++ Nix has recently (some time after Nix 2.3, probably much later actually)
+started checking error messages via expected stderr output. This naturally
+won't work for a implementation independent language test suite:
+
+- It is fine to have differing phrasing for error messages or localize them.
+- Printed error positions and stack traces may be slightly different depending
+  on implementation internals.
+- Formatting will almost certainly differ.
+
+Consequently, just checking for failure when running the test suite should be
+an option. Long term, it may be interesting to have standardized error codes
+and portable error code reporting.
+
+[cpp-nix-pwd-sed]: https://github.com/NixOS/nix/blob/2cb9c7c68102193e7d34fabe6102474fc7f98010/tests/functional/lang.sh#L109
+[cpp-nix-attr-elision-printing-pr]: https://github.com/NixOS/nix/pull/9606
diff --git a/tvix/nix_cli/Cargo.lock b/tvix/nix_cli/Cargo.lock
deleted file mode 100644
index 668ab9d5bd..0000000000
--- a/tvix/nix_cli/Cargo.lock
+++ /dev/null
@@ -1,248 +0,0 @@
-# This file is automatically @generated by Cargo.
-# It is not intended for manual editing.
-version = 3
-
-[[package]]
-name = "atty"
-version = "0.2.14"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
-dependencies = [
- "hermit-abi",
- "libc",
- "winapi",
-]
-
-[[package]]
-name = "autocfg"
-version = "1.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
-
-[[package]]
-name = "bitflags"
-version = "1.3.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
-
-[[package]]
-name = "cfg-if"
-version = "1.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
-
-[[package]]
-name = "clap"
-version = "3.0.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f6f34b09b9ee8c7c7b400fe2f8df39cafc9538b03d6ba7f4ae13e4cb90bfbb7d"
-dependencies = [
- "atty",
- "bitflags",
- "indexmap",
- "os_str_bytes",
- "strsim",
- "termcolor",
- "textwrap",
-]
-
-[[package]]
-name = "getrandom"
-version = "0.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
-dependencies = [
- "cfg-if",
- "libc",
- "wasi",
-]
-
-[[package]]
-name = "hashbrown"
-version = "0.11.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
-
-[[package]]
-name = "hermit-abi"
-version = "0.1.19"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
-dependencies = [
- "libc",
-]
-
-[[package]]
-name = "indexmap"
-version = "1.7.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5"
-dependencies = [
- "autocfg",
- "hashbrown",
-]
-
-[[package]]
-name = "libc"
-version = "0.2.112"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125"
-
-[[package]]
-name = "memchr"
-version = "2.4.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
-
-[[package]]
-name = "nix-cli"
-version = "0.1.0"
-dependencies = [
- "clap",
- "tempfile",
-]
-
-[[package]]
-name = "os_str_bytes"
-version = "6.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64"
-dependencies = [
- "memchr",
-]
-
-[[package]]
-name = "ppv-lite86"
-version = "0.2.16"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
-
-[[package]]
-name = "rand"
-version = "0.8.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8"
-dependencies = [
- "libc",
- "rand_chacha",
- "rand_core",
- "rand_hc",
-]
-
-[[package]]
-name = "rand_chacha"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
-dependencies = [
- "ppv-lite86",
- "rand_core",
-]
-
-[[package]]
-name = "rand_core"
-version = "0.6.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
-dependencies = [
- "getrandom",
-]
-
-[[package]]
-name = "rand_hc"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7"
-dependencies = [
- "rand_core",
-]
-
-[[package]]
-name = "redox_syscall"
-version = "0.2.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff"
-dependencies = [
- "bitflags",
-]
-
-[[package]]
-name = "remove_dir_all"
-version = "0.5.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
-dependencies = [
- "winapi",
-]
-
-[[package]]
-name = "strsim"
-version = "0.10.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
-
-[[package]]
-name = "tempfile"
-version = "3.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22"
-dependencies = [
- "cfg-if",
- "libc",
- "rand",
- "redox_syscall",
- "remove_dir_all",
- "winapi",
-]
-
-[[package]]
-name = "termcolor"
-version = "1.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
-dependencies = [
- "winapi-util",
-]
-
-[[package]]
-name = "textwrap"
-version = "0.14.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80"
-
-[[package]]
-name = "wasi"
-version = "0.10.2+wasi-snapshot-preview1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
-
-[[package]]
-name = "winapi"
-version = "0.3.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
-dependencies = [
- "winapi-i686-pc-windows-gnu",
- "winapi-x86_64-pc-windows-gnu",
-]
-
-[[package]]
-name = "winapi-i686-pc-windows-gnu"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
-
-[[package]]
-name = "winapi-util"
-version = "0.1.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
-dependencies = [
- "winapi",
-]
-
-[[package]]
-name = "winapi-x86_64-pc-windows-gnu"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
diff --git a/tvix/nix_cli/Cargo.toml b/tvix/nix_cli/Cargo.toml
deleted file mode 100644
index b1d1d339b4..0000000000
--- a/tvix/nix_cli/Cargo.toml
+++ /dev/null
@@ -1,14 +0,0 @@
-[package]
-name = "nix-cli"
-version = "0.1.0"
-edition = "2021"
-
-# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
-
-[dependencies.clap]
-version = "3.0.5"
-[dependencies.tempfile]
-version = "3.2.0"
-
-[[bin]]
-name = "nix-store"
diff --git a/tvix/nix_cli/default.nix b/tvix/nix_cli/default.nix
deleted file mode 100644
index fb4b367ecb..0000000000
--- a/tvix/nix_cli/default.nix
+++ /dev/null
@@ -1,2 +0,0 @@
-{ ... }:
-{ }
diff --git a/tvix/nix_cli/shell.nix b/tvix/nix_cli/shell.nix
deleted file mode 100644
index f57019cd94..0000000000
--- a/tvix/nix_cli/shell.nix
+++ /dev/null
@@ -1,11 +0,0 @@
-let
-  depot = (import ./.. { });
-  pkgs = depot.third_party.nixpkgs;
-
-in
-pkgs.mkShell {
-  buildInputs = [
-    pkgs.rustup
-    pkgs.rust-analyzer
-  ];
-}
diff --git a/tvix/nix_cli/src/bin/nix-store.rs b/tvix/nix_cli/src/bin/nix-store.rs
deleted file mode 100644
index e1568fff73..0000000000
--- a/tvix/nix_cli/src/bin/nix-store.rs
+++ /dev/null
@@ -1,104 +0,0 @@
-fn main() {
-    main_args(std::env::args().collect()).unwrap_or_else(|e| e.exit());
-}
-
-pub fn main_args(args: Vec<String>) -> clap::Result<NixResult> {
-    let matches = clap::App::new("nix-store")
-        .subcommand(clap::App::new("--add").arg(clap::Arg::new("FILE").required(true).index(1)))
-        .try_get_matches_from(args.iter())?;
-    if let Some(add) = matches.subcommand_matches("--add") {
-        let file = add.value_of("FILE").expect("--add needs a file");
-        let file_contents =
-            std::fs::read_to_string(file).expect(&format!("file {} does not exist", file));
-        Ok(NixResult::FileAddedToStore {
-            content: file_contents,
-        })
-    } else {
-        panic!("read some arguments that we do not know: {:?}", args)
-    }
-}
-
-#[derive(Debug, Eq, PartialEq)]
-pub enum NixResult {
-    FileAddedToStore { content: String },
-}
-
-#[cfg(test)]
-mod integration_tests {
-    use std::collections::VecDeque;
-    use std::io::Write;
-
-    use super::*;
-
-    #[derive(Debug)]
-    enum NixOutput {
-        Err {
-            status: i32,
-            stdout: String,
-            stderr: String,
-        },
-        Ok {
-            stdout: String,
-            stderr: String,
-        },
-    }
-
-    fn run_nix_command(cmd: &str, args: Vec<String>) -> NixOutput {
-        let out = std::process::Command::new(cmd)
-            .args(args)
-            .output()
-            .expect(&format!("could not run {}", cmd));
-        match out.status.code().expect("no status code!") {
-            0 => NixOutput::Ok {
-                stdout: String::from_utf8_lossy(&out.stdout).trim_end().to_string(),
-                stderr: String::from_utf8_lossy(&out.stderr).trim_end().to_string(),
-            },
-            status => NixOutput::Err {
-                status,
-                stdout: String::from_utf8_lossy(&out.stdout).trim_end().to_string(),
-                stderr: String::from_utf8_lossy(&out.stderr).trim_end().to_string(),
-            },
-        }
-    }
-
-    fn nix_nix_store<'a>(args: Vec<String>) -> NixResult {
-        match run_nix_command("nix-store", args) {
-            err @ NixOutput::Err { .. } => panic!("nix-store --add failed: {:#?}", err),
-            NixOutput::Ok { stdout, .. } => NixResult::FileAddedToStore {
-                content: std::fs::read_to_string(&stdout)
-                    .expect(&format!("cannot open {} as store file", stdout)),
-            },
-        }
-    }
-
-    fn tvix_nix_store<'a>(args: Vec<String>) -> NixResult {
-        eprintln!("running tvix with arguments {:?}", args);
-        let mut args = VecDeque::from(args);
-        args.push_front("tvix-store".to_string());
-        super::main_args(Vec::from(args))
-            .unwrap_or_else(|e| panic!("clap command line parsing failed:\n{}", e))
-    }
-
-    #[test]
-    fn test_nix_store_add() {
-        let file_content = "I am a copied file";
-        let mut tempfile = tempfile::NamedTempFile::new().expect("cannot create temp file");
-        tempfile
-            .write_all(file_content.as_bytes())
-            .expect("could not write to tempfile");
-        assert_eq!(
-            tvix_nix_store(vec![
-                "--add".to_string(),
-                tempfile.path().as_os_str().to_string_lossy().into_owned()
-            ]),
-            nix_nix_store(vec![
-                "--add".to_string(),
-                tempfile.path().as_os_str().to_string_lossy().into_owned()
-            ]),
-            "added file contents were not the same"
-        );
-
-        // make sure the tempfile lives till here
-        drop(tempfile)
-    }
-}
diff --git a/tvix/nix_cli/src/main.rs b/tvix/nix_cli/src/main.rs
deleted file mode 100644
index 40086e6f27..0000000000
--- a/tvix/nix_cli/src/main.rs
+++ /dev/null
@@ -1,3 +0,0 @@
-fn main() {
-    println!("Hello, tvix!");
-}
diff --git a/tvix/proto/default.nix b/tvix/proto/default.nix
deleted file mode 100644
index ac0ee66e87..0000000000
--- a/tvix/proto/default.nix
+++ /dev/null
@@ -1,9 +0,0 @@
-# Build protocol buffer definitions to ensure that protos are valid in
-# CI. Note that the output of this build target is not actually used
-# anywhere, it just functions as a CI check for now.
-{ pkgs, ... }:
-
-pkgs.runCommandNoCC "tvix-cc-proto" { } ''
-  mkdir $out
-  ${pkgs.protobuf}/bin/protoc -I ${./.} evaluator.proto --cpp_out=$out
-''
diff --git a/tvix/proto/evaluator.proto b/tvix/proto/evaluator.proto
deleted file mode 100644
index 710a28fb9d..0000000000
--- a/tvix/proto/evaluator.proto
+++ /dev/null
@@ -1,144 +0,0 @@
-// SPDX-License-Identifier: MIT
-// Copyright © 2021 The Tvix Authors
-syntax = "proto3";
-
-package tvix.proto.evaluator.v1;
-
-service EvaluatorService {
-  rpc Evaluate(stream EvaluateRequest) returns (stream EvaluateResponse) {}
-}
-
-//
-// Message types for EvaluateRequest
-//
-
-message EvaluateFile {
-  // Absolute path at which the evaluator can find the file to be
-  // evaluated.
-  string file_path = 1;
-
-  // Optional attribute that should be evaluated within the file,
-  // assuming that the value it evaluates to is an attribute set.
-  optional string attribute = 2;
-
-  // Additional arguments to pass into the evaluation, with which the
-  // file's top-level function will be auto-called.
-  map<string, NixValue> arguments = 3;
-}
-
-message EvaluateExpression {
-  // Literal Nix expression to evaluate.
-  string expression = 1;
-
-  // Working directory in which the expression should be evaluated.
-  string working_directory = 2;
-}
-
-message BuildResultChunk {
-  string drv_hash = 1;
-  string output = 2;
-  bytes data = 3;
-
-  // This field may be set on the first build result chunk returned
-  // to the evaluator, indicating the total size of the output that
-  // is going to be streamed in bytes.
-  //
-  // If set, the evaluator can use this to appropriately allocate a
-  // buffer for the output.
-  optional int64 output_size = 4;
-}
-
-// Indicates that a single build has completed successfully. In case
-// that the build outputs were required by the evaluator this also
-// indicates that the output has been returned completely.
-message BuildSuccess {
-  string drv_hash = 1;
-  string output = 2;
-}
-
-// Describes an error that occured during a single build.
-//
-// TODO: We might want a more sophisticated error type.
-message BuildError {
-  string drv_hash = 1;
-  string output = 2;
-  string error = 3;
-}
-
-message BuildResult {
-  oneof build_result {
-    BuildSuccess build_success = 1;
-    BuildError build_error = 2;
-  }
-}
-
-
-/// Messages sent to the evaluator by the build coordinator.
-message EvaluateRequest {
-  oneof message {
-    // Ask the evaluator to evaluate the specified file, and
-    // optionally attribute within that file. Must be the first
-    // message.
-    EvaluateFile evaluate_file = 1;
-
-    // Ask the evaluator to evaluate the specified Nix expression.
-    // Must be the first message.
-    EvaluateExpression evaluate_expression = 2;
-
-    // Send the chunks of a build result, in response to a
-    // BuildRequest.
-    //
-    // Note: This message might change as the store protocol is
-    // designed, as it is possible that mechanisms for transferring
-    // files might be reused between the protocols.
-    BuildResultChunk build_result_chunk = 3;
-
-    // Indicate the result of a single build. See the documentation
-    // for the message types defined above for semantic details.
-    BuildResult build_result = 4;
-  }
-}
-
-//
-// Message types for EvaluateResponse
-//
-
-// TODO: Placeholder type.
-message Derivation {
-  string drv = 1;
-}
-
-// TODO: Placeholder type.
-message NixValue {
-  string value = 1;
-}
-
-// TODO: Placeholder type.
-message NixError {
-  string value = 1;
-}
-
-message BuildRequest {
-  Derivation drv = 1;
-  string output = 2;
-}
-
-// Messages returned to the coordinator by the evaluator.
-message EvaluateResponse {
-  oneof message {
-    // A derivation that was instantiated while reducing the graph,
-    // and whose output is not required by the evaluator.
-    Derivation derivation = 1;
-
-    // A derivation that was instantiated while reducing the graph,
-    // and whose output is required by the evaluator (IFD).
-    BuildRequest build_request = 2;
-
-    // The final value yielded by the evaluation. Stream is closed
-    // after this.
-    NixValue done = 3;
-
-    // Evaluation error. Stream is closed after this.
-    NixError error = 4;
-  }
-}
diff --git a/tvix/scripts/bench-windtunnel.sh b/tvix/scripts/bench-windtunnel.sh
new file mode 100755
index 0000000000..aa359a8a82
--- /dev/null
+++ b/tvix/scripts/bench-windtunnel.sh
@@ -0,0 +1,27 @@
+#!/usr/bin/env nix-shell
+#!nix-shell -i bash ../.. -A tvix.shell
+
+# Benchmark script that runs inside the Windtunnel build agent
+
+set -euo pipefail
+
+echo "Running benchmarks for tvix..."
+pushd "$(dirname "$(dirname "$0")")"
+cargo bench
+windtunnel-cli report -f criterion-rust .
+popd
+
+echo "Running tvix macrobenchmarks..."
+pushd "$(dirname "$(dirname "$0")")"
+
+depot_nixpkgs_path="$(nix eval --raw '("${((import ../third_party/sources {}).nixpkgs)}")')"
+pinned_nixpkgs_path="$(nix eval --raw '(builtins.fetchTarball {url = "https://github.com/NixOS/nixpkgs/archive/91050ea1e57e50388fa87a3302ba12d188ef723a.tar.gz"; sha256 = "1hf6cgaci1n186kkkjq106ryf8mmlq9vnwgfwh625wa8hfgdn4dm";})')"
+
+cargo build --release --bin tvix
+hyperfine --export-json ./results.json \
+    -n 'tvix-eval-depot-nixpkgs-hello' "target/release/tvix -E '(import ${depot_nixpkgs_path} {}).hello.outPath'" \
+    -n 'tvix-eval-depot-nixpkgs-cross-hello' "target/release/tvix -E '(import ${depot_nixpkgs_path} {}).pkgsCross.aarch64-multiplatform.hello.outPath'" \
+    -n 'tvix-eval-pinned-nixpkgs-hello' "target/release/tvix -E '(import ${pinned_nixpkgs_path} {}).hello.outPath'" \
+    -n 'tvix-eval-pinned-nixpkgs-cross-hello' "target/release/tvix -E '(import ${pinned_nixpkgs_path} {}).pkgsCross.aarch64-multiplatform.hello.outPath'"
+windtunnel-cli report -f hyperfine-json ./results.json
+popd
diff --git a/tvix/serde/.skip-subtree b/tvix/serde/.skip-subtree
new file mode 100644
index 0000000000..21b2d0d358
--- /dev/null
+++ b/tvix/serde/.skip-subtree
@@ -0,0 +1 @@
+The foods.nix can not be read by readTree.
diff --git a/tvix/serde/Cargo.toml b/tvix/serde/Cargo.toml
new file mode 100644
index 0000000000..5652126ada
--- /dev/null
+++ b/tvix/serde/Cargo.toml
@@ -0,0 +1,9 @@
+[package]
+name = "tvix-serde"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+tvix-eval = { path = "../eval" }
+serde = { version = "1.0", features = ["derive"] }
+bstr = { version = "1.8.0", features = ["serde"] }
diff --git a/tvix/serde/default.nix b/tvix/serde/default.nix
new file mode 100644
index 0000000000..5880bd24f6
--- /dev/null
+++ b/tvix/serde/default.nix
@@ -0,0 +1,5 @@
+{ depot, ... }:
+
+depot.tvix.crates.workspaceMembers.tvix-serde.build.override {
+  runTests = true;
+}
diff --git a/tvix/serde/examples/cfg-demo.rs b/tvix/serde/examples/cfg-demo.rs
new file mode 100644
index 0000000000..5774a81f77
--- /dev/null
+++ b/tvix/serde/examples/cfg-demo.rs
@@ -0,0 +1,35 @@
+//! This program demonstrates how to use tvix_serde to deserialise
+//! program configuration (or other data) from Nix code.
+//!
+//! This makes it possible to use Nix as an embedded config language.
+//! For greater control over evaluation, and for features like adding
+//! additional builtins, depending directly on tvix_eval would be
+//! required.
+use serde::Deserialize;
+use std::collections::HashMap;
+
+#[derive(Debug, Deserialize)]
+enum Flavour {
+    Tasty,
+    Okay,
+    Eww,
+}
+
+#[allow(dead_code)]
+#[derive(Debug, Deserialize)]
+struct Data {
+    name: String,
+    foods: HashMap<String, Flavour>,
+}
+
+fn main() {
+    // Get the content from wherever, read it from a file, receive it
+    // over the network - whatever floats your boat! We'll include it
+    // as a string.
+    let code = include_str!("foods.nix");
+
+    // Now you can use tvix_serde to deserialise the struct:
+    let foods: Data = tvix_serde::from_str(code).expect("deserialisation should succeed");
+
+    println!("These are the foods:\n{:#?}", foods);
+}
diff --git a/tvix/serde/examples/foods.nix b/tvix/serde/examples/foods.nix
new file mode 100644
index 0000000000..c8733cd3ef
--- /dev/null
+++ b/tvix/serde/examples/foods.nix
@@ -0,0 +1,22 @@
+# This is content for the `Data` struct, written in intentionally
+# convoluted Nix code.
+let
+  mkFlavour = flavour: name: {
+    inherit name;
+    value = flavour;
+  };
+
+  tasty = mkFlavour "Tasty";
+  okay = mkFlavour "Okay";
+  eww = mkFlavour "Eww";
+in
+{
+  name = "exhaustive list of foods";
+
+  foods = builtins.listToAttrs [
+    (tasty "beef")
+    (okay "tomatoes")
+    (eww "olives")
+    (tasty "coffee")
+  ];
+}
diff --git a/tvix/serde/examples/nixpkgs.rs b/tvix/serde/examples/nixpkgs.rs
new file mode 100644
index 0000000000..ad8c4160b5
--- /dev/null
+++ b/tvix/serde/examples/nixpkgs.rs
@@ -0,0 +1,34 @@
+//! This program demonstrates deserialising some configuration
+//! structure from Nix code that makes use of nixpkgs.lib
+//!
+//! This example does not add the full set of Nix features (i.e.
+//! builds & derivations).
+
+use serde::Deserialize;
+
+#[derive(Debug, Deserialize)]
+struct Config {
+    host: String,
+    port: usize,
+}
+
+fn main() {
+    let code = r#"
+    let
+       lib = import <nixpkgs/lib>;
+       host = lib.strings.concatStringsSep "." ["foo" "example" "com"];
+    in {
+      inherit host;
+      port = 4242;
+    }
+    "#;
+
+    let result = tvix_serde::from_str_with_config::<Config, _>(code, |eval| {
+        eval.enable_impure(None);
+    });
+
+    match result {
+        Ok(cfg) => println!("Config says: {}:{}", cfg.host, cfg.port),
+        Err(e) => eprintln!("{:?} / {}", e, e),
+    }
+}
diff --git a/tvix/serde/src/de.rs b/tvix/serde/src/de.rs
new file mode 100644
index 0000000000..428b9e2e81
--- /dev/null
+++ b/tvix/serde/src/de.rs
@@ -0,0 +1,475 @@
+//! Deserialisation from Nix to Rust values.
+
+use bstr::ByteSlice;
+use serde::de::value::{MapDeserializer, SeqDeserializer};
+use serde::de::{self, EnumAccess, VariantAccess};
+pub use tvix_eval::Evaluation;
+use tvix_eval::{EvalIO, Value};
+
+use crate::error::Error;
+
+struct NixDeserializer {
+    value: tvix_eval::Value,
+}
+
+impl NixDeserializer {
+    fn new(value: Value) -> Self {
+        if let Value::Thunk(thunk) = value {
+            Self::new(thunk.value().clone())
+        } else {
+            Self { value }
+        }
+    }
+}
+
+impl de::IntoDeserializer<'_, Error> for NixDeserializer {
+    type Deserializer = Self;
+
+    fn into_deserializer(self) -> Self::Deserializer {
+        self
+    }
+}
+
+/// Evaluate the Nix code in `src` and attempt to deserialise the
+/// value it returns to `T`.
+pub fn from_str<'code, T>(src: &'code str) -> Result<T, Error>
+where
+    T: serde::Deserialize<'code>,
+{
+    from_str_with_config(src, |_| /* no extra config */ ())
+}
+
+/// Evaluate the Nix code in `src`, with extra configuration for the
+/// `tvix_eval::Evaluation` provided by the given closure.
+pub fn from_str_with_config<'code, T, F>(src: &'code str, config: F) -> Result<T, Error>
+where
+    T: serde::Deserialize<'code>,
+    F: FnOnce(&mut Evaluation<Box<dyn EvalIO>>),
+{
+    // First step is to evaluate the Nix code ...
+    let mut eval = Evaluation::new_pure();
+    config(&mut eval);
+
+    eval.strict = true;
+    let result = eval.evaluate(src, None);
+
+    if !result.errors.is_empty() {
+        return Err(Error::NixErrors {
+            errors: result.errors,
+        });
+    }
+
+    let de = NixDeserializer::new(result.value.expect("value should be present on success"));
+
+    T::deserialize(de)
+}
+
+fn unexpected(expected: &'static str, got: &Value) -> Error {
+    Error::UnexpectedType {
+        expected,
+        got: got.type_of(),
+    }
+}
+
+fn visit_integer<I: TryFrom<i64>>(v: &Value) -> Result<I, Error> {
+    match v {
+        Value::Integer(i) => I::try_from(*i).map_err(|_| Error::IntegerConversion {
+            got: *i,
+            need: std::any::type_name::<I>(),
+        }),
+
+        _ => Err(unexpected("integer", v)),
+    }
+}
+
+impl<'de> de::Deserializer<'de> for NixDeserializer {
+    type Error = Error;
+
+    fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
+    where
+        V: de::Visitor<'de>,
+    {
+        match self.value {
+            Value::Null => visitor.visit_unit(),
+            Value::Bool(b) => visitor.visit_bool(b),
+            Value::Integer(i) => visitor.visit_i64(i),
+            Value::Float(f) => visitor.visit_f64(f),
+            Value::String(s) => visitor.visit_string(s.to_string()),
+            Value::Path(p) => visitor.visit_string(p.to_string_lossy().into()), // TODO: hmm
+            Value::Attrs(_) => self.deserialize_map(visitor),
+            Value::List(_) => self.deserialize_seq(visitor),
+
+            // tvix-eval types that can not be deserialized through serde.
+            Value::Closure(_)
+            | Value::Builtin(_)
+            | Value::Thunk(_)
+            | Value::AttrNotFound
+            | Value::Blueprint(_)
+            | Value::DeferredUpvalue(_)
+            | Value::UnresolvedPath(_)
+            | Value::Json(..)
+            | Value::Catchable(_)
+            | Value::FinaliseRequest(_) => Err(Error::Unserializable {
+                value_type: self.value.type_of(),
+            }),
+        }
+    }
+
+    fn deserialize_bool<V>(self, visitor: V) -> Result<V::Value, Self::Error>
+    where
+        V: de::Visitor<'de>,
+    {
+        match self.value {
+            Value::Bool(b) => visitor.visit_bool(b),
+            _ => Err(unexpected("bool", &self.value)),
+        }
+    }
+
+    fn deserialize_i8<V>(self, visitor: V) -> Result<V::Value, Self::Error>
+    where
+        V: de::Visitor<'de>,
+    {
+        visitor.visit_i8(visit_integer(&self.value)?)
+    }
+
+    fn deserialize_i16<V>(self, visitor: V) -> Result<V::Value, Self::Error>
+    where
+        V: de::Visitor<'de>,
+    {
+        visitor.visit_i16(visit_integer(&self.value)?)
+    }
+
+    fn deserialize_i32<V>(self, visitor: V) -> Result<V::Value, Self::Error>
+    where
+        V: de::Visitor<'de>,
+    {
+        visitor.visit_i32(visit_integer(&self.value)?)
+    }
+
+    fn deserialize_i64<V>(self, visitor: V) -> Result<V::Value, Self::Error>
+    where
+        V: de::Visitor<'de>,
+    {
+        visitor.visit_i64(visit_integer(&self.value)?)
+    }
+
+    fn deserialize_u8<V>(self, visitor: V) -> Result<V::Value, Self::Error>
+    where
+        V: de::Visitor<'de>,
+    {
+        visitor.visit_u8(visit_integer(&self.value)?)
+    }
+
+    fn deserialize_u16<V>(self, visitor: V) -> Result<V::Value, Self::Error>
+    where
+        V: de::Visitor<'de>,
+    {
+        visitor.visit_u16(visit_integer(&self.value)?)
+    }
+
+    fn deserialize_u32<V>(self, visitor: V) -> Result<V::Value, Self::Error>
+    where
+        V: de::Visitor<'de>,
+    {
+        visitor.visit_u32(visit_integer(&self.value)?)
+    }
+
+    fn deserialize_u64<V>(self, visitor: V) -> Result<V::Value, Self::Error>
+    where
+        V: de::Visitor<'de>,
+    {
+        visitor.visit_u64(visit_integer(&self.value)?)
+    }
+
+    fn deserialize_f32<V>(self, visitor: V) -> Result<V::Value, Self::Error>
+    where
+        V: de::Visitor<'de>,
+    {
+        if let Value::Float(f) = self.value {
+            return visitor.visit_f32(f as f32);
+        }
+
+        Err(unexpected("float", &self.value))
+    }
+
+    fn deserialize_f64<V>(self, visitor: V) -> Result<V::Value, Self::Error>
+    where
+        V: de::Visitor<'de>,
+    {
+        if let Value::Float(f) = self.value {
+            return visitor.visit_f64(f);
+        }
+
+        Err(unexpected("float", &self.value))
+    }
+
+    fn deserialize_char<V>(self, visitor: V) -> Result<V::Value, Self::Error>
+    where
+        V: de::Visitor<'de>,
+    {
+        if let Value::String(s) = &self.value {
+            let chars = s.chars().collect::<Vec<_>>();
+            if chars.len() == 1 {
+                return visitor.visit_char(chars[0]);
+            }
+        }
+
+        Err(unexpected("char", &self.value))
+    }
+
+    fn deserialize_str<V>(self, visitor: V) -> Result<V::Value, Self::Error>
+    where
+        V: de::Visitor<'de>,
+    {
+        if let Value::String(s) = &self.value {
+            if let Ok(s) = s.to_str() {
+                return visitor.visit_str(s);
+            }
+        }
+
+        Err(unexpected("string", &self.value))
+    }
+
+    fn deserialize_string<V>(self, visitor: V) -> Result<V::Value, Self::Error>
+    where
+        V: de::Visitor<'de>,
+    {
+        if let Value::String(s) = &self.value {
+            if let Ok(s) = s.to_str() {
+                return visitor.visit_str(s);
+            }
+        }
+
+        Err(unexpected("string", &self.value))
+    }
+
+    fn deserialize_bytes<V>(self, _visitor: V) -> Result<V::Value, Self::Error>
+    where
+        V: de::Visitor<'de>,
+    {
+        unimplemented!()
+    }
+
+    fn deserialize_byte_buf<V>(self, _visitor: V) -> Result<V::Value, Self::Error>
+    where
+        V: de::Visitor<'de>,
+    {
+        unimplemented!()
+    }
+
+    // Note that this can not distinguish between a serialisation of
+    // `Some(())` and `None`.
+    fn deserialize_option<V>(self, visitor: V) -> Result<V::Value, Self::Error>
+    where
+        V: de::Visitor<'de>,
+    {
+        if let Value::Null = self.value {
+            visitor.visit_none()
+        } else {
+            visitor.visit_some(self)
+        }
+    }
+
+    fn deserialize_unit<V>(self, visitor: V) -> Result<V::Value, Self::Error>
+    where
+        V: de::Visitor<'de>,
+    {
+        if let Value::Null = self.value {
+            return visitor.visit_unit();
+        }
+
+        Err(unexpected("null", &self.value))
+    }
+
+    fn deserialize_unit_struct<V>(
+        self,
+        _name: &'static str,
+        visitor: V,
+    ) -> Result<V::Value, Self::Error>
+    where
+        V: de::Visitor<'de>,
+    {
+        self.deserialize_unit(visitor)
+    }
+
+    fn deserialize_newtype_struct<V>(
+        self,
+        _name: &'static str,
+        visitor: V,
+    ) -> Result<V::Value, Self::Error>
+    where
+        V: de::Visitor<'de>,
+    {
+        visitor.visit_newtype_struct(self)
+    }
+
+    fn deserialize_seq<V>(self, visitor: V) -> Result<V::Value, Self::Error>
+    where
+        V: de::Visitor<'de>,
+    {
+        if let Value::List(list) = self.value {
+            let mut seq = SeqDeserializer::new(list.into_iter().map(NixDeserializer::new));
+            let result = visitor.visit_seq(&mut seq)?;
+            seq.end()?;
+            return Ok(result);
+        }
+
+        Err(unexpected("list", &self.value))
+    }
+
+    fn deserialize_tuple<V>(self, _len: usize, visitor: V) -> Result<V::Value, Self::Error>
+    where
+        V: de::Visitor<'de>,
+    {
+        // just represent tuples as lists ...
+        self.deserialize_seq(visitor)
+    }
+
+    fn deserialize_tuple_struct<V>(
+        self,
+        _name: &'static str,
+        _len: usize,
+        visitor: V,
+    ) -> Result<V::Value, Self::Error>
+    where
+        V: de::Visitor<'de>,
+    {
+        // same as above
+        self.deserialize_seq(visitor)
+    }
+
+    fn deserialize_map<V>(self, visitor: V) -> Result<V::Value, Self::Error>
+    where
+        V: de::Visitor<'de>,
+    {
+        if let Value::Attrs(attrs) = self.value {
+            let mut map = MapDeserializer::new(attrs.into_iter().map(|(k, v)| {
+                (
+                    NixDeserializer::new(Value::from(k)),
+                    NixDeserializer::new(v),
+                )
+            }));
+            let result = visitor.visit_map(&mut map)?;
+            map.end()?;
+            return Ok(result);
+        }
+
+        Err(unexpected("map", &self.value))
+    }
+
+    fn deserialize_struct<V>(
+        self,
+        _name: &'static str,
+        _fields: &'static [&'static str],
+        visitor: V,
+    ) -> Result<V::Value, Self::Error>
+    where
+        V: de::Visitor<'de>,
+    {
+        self.deserialize_map(visitor)
+    }
+
+    // This method is responsible for deserializing the externally
+    // tagged enum variant serialisation.
+    fn deserialize_enum<V>(
+        self,
+        name: &'static str,
+        _variants: &'static [&'static str],
+        visitor: V,
+    ) -> Result<V::Value, Self::Error>
+    where
+        V: de::Visitor<'de>,
+    {
+        match self.value {
+            // a string represents a unit variant
+            Value::String(ref s) => {
+                if let Ok(s) = s.to_str() {
+                    visitor.visit_enum(de::value::StrDeserializer::new(s))
+                } else {
+                    Err(unexpected(name, &self.value))
+                }
+            }
+
+            // an attribute set however represents an externally
+            // tagged enum with content
+            Value::Attrs(attrs) => visitor.visit_enum(Enum(*attrs)),
+
+            _ => Err(unexpected(name, &self.value)),
+        }
+    }
+
+    fn deserialize_identifier<V>(self, visitor: V) -> Result<V::Value, Self::Error>
+    where
+        V: de::Visitor<'de>,
+    {
+        self.deserialize_str(visitor)
+    }
+
+    fn deserialize_ignored_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
+    where
+        V: de::Visitor<'de>,
+    {
+        visitor.visit_unit()
+    }
+}
+
+struct Enum(tvix_eval::NixAttrs);
+
+impl<'de> EnumAccess<'de> for Enum {
+    type Error = Error;
+    type Variant = NixDeserializer;
+
+    // TODO: pass the known variants down here and check against them
+    fn variant_seed<V>(self, seed: V) -> Result<(V::Value, Self::Variant), Self::Error>
+    where
+        V: de::DeserializeSeed<'de>,
+    {
+        if self.0.len() != 1 {
+            return Err(Error::AmbiguousEnum);
+        }
+
+        let (key, value) = self.0.into_iter().next().expect("length asserted above");
+        if let Ok(k) = key.to_str() {
+            let val = seed.deserialize(de::value::StrDeserializer::<Error>::new(k))?;
+            Ok((val, NixDeserializer::new(value)))
+        } else {
+            Err(unexpected("string", &key.clone().into()))
+        }
+    }
+}
+
+impl<'de> VariantAccess<'de> for NixDeserializer {
+    type Error = Error;
+
+    fn unit_variant(self) -> Result<(), Self::Error> {
+        // If this case is hit, a user specified the name of a unit
+        // enum variant but gave it content. Unit enum deserialisation
+        // is handled in `deserialize_enum` above.
+        Err(Error::UnitEnumContent)
+    }
+
+    fn newtype_variant_seed<T>(self, seed: T) -> Result<T::Value, Self::Error>
+    where
+        T: de::DeserializeSeed<'de>,
+    {
+        seed.deserialize(self)
+    }
+
+    fn tuple_variant<V>(self, _len: usize, visitor: V) -> Result<V::Value, Self::Error>
+    where
+        V: de::Visitor<'de>,
+    {
+        de::Deserializer::deserialize_seq(self, visitor)
+    }
+
+    fn struct_variant<V>(
+        self,
+        _fields: &'static [&'static str],
+        visitor: V,
+    ) -> Result<V::Value, Self::Error>
+    where
+        V: de::Visitor<'de>,
+    {
+        de::Deserializer::deserialize_map(self, visitor)
+    }
+}
diff --git a/tvix/serde/src/de_tests.rs b/tvix/serde/src/de_tests.rs
new file mode 100644
index 0000000000..1c3acd1c2f
--- /dev/null
+++ b/tvix/serde/src/de_tests.rs
@@ -0,0 +1,245 @@
+use serde::Deserialize;
+use std::collections::HashMap;
+use tvix_eval::builtin_macros::builtins;
+
+use crate::de::{from_str, from_str_with_config};
+
+#[test]
+fn deserialize_none() {
+    let result: Option<usize> = from_str("null").expect("should deserialize");
+    assert_eq!(None, result);
+}
+
+#[test]
+fn deserialize_some() {
+    let result: Option<usize> = from_str("40 + 2").expect("should deserialize");
+    assert_eq!(Some(42), result);
+}
+
+#[test]
+fn deserialize_string() {
+    let result: String = from_str(
+        r#"
+      let greeter = name: "Hello ${name}!";
+      in greeter "Slartibartfast"
+    "#,
+    )
+    .expect("should deserialize");
+
+    assert_eq!(result, "Hello Slartibartfast!");
+}
+
+#[test]
+fn deserialize_empty_list() {
+    let result: Vec<usize> = from_str("[ ]").expect("should deserialize");
+    assert!(result.is_empty())
+}
+
+#[test]
+fn deserialize_integer_list() {
+    let result: Vec<usize> =
+        from_str("builtins.map (n: n + 2) [ 21 40 67 ]").expect("should deserialize");
+    assert_eq!(result, vec![23, 42, 69]);
+}
+
+#[test]
+fn deserialize_empty_map() {
+    let result: HashMap<String, usize> = from_str("{ }").expect("should deserialize");
+    assert!(result.is_empty());
+}
+
+#[test]
+fn deserialize_integer_map() {
+    let result: HashMap<String, usize> = from_str("{ age = 40 + 2; }").expect("should deserialize");
+    assert_eq!(result.len(), 1);
+    assert_eq!(*result.get("age").unwrap(), 42);
+}
+
+#[test]
+fn deserialize_struct() {
+    #[derive(Debug, Deserialize, PartialEq)]
+    struct Person {
+        name: String,
+        age: usize,
+    }
+
+    let result: Person = from_str(
+        r#"
+    {
+      name = "Slartibartfast";
+      age = 42;
+    }
+    "#,
+    )
+    .expect("should deserialize");
+
+    assert_eq!(
+        result,
+        Person {
+            name: "Slartibartfast".into(),
+            age: 42,
+        }
+    );
+}
+
+#[test]
+fn deserialize_newtype() {
+    #[derive(Debug, Deserialize, PartialEq)]
+    struct Number(usize);
+
+    let result: Number = from_str("42").expect("should deserialize");
+    assert_eq!(result, Number(42));
+}
+
+#[test]
+fn deserialize_tuple() {
+    let result: (String, usize) = from_str(r#" [ "foo" 42 ] "#).expect("should deserialize");
+    assert_eq!(result, ("foo".into(), 42));
+}
+
+#[test]
+fn deserialize_unit_enum() {
+    #[derive(Debug, Deserialize, PartialEq)]
+    enum Foo {
+        Bar,
+        Baz,
+    }
+
+    let result: Foo = from_str("\"Baz\"").expect("should deserialize");
+    assert_eq!(result, Foo::Baz);
+}
+
+#[test]
+fn deserialize_tuple_enum() {
+    #[derive(Debug, Deserialize, PartialEq)]
+    enum Foo {
+        Bar,
+        Baz(String, usize),
+    }
+
+    let result: Foo = from_str(
+        r#"
+    {
+      Baz = [ "Slartibartfast" 42 ];
+    }
+    "#,
+    )
+    .expect("should deserialize");
+
+    assert_eq!(result, Foo::Baz("Slartibartfast".into(), 42));
+}
+
+#[test]
+fn deserialize_struct_enum() {
+    #[derive(Debug, Deserialize, PartialEq)]
+    enum Foo {
+        Bar,
+        Baz { name: String, age: usize },
+    }
+
+    let result: Foo = from_str(
+        r#"
+    {
+      Baz.name = "Slartibartfast";
+      Baz.age = 42;
+    }
+    "#,
+    )
+    .expect("should deserialize");
+
+    assert_eq!(
+        result,
+        Foo::Baz {
+            name: "Slartibartfast".into(),
+            age: 42
+        }
+    );
+}
+
+#[test]
+fn deserialize_enum_all() {
+    #[derive(Debug, Deserialize, PartialEq)]
+    #[serde(rename_all = "snake_case")]
+    enum TestEnum {
+        Unit,
+        Tuple(String, String),
+        Struct { name: String, age: usize },
+    }
+
+    let result: Vec<TestEnum> = from_str(
+        r#"
+      let
+        mkTuple = country: drink: { tuple = [ country drink ]; };
+      in
+      [
+        (mkTuple "UK" "cask ale")
+
+        "unit"
+
+        {
+          struct.name = "Slartibartfast";
+          struct.age = 42;
+        }
+
+        (mkTuple "Russia" "квас")
+      ]
+    "#,
+    )
+    .expect("should deserialize");
+
+    let expected = vec![
+        TestEnum::Tuple("UK".into(), "cask ale".into()),
+        TestEnum::Unit,
+        TestEnum::Struct {
+            name: "Slartibartfast".into(),
+            age: 42,
+        },
+        TestEnum::Tuple("Russia".into(), "квас".into()),
+    ];
+
+    assert_eq!(result, expected);
+}
+
+#[test]
+fn deserialize_with_config() {
+    let result: String = from_str_with_config("builtins.testWithConfig", |eval| {
+        // Add a literal string builtin that just returns `"ok"`.
+        eval.src_builtins.push(("testWithConfig", "\"ok\""));
+    })
+    .expect("should deserialize");
+
+    assert_eq!(result, "ok");
+}
+
+#[builtins]
+mod test_builtins {
+    use bstr::ByteSlice;
+    use tvix_eval::generators::{Gen, GenCo};
+    use tvix_eval::{ErrorKind, NixString, Value};
+
+    #[builtin("prependHello")]
+    pub async fn builtin_prepend_hello(co: GenCo, x: Value) -> Result<Value, ErrorKind> {
+        match x {
+            Value::String(s) => {
+                let new_string = NixString::from(format!("hello {}", s.to_str().unwrap()));
+                Ok(Value::from(new_string))
+            }
+            _ => Err(ErrorKind::TypeError {
+                expected: "string",
+                actual: "not string",
+            }),
+        }
+    }
+}
+
+#[test]
+fn deserialize_with_extra_builtin() {
+    let code = "builtins.prependHello \"world\"";
+
+    let result: String = from_str_with_config(code, |eval| {
+        eval.builtins.append(&mut test_builtins::builtins());
+    })
+    .expect("should deserialize");
+
+    assert_eq!(result, "hello world");
+}
diff --git a/tvix/serde/src/error.rs b/tvix/serde/src/error.rs
new file mode 100644
index 0000000000..d921cc4b4b
--- /dev/null
+++ b/tvix/serde/src/error.rs
@@ -0,0 +1,99 @@
+//! When serialising Nix goes wrong ...
+
+use std::error;
+use std::fmt::Display;
+
+#[derive(Clone, Debug)]
+pub enum Error {
+    /// Attempted to deserialise an unsupported Nix value (such as a
+    /// function) that can not be represented by the
+    /// [`serde::Deserialize`] trait.
+    Unserializable { value_type: &'static str },
+
+    /// Expected to deserialize a value that is unsupported by Nix.
+    Unsupported { wanted: &'static str },
+
+    /// Expected a specific type, but got something else on the Nix side.
+    UnexpectedType {
+        expected: &'static str,
+        got: &'static str,
+    },
+
+    /// Deserialisation error returned from `serde::de`.
+    Deserialization(String),
+
+    /// Deserialized integer did not fit.
+    IntegerConversion { got: i64, need: &'static str },
+
+    /// Evaluation of the supplied Nix code failed while computing the
+    /// value for deserialisation.
+    NixErrors { errors: Vec<tvix_eval::Error> },
+
+    /// Could not determine an externally tagged enum representation.
+    AmbiguousEnum,
+
+    /// Attempted to provide content to a unit enum.
+    UnitEnumContent,
+}
+
+impl Display for Error {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            Error::Unserializable { value_type } => write!(
+                f,
+                "can not deserialise a Nix '{}' into a Rust type",
+                value_type
+            ),
+
+            Error::Unsupported { wanted } => {
+                write!(f, "can not deserialize a '{}' from a Nix value", wanted)
+            }
+
+            Error::UnexpectedType { expected, got } => {
+                write!(f, "expected type {}, but got Nix type {}", expected, got)
+            }
+
+            Error::NixErrors { errors } => {
+                writeln!(
+                    f,
+                    "{} occured during Nix evaluation: ",
+                    if errors.len() == 1 { "error" } else { "errors" }
+                )?;
+
+                for err in errors {
+                    writeln!(f, "{}", err.fancy_format_str())?;
+                }
+
+                Ok(())
+            }
+
+            Error::Deserialization(err) => write!(f, "deserialisation error occured: {}", err),
+
+            Error::IntegerConversion { got, need } => {
+                write!(f, "i64({}) does not fit in a {}", got, need)
+            }
+
+            Error::AmbiguousEnum => write!(f, "could not determine enum variant: ambiguous keys"),
+
+            Error::UnitEnumContent => write!(f, "provided content for unit enum variant"),
+        }
+    }
+}
+
+impl error::Error for Error {
+    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
+        match self {
+            Self::NixErrors { errors, .. } => errors.first().map(|e| e as &dyn error::Error),
+            _ => None,
+        }
+    }
+}
+
+impl serde::de::Error for Error {
+    fn custom<T>(err: T) -> Self
+    where
+        T: Display,
+    {
+        Self::Deserialization(err.to_string())
+    }
+}
diff --git a/tvix/serde/src/lib.rs b/tvix/serde/src/lib.rs
new file mode 100644
index 0000000000..6a44affdc0
--- /dev/null
+++ b/tvix/serde/src/lib.rs
@@ -0,0 +1,12 @@
+//! `tvix-serde` implements (de-)serialisation of Rust data structures
+//! to/from Nix. This is intended to make it easy to use Nix as as
+//! configuration language.
+
+mod de;
+mod error;
+
+pub use de::from_str;
+pub use de::from_str_with_config;
+
+#[cfg(test)]
+mod de_tests;
diff --git a/tvix/shell.nix b/tvix/shell.nix
new file mode 100644
index 0000000000..f0d8ab1657
--- /dev/null
+++ b/tvix/shell.nix
@@ -0,0 +1,62 @@
+# This file is shell.nix in the tvix josh workspace,
+# *and* used to provide the //tvix:shell attribute in a full depot checkout.
+# Hence, it may not use depot as a toplevel argument.
+
+{
+  # This falls back to the tvix josh workspace-provided nixpkgs checkout.
+  # In the case of depot, it's always set explicitly.
+  pkgs ? (import ./nixpkgs {
+    depotOverlays = false;
+    depot.third_party.sources = import ./sources { };
+    additionalOverlays = [
+      (self: super: {
+        # https://github.com/googleapis/google-cloud-go/pull/9665
+        cbtemulator = super.cbtemulator.overrideAttrs (old: {
+          patches = old.patches or [ ] ++ [
+            ./nixpkgs/cbtemulator-uds.patch
+          ];
+        });
+      })
+    ];
+  })
+, ...
+}:
+
+pkgs.mkShell {
+  name = "tvix-rust-dev-env";
+  packages = [
+    pkgs.buf-language-server
+    pkgs.cargo
+    pkgs.cargo-machete
+    pkgs.cargo-expand
+    pkgs.clippy
+    pkgs.evans
+    pkgs.fuse
+    pkgs.go
+    pkgs.grpcurl
+    pkgs.hyperfine
+    pkgs.mdbook
+    pkgs.mdbook-plantuml
+    pkgs.nix_2_3 # b/313
+    pkgs.pkg-config
+    pkgs.rust-analyzer
+    pkgs.rustc
+    pkgs.rustfmt
+    pkgs.plantuml
+    pkgs.protobuf
+  ] ++ pkgs.lib.optionals pkgs.stdenv.isDarwin [
+    # We need these two dependencies in the ambient environment to be able to
+    # `cargo build` on MacOS.
+    pkgs.libiconv
+    pkgs.buildPackages.darwin.apple_sdk.frameworks.Security
+  ];
+
+  # Set TVIX_BENCH_NIX_PATH to a somewhat pinned nixpkgs path.
+  # This is for invoking `cargo bench` imperatively on the developer machine.
+  # For tvix benchmarking across longer periods of time (by CI), we probably
+  # should also benchmark with a more static nixpkgs checkout, so nixpkgs
+  # refactorings are not observed as eval perf changes.
+  shellHook = ''
+    export TVIX_BENCH_NIX_PATH=nixpkgs=${pkgs.path}
+  '';
+}
diff --git a/tvix/store-go/LICENSE b/tvix/store-go/LICENSE
new file mode 100644
index 0000000000..2034ada6fd
--- /dev/null
+++ b/tvix/store-go/LICENSE
@@ -0,0 +1,21 @@
+Copyright © The Tvix Authors
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+“Software”), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
diff --git a/tvix/store-go/README.md b/tvix/store-go/README.md
new file mode 100644
index 0000000000..594513412d
--- /dev/null
+++ b/tvix/store-go/README.md
@@ -0,0 +1,10 @@
+# store-go
+
+This directory contains generated golang bindings, both for the tvix-store data
+models, as well as the gRPC bindings.
+
+They are generated with `mg run //tvix:store-go:regenerate`.
+These files end with `.pb.go`, and are ensured to be up to date by Ci check.
+
+Additionally, code useful when interacting with these data structures
+(ending just with `.go`) is provided.
diff --git a/tvix/store-go/default.nix b/tvix/store-go/default.nix
new file mode 100644
index 0000000000..e4c3efd7ad
--- /dev/null
+++ b/tvix/store-go/default.nix
@@ -0,0 +1,31 @@
+{ depot, pkgs, ... }:
+
+let
+  regenerate = pkgs.writeShellScript "regenerate" ''
+    (cd $(git rev-parse --show-toplevel)/tvix/store-go && rm *.pb.go && cp ${depot.tvix.store.protos.go-bindings}/*.pb.go . && chmod +w *.pb.go)
+  '';
+in
+(pkgs.buildGoModule {
+  name = "store-go";
+  src = depot.third_party.gitignoreSource ./.;
+  vendorHash = "sha256-JAxjSI4efCwbAUbvS7AQ5ZbVlf3ebGDBzDFMTK7dvl4=";
+}).overrideAttrs (_: {
+  meta.ci.extraSteps = {
+    check = {
+      label = ":water_buffalo: ensure generated protobuf files match";
+      needsOutput = true;
+      command = pkgs.writeShellScript "pb-go-check" ''
+        ${regenerate}
+        if [[ -n "$(git status --porcelain -unormal)" ]]; then
+            echo "-----------------------------"
+            echo ".pb.go files need to be updated, mg run //tvix/store-go/regenerate"
+            echo "-----------------------------"
+            git status -unormal
+            exit 1
+        fi
+      '';
+      alwaysRun = true;
+    };
+  };
+  passthru.regenerate = regenerate;
+})
diff --git a/tvix/store-go/export.go b/tvix/store-go/export.go
new file mode 100644
index 0000000000..c68e015cdb
--- /dev/null
+++ b/tvix/store-go/export.go
@@ -0,0 +1,273 @@
+package storev1
+
+import (
+	"fmt"
+	"io"
+	"path"
+
+	castorev1pb "code.tvl.fyi/tvix/castore-go"
+	"github.com/nix-community/go-nix/pkg/nar"
+)
+
+type DirectoryLookupFn func([]byte) (*castorev1pb.Directory, error)
+type BlobLookupFn func([]byte) (io.ReadCloser, error)
+
+// Export will traverse a given root node, and write the contents in NAR format
+// to the passed Writer.
+// It uses directoryLookupFn and blobLookupFn to resolve references.
+func Export(
+	w io.Writer,
+	rootNode *castorev1pb.Node,
+	directoryLookupFn DirectoryLookupFn,
+	blobLookupFn BlobLookupFn,
+) error {
+	// initialize a NAR writer
+	narWriter, err := nar.NewWriter(w)
+	if err != nil {
+		return fmt.Errorf("unable to initialize nar writer: %w", err)
+	}
+	defer narWriter.Close()
+
+	// populate rootHeader
+	rootHeader := &nar.Header{
+		Path: "/",
+	}
+
+	// populate a stack
+	// we will push paths and directories to it when entering a directory,
+	// and emit individual elements to the NAR writer, draining the Directory object.
+	// once it's empty, we can pop it off the stack.
+	var stackPaths = []string{}
+	var stackDirectories = []*castorev1pb.Directory{}
+
+	// peek at the pathInfo root and assemble the root node and write to writer
+	// in the case of a regular file, we retrieve and write the contents, close and exit
+	// in the case of a symlink, we write the symlink, close and exit
+	if fileNode := rootNode.GetFile(); fileNode != nil {
+		rootHeader.Type = nar.TypeRegular
+		rootHeader.Size = int64(fileNode.GetSize())
+		rootHeader.Executable = fileNode.GetExecutable()
+		err := narWriter.WriteHeader(rootHeader)
+		if err != nil {
+			return fmt.Errorf("unable to write root header: %w", err)
+		}
+
+		// if it's a regular file, retrieve and write the contents
+		blobReader, err := blobLookupFn(fileNode.GetDigest())
+		if err != nil {
+			return fmt.Errorf("unable to lookup blob: %w", err)
+		}
+		defer blobReader.Close()
+
+		_, err = io.Copy(narWriter, blobReader)
+		if err != nil {
+			return fmt.Errorf("unable to read from blobReader: %w", err)
+		}
+
+		err = blobReader.Close()
+		if err != nil {
+			return fmt.Errorf("unable to close content reader: %w", err)
+		}
+
+		err = narWriter.Close()
+		if err != nil {
+			return fmt.Errorf("unable to close nar reader: %w", err)
+		}
+
+		return nil
+	} else if symlinkNode := rootNode.GetSymlink(); symlinkNode != nil {
+		rootHeader.Type = nar.TypeSymlink
+		rootHeader.LinkTarget = string(symlinkNode.GetTarget())
+		err := narWriter.WriteHeader(rootHeader)
+		if err != nil {
+			return fmt.Errorf("unable to write root header: %w", err)
+		}
+
+		err = narWriter.Close()
+		if err != nil {
+			return fmt.Errorf("unable to close nar reader: %w", err)
+		}
+	} else if directoryNode := rootNode.GetDirectory(); directoryNode != nil {
+		// We have a directory at the root, look it up and put in on the stack.
+		directory, err := directoryLookupFn(directoryNode.GetDigest())
+		if err != nil {
+			return fmt.Errorf("unable to lookup directory: %w", err)
+		}
+		stackDirectories = append(stackDirectories, directory)
+		stackPaths = append(stackPaths, "/")
+
+		err = narWriter.WriteHeader(&nar.Header{
+			Path: "/",
+			Type: nar.TypeDirectory,
+		})
+
+		if err != nil {
+			return fmt.Errorf("error writing header: %w", err)
+		}
+	} else {
+		panic("invalid type") // unreachable
+	}
+
+	// as long as the stack is not empty, we keep running.
+	for {
+		if len(stackDirectories) == 0 {
+			return nil
+		}
+
+		// Peek at the current top of the stack.
+		topOfStack := stackDirectories[len(stackDirectories)-1]
+		topOfStackPath := stackPaths[len(stackPaths)-1]
+
+		// get the next element that's lexicographically smallest, and drain it from
+		// the current directory on top of the stack.
+		nextNode := drainNextNode(topOfStack)
+
+		// If nextNode returns nil, there's nothing left in the directory node, so we
+		// can emit it from the stack.
+		// Contrary to the import case, we don't emit the node popping from the stack, but when pushing.
+		if nextNode == nil {
+			// pop off stack
+			stackDirectories = stackDirectories[:len(stackDirectories)-1]
+			stackPaths = stackPaths[:len(stackPaths)-1]
+
+			continue
+		}
+
+		switch n := (nextNode).(type) {
+		case *castorev1pb.DirectoryNode:
+			err := narWriter.WriteHeader(&nar.Header{
+				Path: path.Join(topOfStackPath, string(n.GetName())),
+				Type: nar.TypeDirectory,
+			})
+			if err != nil {
+				return fmt.Errorf("unable to write nar header: %w", err)
+			}
+
+			d, err := directoryLookupFn(n.GetDigest())
+			if err != nil {
+				return fmt.Errorf("unable to lookup directory: %w", err)
+			}
+
+			// add to stack
+			stackDirectories = append(stackDirectories, d)
+			stackPaths = append(stackPaths, path.Join(topOfStackPath, string(n.GetName())))
+		case *castorev1pb.FileNode:
+			err := narWriter.WriteHeader(&nar.Header{
+				Path:       path.Join(topOfStackPath, string(n.GetName())),
+				Type:       nar.TypeRegular,
+				Size:       int64(n.GetSize()),
+				Executable: n.GetExecutable(),
+			})
+			if err != nil {
+				return fmt.Errorf("unable to write nar header: %w", err)
+			}
+
+			// copy file contents
+			contentReader, err := blobLookupFn(n.GetDigest())
+			if err != nil {
+				return fmt.Errorf("unable to get blob: %w", err)
+			}
+			defer contentReader.Close()
+
+			_, err = io.Copy(narWriter, contentReader)
+			if err != nil {
+				return fmt.Errorf("unable to copy contents from contentReader: %w", err)
+			}
+
+			err = contentReader.Close()
+			if err != nil {
+				return fmt.Errorf("unable to close content reader: %w", err)
+			}
+		case *castorev1pb.SymlinkNode:
+			err := narWriter.WriteHeader(&nar.Header{
+				Path:       path.Join(topOfStackPath, string(n.GetName())),
+				Type:       nar.TypeSymlink,
+				LinkTarget: string(n.GetTarget()),
+			})
+			if err != nil {
+				return fmt.Errorf("unable to write nar header: %w", err)
+			}
+		}
+	}
+}
+
+// drainNextNode will drain a directory message with one of its child nodes,
+// whichever comes first alphabetically.
+func drainNextNode(d *castorev1pb.Directory) interface{} {
+	switch v := (smallestNode(d)).(type) {
+	case *castorev1pb.DirectoryNode:
+		d.Directories = d.Directories[1:]
+		return v
+	case *castorev1pb.FileNode:
+		d.Files = d.Files[1:]
+		return v
+	case *castorev1pb.SymlinkNode:
+		d.Symlinks = d.Symlinks[1:]
+		return v
+	case nil:
+		return nil
+	default:
+		panic("invalid type encountered")
+	}
+}
+
+// smallestNode will return the node from a directory message,
+// whichever comes first alphabetically.
+func smallestNode(d *castorev1pb.Directory) interface{} {
+	childDirectories := d.GetDirectories()
+	childFiles := d.GetFiles()
+	childSymlinks := d.GetSymlinks()
+
+	if len(childDirectories) > 0 {
+		if len(childFiles) > 0 {
+			if len(childSymlinks) > 0 {
+				// directories,files,symlinks
+				return smallerNode(smallerNode(childDirectories[0], childFiles[0]), childSymlinks[0])
+			} else {
+				// directories,files,!symlinks
+				return smallerNode(childDirectories[0], childFiles[0])
+			}
+		} else {
+			// directories,!files
+			if len(childSymlinks) > 0 {
+				// directories,!files,symlinks
+				return smallerNode(childDirectories[0], childSymlinks[0])
+			} else {
+				// directories,!files,!symlinks
+				return childDirectories[0]
+			}
+		}
+	} else {
+		// !directories
+		if len(childFiles) > 0 {
+			// !directories,files
+			if len(childSymlinks) > 0 {
+				// !directories,files,symlinks
+				return smallerNode(childFiles[0], childSymlinks[0])
+			} else {
+				// !directories,files,!symlinks
+				return childFiles[0]
+			}
+		} else {
+			//!directories,!files
+			if len(childSymlinks) > 0 {
+				//!directories,!files,symlinks
+				return childSymlinks[0]
+			} else {
+				//!directories,!files,!symlinks
+				return nil
+			}
+		}
+	}
+}
+
+// smallerNode compares two nodes by their name,
+// and returns the one with the smaller name.
+// both nodes may not be nil, we do check for these cases in smallestNode.
+func smallerNode(a interface{ GetName() []byte }, b interface{ GetName() []byte }) interface{ GetName() []byte } {
+	if string(a.GetName()) < string(b.GetName()) {
+		return a
+	} else {
+		return b
+	}
+}
diff --git a/tvix/store-go/export_test.go b/tvix/store-go/export_test.go
new file mode 100644
index 0000000000..6814df6414
--- /dev/null
+++ b/tvix/store-go/export_test.go
@@ -0,0 +1,134 @@
+package storev1_test
+
+import (
+	"bytes"
+	"io"
+	"os"
+	"testing"
+
+	castorev1pb "code.tvl.fyi/tvix/castore-go"
+	storev1pb "code.tvl.fyi/tvix/store-go"
+	"github.com/stretchr/testify/require"
+)
+
+func mustDirectoryDigest(d *castorev1pb.Directory) []byte {
+	dgst, err := d.Digest()
+	if err != nil {
+		panic(err)
+	}
+	return dgst
+}
+
+func TestSymlink(t *testing.T) {
+	node := &castorev1pb.Node{
+		Node: &castorev1pb.Node_Symlink{
+			Symlink: &castorev1pb.SymlinkNode{
+				Name:   []byte("doesntmatter"),
+				Target: []byte("/nix/store/somewhereelse"),
+			},
+		},
+	}
+
+	var buf bytes.Buffer
+
+	err := storev1pb.Export(&buf, node, func([]byte) (*castorev1pb.Directory, error) {
+		panic("no directories expected")
+	}, func([]byte) (io.ReadCloser, error) {
+		panic("no files expected")
+	})
+	require.NoError(t, err, "exporter shouldn't fail")
+
+	f, err := os.Open("testdata/symlink.nar")
+	require.NoError(t, err)
+
+	bytesExpected, err := io.ReadAll(f)
+	if err != nil {
+		panic(err)
+	}
+
+	require.Equal(t, bytesExpected, buf.Bytes(), "expected nar contents to match")
+}
+
+func TestRegular(t *testing.T) {
+	// The blake3 digest of the 0x01 byte.
+	BLAKE3_DIGEST_0X01 := []byte{
+		0x48, 0xfc, 0x72, 0x1f, 0xbb, 0xc1, 0x72, 0xe0, 0x92, 0x5f, 0xa2, 0x7a, 0xf1, 0x67, 0x1d,
+		0xe2, 0x25, 0xba, 0x92, 0x71, 0x34, 0x80, 0x29, 0x98, 0xb1, 0x0a, 0x15, 0x68, 0xa1, 0x88,
+		0x65, 0x2b,
+	}
+
+	node := &castorev1pb.Node{
+		Node: &castorev1pb.Node_File{
+			File: &castorev1pb.FileNode{
+				Name:       []byte("doesntmatter"),
+				Digest:     BLAKE3_DIGEST_0X01,
+				Size:       1,
+				Executable: false,
+			},
+		},
+	}
+
+	var buf bytes.Buffer
+
+	err := storev1pb.Export(&buf, node, func([]byte) (*castorev1pb.Directory, error) {
+		panic("no directories expected")
+	}, func(blobRef []byte) (io.ReadCloser, error) {
+		if !bytes.Equal(blobRef, BLAKE3_DIGEST_0X01) {
+			panic("unexpected blobref")
+		}
+		return io.NopCloser(bytes.NewBuffer([]byte{0x01})), nil
+	})
+	require.NoError(t, err, "exporter shouldn't fail")
+
+	f, err := os.Open("testdata/onebyteregular.nar")
+	require.NoError(t, err)
+
+	bytesExpected, err := io.ReadAll(f)
+	if err != nil {
+		panic(err)
+	}
+
+	require.Equal(t, bytesExpected, buf.Bytes(), "expected nar contents to match")
+}
+
+func TestEmptyDirectory(t *testing.T) {
+	// construct empty directory node this refers to
+	emptyDirectory := &castorev1pb.Directory{
+		Directories: []*castorev1pb.DirectoryNode{},
+		Files:       []*castorev1pb.FileNode{},
+		Symlinks:    []*castorev1pb.SymlinkNode{},
+	}
+	emptyDirectoryDigest := mustDirectoryDigest(emptyDirectory)
+
+	node := &castorev1pb.Node{
+		Node: &castorev1pb.Node_Directory{
+			Directory: &castorev1pb.DirectoryNode{
+				Name:   []byte("doesntmatter"),
+				Digest: emptyDirectoryDigest,
+				Size:   0,
+			},
+		},
+	}
+
+	var buf bytes.Buffer
+
+	err := storev1pb.Export(&buf, node, func(directoryRef []byte) (*castorev1pb.Directory, error) {
+		if !bytes.Equal(directoryRef, emptyDirectoryDigest) {
+			panic("unexpected directoryRef")
+		}
+		return emptyDirectory, nil
+	}, func([]byte) (io.ReadCloser, error) {
+		panic("no files expected")
+	})
+	require.NoError(t, err, "exporter shouldn't fail")
+
+	f, err := os.Open("testdata/emptydirectory.nar")
+	require.NoError(t, err)
+
+	bytesExpected, err := io.ReadAll(f)
+	if err != nil {
+		panic(err)
+	}
+
+	require.Equal(t, bytesExpected, buf.Bytes(), "expected nar contents to match")
+}
diff --git a/tvix/store-go/go.mod b/tvix/store-go/go.mod
new file mode 100644
index 0000000000..bd8450b000
--- /dev/null
+++ b/tvix/store-go/go.mod
@@ -0,0 +1,25 @@
+module code.tvl.fyi/tvix/store-go
+
+go 1.19
+
+require (
+	code.tvl.fyi/tvix/castore-go v0.0.0-20231105151352-990d6ba2175e
+	github.com/google/go-cmp v0.5.9
+	github.com/nix-community/go-nix v0.0.0-20231009143713-ebca3299475b
+	github.com/stretchr/testify v1.8.1
+	google.golang.org/grpc v1.59.0
+	google.golang.org/protobuf v1.31.0
+)
+
+require (
+	github.com/davecgh/go-spew v1.1.1 // indirect
+	github.com/golang/protobuf v1.5.3 // indirect
+	github.com/klauspost/cpuid/v2 v2.2.5 // indirect
+	github.com/pmezard/go-difflib v1.0.0 // indirect
+	golang.org/x/net v0.17.0 // indirect
+	golang.org/x/sys v0.14.0 // indirect
+	golang.org/x/text v0.14.0 // indirect
+	google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405 // indirect
+	gopkg.in/yaml.v3 v3.0.1 // indirect
+	lukechampine.com/blake3 v1.2.1 // indirect
+)
diff --git a/tvix/store-go/go.sum b/tvix/store-go/go.sum
new file mode 100644
index 0000000000..1b4bb2e708
--- /dev/null
+++ b/tvix/store-go/go.sum
@@ -0,0 +1,47 @@
+code.tvl.fyi/tvix/castore-go v0.0.0-20231105151352-990d6ba2175e h1:Nj+anfyEYeEdhnIo2BG/N1ZwQl1IvI7AH3TbNDLwUOA=
+code.tvl.fyi/tvix/castore-go v0.0.0-20231105151352-990d6ba2175e/go.mod h1:+vKbozsa04yy2TWh3kUVU568jaza3Hf0p1jAEoMoCwA=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
+github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
+github.com/nix-community/go-nix v0.0.0-20231009143713-ebca3299475b h1:AWEKOdDO3JnHApQDOmONEKLXbMCQJhYJJfJpiWB9VGI=
+github.com/nix-community/go-nix v0.0.0-20231009143713-ebca3299475b/go.mod h1:hHM9UK2zOCjvmiLgeaW4LVbOW/vBaRWFJGzfi31/slQ=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
+github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
+golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
+golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
+golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405 h1:AB/lmRny7e2pLhFEYIbl5qkDAUt2h0ZRO4wGPhZf+ik=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405/go.mod h1:67X1fPuzjcrkymZzZV1vvkFeTn2Rvc6lYF9MYFGCcwE=
+google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
+google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
+google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI=
+lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
diff --git a/tvix/store-go/pathinfo.go b/tvix/store-go/pathinfo.go
new file mode 100644
index 0000000000..d0384c4fe2
--- /dev/null
+++ b/tvix/store-go/pathinfo.go
@@ -0,0 +1,99 @@
+package storev1
+
+import (
+	"bytes"
+	"crypto/sha256"
+	"encoding/base64"
+	"fmt"
+
+	"github.com/nix-community/go-nix/pkg/storepath"
+)
+
+// Validate performs some checks on the PathInfo struct, returning either the
+// StorePath of the root node, or an error.
+func (p *PathInfo) Validate() (*storepath.StorePath, error) {
+	// ensure References has the right number of bytes.
+	for i, reference := range p.GetReferences() {
+		if len(reference) != storepath.PathHashSize {
+			return nil, fmt.Errorf("invalid length of digest at position %d, expected %d, got %d", i, storepath.PathHashSize, len(reference))
+		}
+	}
+
+	// If there's a Narinfo field populated..
+	if narInfo := p.GetNarinfo(); narInfo != nil {
+		// ensure the NarSha256 digest has the correct length.
+		if len(narInfo.GetNarSha256()) != sha256.Size {
+			return nil, fmt.Errorf("invalid number of bytes for NarSha256: expected %d, got %d", sha256.Size, len(narInfo.GetNarSha256()))
+		}
+
+		// ensure the number of references matches len(References).
+		if len(narInfo.GetReferenceNames()) != len(p.GetReferences()) {
+			return nil, fmt.Errorf("inconsistent number of references: %d (references) vs %d (narinfo)", len(narInfo.GetReferenceNames()), len(p.GetReferences()))
+		}
+
+		// for each ReferenceName…
+		for i, referenceName := range narInfo.GetReferenceNames() {
+			// ensure it parses to a store path
+			storePath, err := storepath.FromString(referenceName)
+			if err != nil {
+				return nil, fmt.Errorf("invalid ReferenceName at position %d: %w", i, err)
+			}
+
+			// ensure the digest matches the one at References[i]
+			if !bytes.Equal(p.GetReferences()[i], storePath.Digest) {
+				return nil, fmt.Errorf(
+					"digest in ReferenceName at position %d does not match digest in PathInfo, expected %s, got %s",
+					i,
+					base64.StdEncoding.EncodeToString(p.GetReferences()[i]),
+					base64.StdEncoding.EncodeToString(storePath.Digest),
+				)
+			}
+		}
+
+		// If the Deriver field is populated, ensure it parses to a StorePath.
+		// We can't check for it to *not* end with .drv, as the .drv files produced by
+		// recursive Nix end with multiple .drv suffixes, and only one is popped when
+		// converting to this field.
+		if deriver := narInfo.GetDeriver(); deriver != nil {
+			deriverStorePath := storepath.StorePath{
+				Name:   string(deriver.GetName()),
+				Digest: deriver.GetDigest(),
+			}
+			if err := deriverStorePath.Validate(); err != nil {
+				return nil, fmt.Errorf("invalid deriver field: %w", err)
+			}
+		}
+	}
+
+	// ensure there is a (root) node present
+	rootNode := p.GetNode()
+	if rootNode == nil {
+		return nil, fmt.Errorf("root node must be set")
+	}
+
+	if err := rootNode.Validate(); err != nil {
+		return nil, fmt.Errorf("root node failed validation: %w", err)
+	}
+
+	// for all three node types, ensure the name properly parses to a store path.
+	// This is a stricter check as the ones already performed in the rootNode.Validate() call.
+	var rootNodeName []byte
+
+	if node := rootNode.GetDirectory(); node != nil {
+		rootNodeName = node.GetName()
+	} else if node := rootNode.GetFile(); node != nil {
+		rootNodeName = node.GetName()
+	} else if node := rootNode.GetSymlink(); node != nil {
+		rootNodeName = node.GetName()
+	} else {
+		// already caught by rootNode.Validate()
+		panic("unreachable")
+	}
+
+	storePath, err := storepath.FromString(string(rootNodeName))
+	if err != nil {
+		return nil, fmt.Errorf("unable to parse root node name %s as StorePath: %w", rootNodeName, err)
+	}
+
+	return storePath, nil
+}
diff --git a/tvix/store-go/pathinfo.pb.go b/tvix/store-go/pathinfo.pb.go
new file mode 100644
index 0000000000..a4915a3c1f
--- /dev/null
+++ b/tvix/store-go/pathinfo.pb.go
@@ -0,0 +1,657 @@
+// SPDX-License-Identifier: MIT
+// Copyright © 2022 The Tvix Authors
+
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// 	protoc-gen-go v1.33.0
+// 	protoc        (unknown)
+// source: tvix/store/protos/pathinfo.proto
+
+package storev1
+
+import (
+	castore_go "code.tvl.fyi/tvix/castore-go"
+	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+	reflect "reflect"
+	sync "sync"
+)
+
+const (
+	// Verify that this generated code is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+	// Verify that runtime/protoimpl is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type NARInfo_CA_Hash int32
+
+const (
+	// produced when uploading fixed-output store paths using NAR-based
+	// hashing (`outputHashMode = "recursive"`).
+	NARInfo_CA_NAR_SHA256 NARInfo_CA_Hash = 0
+	NARInfo_CA_NAR_SHA1   NARInfo_CA_Hash = 1
+	NARInfo_CA_NAR_SHA512 NARInfo_CA_Hash = 2
+	NARInfo_CA_NAR_MD5    NARInfo_CA_Hash = 3
+	// Produced when uploading .drv files or outputs produced by
+	// builtins.toFile.
+	// Produces equivalent digests as FLAT_SHA256, but is a separate
+	// hashing type in Nix, affecting output path calculation.
+	NARInfo_CA_TEXT_SHA256 NARInfo_CA_Hash = 4
+	// Produced when using fixed-output derivations with
+	// `outputHashMode = "flat"`.
+	NARInfo_CA_FLAT_SHA1   NARInfo_CA_Hash = 5
+	NARInfo_CA_FLAT_MD5    NARInfo_CA_Hash = 6
+	NARInfo_CA_FLAT_SHA256 NARInfo_CA_Hash = 7
+	NARInfo_CA_FLAT_SHA512 NARInfo_CA_Hash = 8
+)
+
+// Enum value maps for NARInfo_CA_Hash.
+var (
+	NARInfo_CA_Hash_name = map[int32]string{
+		0: "NAR_SHA256",
+		1: "NAR_SHA1",
+		2: "NAR_SHA512",
+		3: "NAR_MD5",
+		4: "TEXT_SHA256",
+		5: "FLAT_SHA1",
+		6: "FLAT_MD5",
+		7: "FLAT_SHA256",
+		8: "FLAT_SHA512",
+	}
+	NARInfo_CA_Hash_value = map[string]int32{
+		"NAR_SHA256":  0,
+		"NAR_SHA1":    1,
+		"NAR_SHA512":  2,
+		"NAR_MD5":     3,
+		"TEXT_SHA256": 4,
+		"FLAT_SHA1":   5,
+		"FLAT_MD5":    6,
+		"FLAT_SHA256": 7,
+		"FLAT_SHA512": 8,
+	}
+)
+
+func (x NARInfo_CA_Hash) Enum() *NARInfo_CA_Hash {
+	p := new(NARInfo_CA_Hash)
+	*p = x
+	return p
+}
+
+func (x NARInfo_CA_Hash) String() string {
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (NARInfo_CA_Hash) Descriptor() protoreflect.EnumDescriptor {
+	return file_tvix_store_protos_pathinfo_proto_enumTypes[0].Descriptor()
+}
+
+func (NARInfo_CA_Hash) Type() protoreflect.EnumType {
+	return &file_tvix_store_protos_pathinfo_proto_enumTypes[0]
+}
+
+func (x NARInfo_CA_Hash) Number() protoreflect.EnumNumber {
+	return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Use NARInfo_CA_Hash.Descriptor instead.
+func (NARInfo_CA_Hash) EnumDescriptor() ([]byte, []int) {
+	return file_tvix_store_protos_pathinfo_proto_rawDescGZIP(), []int{2, 1, 0}
+}
+
+// PathInfo shows information about a Nix Store Path.
+// That's a single element inside /nix/store.
+type PathInfo struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// The path can be a directory, file or symlink.
+	Node *castore_go.Node `protobuf:"bytes,1,opt,name=node,proto3" json:"node,omitempty"`
+	// List of references (output path hashes)
+	// This really is the raw *bytes*, after decoding nixbase32, and not a
+	// base32-encoded string.
+	References [][]byte `protobuf:"bytes,2,rep,name=references,proto3" json:"references,omitempty"`
+	// see below.
+	Narinfo *NARInfo `protobuf:"bytes,3,opt,name=narinfo,proto3" json:"narinfo,omitempty"`
+}
+
+func (x *PathInfo) Reset() {
+	*x = PathInfo{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_tvix_store_protos_pathinfo_proto_msgTypes[0]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *PathInfo) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*PathInfo) ProtoMessage() {}
+
+func (x *PathInfo) ProtoReflect() protoreflect.Message {
+	mi := &file_tvix_store_protos_pathinfo_proto_msgTypes[0]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use PathInfo.ProtoReflect.Descriptor instead.
+func (*PathInfo) Descriptor() ([]byte, []int) {
+	return file_tvix_store_protos_pathinfo_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *PathInfo) GetNode() *castore_go.Node {
+	if x != nil {
+		return x.Node
+	}
+	return nil
+}
+
+func (x *PathInfo) GetReferences() [][]byte {
+	if x != nil {
+		return x.References
+	}
+	return nil
+}
+
+func (x *PathInfo) GetNarinfo() *NARInfo {
+	if x != nil {
+		return x.Narinfo
+	}
+	return nil
+}
+
+// Represents a path in the Nix store (a direct child of STORE_DIR).
+// It is commonly formatted by a nixbase32-encoding the digest, and
+// concatenating the name, separated by a `-`.
+type StorePath struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// The string after digest and `-`.
+	Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
+	// The digest (20 bytes).
+	Digest []byte `protobuf:"bytes,2,opt,name=digest,proto3" json:"digest,omitempty"`
+}
+
+func (x *StorePath) Reset() {
+	*x = StorePath{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_tvix_store_protos_pathinfo_proto_msgTypes[1]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *StorePath) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*StorePath) ProtoMessage() {}
+
+func (x *StorePath) ProtoReflect() protoreflect.Message {
+	mi := &file_tvix_store_protos_pathinfo_proto_msgTypes[1]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use StorePath.ProtoReflect.Descriptor instead.
+func (*StorePath) Descriptor() ([]byte, []int) {
+	return file_tvix_store_protos_pathinfo_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *StorePath) GetName() string {
+	if x != nil {
+		return x.Name
+	}
+	return ""
+}
+
+func (x *StorePath) GetDigest() []byte {
+	if x != nil {
+		return x.Digest
+	}
+	return nil
+}
+
+// Nix C++ uses NAR (Nix Archive) as a format to transfer store paths,
+// and stores metadata and signatures in NARInfo files.
+// Store all these attributes in a separate message.
+//
+// This is useful to render .narinfo files to clients, or to preserve/validate
+// these signatures.
+// As verifying these signatures requires the whole NAR file to be synthesized,
+// moving to another signature scheme is desired.
+// Even then, it still makes sense to hold this data, for old clients.
+type NARInfo struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// This size of the NAR file, in bytes.
+	NarSize uint64 `protobuf:"varint,1,opt,name=nar_size,json=narSize,proto3" json:"nar_size,omitempty"`
+	// The sha256 of the NAR file representation.
+	NarSha256 []byte `protobuf:"bytes,2,opt,name=nar_sha256,json=narSha256,proto3" json:"nar_sha256,omitempty"`
+	// The signatures in a .narinfo file.
+	Signatures []*NARInfo_Signature `protobuf:"bytes,3,rep,name=signatures,proto3" json:"signatures,omitempty"`
+	// A list of references. To validate .narinfo signatures, a fingerprint needs
+	// to be constructed.
+	// This fingerprint doesn't just contain the hashes of the output paths of all
+	// references (like PathInfo.references), but their whole (base)names, so we
+	// need to keep them somewhere.
+	ReferenceNames []string `protobuf:"bytes,4,rep,name=reference_names,json=referenceNames,proto3" json:"reference_names,omitempty"`
+	// The StorePath of the .drv file producing this output.
+	// The .drv suffix is omitted in its `name` field.
+	Deriver *StorePath `protobuf:"bytes,5,opt,name=deriver,proto3" json:"deriver,omitempty"`
+	// The CA field in the .narinfo.
+	// Its textual representations seen in the wild are one of the following:
+	//   - `fixed:r:sha256:1gcky5hlf5vqfzpyhihydmm54grhc94mcs8w7xr8613qsqb1v2j6`
+	//     fixed-output derivations using "recursive" `outputHashMode`.
+	//   - `fixed:sha256:19xqkh72crbcba7flwxyi3n293vav6d7qkzkh2v4zfyi4iia8vj8
+	//     fixed-output derivations using "flat" `outputHashMode`
+	//   - `text:sha256:19xqkh72crbcba7flwxyi3n293vav6d7qkzkh2v4zfyi4iia8vj8`
+	//     Text hashing, used for uploaded .drv files and outputs produced by
+	//     builtins.toFile.
+	//
+	// Semantically, they can be split into the following components:
+	//   - "content address prefix". Currently, "fixed" and "text" are supported.
+	//   - "hash mode". Currently, "flat" and "recursive" are supported.
+	//   - "hash type". The underlying hash function used.
+	//     Currently, sha1, md5, sha256, sha512.
+	//   - "digest". The digest itself.
+	//
+	// There are some restrictions on the possible combinations.
+	// For example, `text` and `fixed:recursive` always imply sha256.
+	//
+	// We use an enum to encode the possible combinations, and optimize for the
+	// common case, `fixed:recursive`, identified as `NAR_SHA256`.
+	Ca *NARInfo_CA `protobuf:"bytes,6,opt,name=ca,proto3" json:"ca,omitempty"`
+}
+
+func (x *NARInfo) Reset() {
+	*x = NARInfo{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_tvix_store_protos_pathinfo_proto_msgTypes[2]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *NARInfo) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*NARInfo) ProtoMessage() {}
+
+func (x *NARInfo) ProtoReflect() protoreflect.Message {
+	mi := &file_tvix_store_protos_pathinfo_proto_msgTypes[2]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use NARInfo.ProtoReflect.Descriptor instead.
+func (*NARInfo) Descriptor() ([]byte, []int) {
+	return file_tvix_store_protos_pathinfo_proto_rawDescGZIP(), []int{2}
+}
+
+func (x *NARInfo) GetNarSize() uint64 {
+	if x != nil {
+		return x.NarSize
+	}
+	return 0
+}
+
+func (x *NARInfo) GetNarSha256() []byte {
+	if x != nil {
+		return x.NarSha256
+	}
+	return nil
+}
+
+func (x *NARInfo) GetSignatures() []*NARInfo_Signature {
+	if x != nil {
+		return x.Signatures
+	}
+	return nil
+}
+
+func (x *NARInfo) GetReferenceNames() []string {
+	if x != nil {
+		return x.ReferenceNames
+	}
+	return nil
+}
+
+func (x *NARInfo) GetDeriver() *StorePath {
+	if x != nil {
+		return x.Deriver
+	}
+	return nil
+}
+
+func (x *NARInfo) GetCa() *NARInfo_CA {
+	if x != nil {
+		return x.Ca
+	}
+	return nil
+}
+
+// This represents a (parsed) signature line in a .narinfo file.
+type NARInfo_Signature struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
+	Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"`
+}
+
+func (x *NARInfo_Signature) Reset() {
+	*x = NARInfo_Signature{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_tvix_store_protos_pathinfo_proto_msgTypes[3]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *NARInfo_Signature) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*NARInfo_Signature) ProtoMessage() {}
+
+func (x *NARInfo_Signature) ProtoReflect() protoreflect.Message {
+	mi := &file_tvix_store_protos_pathinfo_proto_msgTypes[3]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use NARInfo_Signature.ProtoReflect.Descriptor instead.
+func (*NARInfo_Signature) Descriptor() ([]byte, []int) {
+	return file_tvix_store_protos_pathinfo_proto_rawDescGZIP(), []int{2, 0}
+}
+
+func (x *NARInfo_Signature) GetName() string {
+	if x != nil {
+		return x.Name
+	}
+	return ""
+}
+
+func (x *NARInfo_Signature) GetData() []byte {
+	if x != nil {
+		return x.Data
+	}
+	return nil
+}
+
+type NARInfo_CA struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// The hashing type used.
+	Type NARInfo_CA_Hash `protobuf:"varint,1,opt,name=type,proto3,enum=tvix.store.v1.NARInfo_CA_Hash" json:"type,omitempty"`
+	// The digest, in raw bytes.
+	Digest []byte `protobuf:"bytes,2,opt,name=digest,proto3" json:"digest,omitempty"`
+}
+
+func (x *NARInfo_CA) Reset() {
+	*x = NARInfo_CA{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_tvix_store_protos_pathinfo_proto_msgTypes[4]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *NARInfo_CA) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*NARInfo_CA) ProtoMessage() {}
+
+func (x *NARInfo_CA) ProtoReflect() protoreflect.Message {
+	mi := &file_tvix_store_protos_pathinfo_proto_msgTypes[4]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use NARInfo_CA.ProtoReflect.Descriptor instead.
+func (*NARInfo_CA) Descriptor() ([]byte, []int) {
+	return file_tvix_store_protos_pathinfo_proto_rawDescGZIP(), []int{2, 1}
+}
+
+func (x *NARInfo_CA) GetType() NARInfo_CA_Hash {
+	if x != nil {
+		return x.Type
+	}
+	return NARInfo_CA_NAR_SHA256
+}
+
+func (x *NARInfo_CA) GetDigest() []byte {
+	if x != nil {
+		return x.Digest
+	}
+	return nil
+}
+
+var File_tvix_store_protos_pathinfo_proto protoreflect.FileDescriptor
+
+var file_tvix_store_protos_pathinfo_proto_rawDesc = []byte{
+	0x0a, 0x20, 0x74, 0x76, 0x69, 0x78, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f,
+	0x74, 0x6f, 0x73, 0x2f, 0x70, 0x61, 0x74, 0x68, 0x69, 0x6e, 0x66, 0x6f, 0x2e, 0x70, 0x72, 0x6f,
+	0x74, 0x6f, 0x12, 0x0d, 0x74, 0x76, 0x69, 0x78, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x76,
+	0x31, 0x1a, 0x21, 0x74, 0x76, 0x69, 0x78, 0x2f, 0x63, 0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2f,
+	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x63, 0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x70,
+	0x72, 0x6f, 0x74, 0x6f, 0x22, 0x87, 0x01, 0x0a, 0x08, 0x50, 0x61, 0x74, 0x68, 0x49, 0x6e, 0x66,
+	0x6f, 0x12, 0x29, 0x0a, 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32,
+	0x15, 0x2e, 0x74, 0x76, 0x69, 0x78, 0x2e, 0x63, 0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x76,
+	0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x12, 0x1e, 0x0a, 0x0a,
+	0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0c,
+	0x52, 0x0a, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x30, 0x0a, 0x07,
+	0x6e, 0x61, 0x72, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e,
+	0x74, 0x76, 0x69, 0x78, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x41,
+	0x52, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x07, 0x6e, 0x61, 0x72, 0x69, 0x6e, 0x66, 0x6f, 0x22, 0x37,
+	0x0a, 0x09, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x50, 0x61, 0x74, 0x68, 0x12, 0x12, 0x0a, 0x04, 0x6e,
+	0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12,
+	0x16, 0x0a, 0x06, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52,
+	0x06, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x22, 0xa9, 0x04, 0x0a, 0x07, 0x4e, 0x41, 0x52, 0x49,
+	0x6e, 0x66, 0x6f, 0x12, 0x19, 0x0a, 0x08, 0x6e, 0x61, 0x72, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18,
+	0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x6e, 0x61, 0x72, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1d,
+	0x0a, 0x0a, 0x6e, 0x61, 0x72, 0x5f, 0x73, 0x68, 0x61, 0x32, 0x35, 0x36, 0x18, 0x02, 0x20, 0x01,
+	0x28, 0x0c, 0x52, 0x09, 0x6e, 0x61, 0x72, 0x53, 0x68, 0x61, 0x32, 0x35, 0x36, 0x12, 0x40, 0x0a,
+	0x0a, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28,
+	0x0b, 0x32, 0x20, 0x2e, 0x74, 0x76, 0x69, 0x78, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x76,
+	0x31, 0x2e, 0x4e, 0x41, 0x52, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74,
+	0x75, 0x72, 0x65, 0x52, 0x0a, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12,
+	0x27, 0x0a, 0x0f, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d,
+	0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65,
+	0x6e, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x32, 0x0a, 0x07, 0x64, 0x65, 0x72, 0x69,
+	0x76, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x74, 0x76, 0x69, 0x78,
+	0x2e, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x50,
+	0x61, 0x74, 0x68, 0x52, 0x07, 0x64, 0x65, 0x72, 0x69, 0x76, 0x65, 0x72, 0x12, 0x29, 0x0a, 0x02,
+	0x63, 0x61, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x74, 0x76, 0x69, 0x78, 0x2e,
+	0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x41, 0x52, 0x49, 0x6e, 0x66, 0x6f,
+	0x2e, 0x43, 0x41, 0x52, 0x02, 0x63, 0x61, 0x1a, 0x33, 0x0a, 0x09, 0x53, 0x69, 0x67, 0x6e, 0x61,
+	0x74, 0x75, 0x72, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01,
+	0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61,
+	0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x1a, 0xe4, 0x01, 0x0a,
+	0x02, 0x43, 0x41, 0x12, 0x32, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
+	0x0e, 0x32, 0x1e, 0x2e, 0x74, 0x76, 0x69, 0x78, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x76,
+	0x31, 0x2e, 0x4e, 0x41, 0x52, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x43, 0x41, 0x2e, 0x48, 0x61, 0x73,
+	0x68, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x69, 0x67, 0x65, 0x73,
+	0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x22,
+	0x91, 0x01, 0x0a, 0x04, 0x48, 0x61, 0x73, 0x68, 0x12, 0x0e, 0x0a, 0x0a, 0x4e, 0x41, 0x52, 0x5f,
+	0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x4e, 0x41, 0x52, 0x5f,
+	0x53, 0x48, 0x41, 0x31, 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x4e, 0x41, 0x52, 0x5f, 0x53, 0x48,
+	0x41, 0x35, 0x31, 0x32, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x4e, 0x41, 0x52, 0x5f, 0x4d, 0x44,
+	0x35, 0x10, 0x03, 0x12, 0x0f, 0x0a, 0x0b, 0x54, 0x45, 0x58, 0x54, 0x5f, 0x53, 0x48, 0x41, 0x32,
+	0x35, 0x36, 0x10, 0x04, 0x12, 0x0d, 0x0a, 0x09, 0x46, 0x4c, 0x41, 0x54, 0x5f, 0x53, 0x48, 0x41,
+	0x31, 0x10, 0x05, 0x12, 0x0c, 0x0a, 0x08, 0x46, 0x4c, 0x41, 0x54, 0x5f, 0x4d, 0x44, 0x35, 0x10,
+	0x06, 0x12, 0x0f, 0x0a, 0x0b, 0x46, 0x4c, 0x41, 0x54, 0x5f, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36,
+	0x10, 0x07, 0x12, 0x0f, 0x0a, 0x0b, 0x46, 0x4c, 0x41, 0x54, 0x5f, 0x53, 0x48, 0x41, 0x35, 0x31,
+	0x32, 0x10, 0x08, 0x42, 0x24, 0x5a, 0x22, 0x63, 0x6f, 0x64, 0x65, 0x2e, 0x74, 0x76, 0x6c, 0x2e,
+	0x66, 0x79, 0x69, 0x2f, 0x74, 0x76, 0x69, 0x78, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2d, 0x67,
+	0x6f, 0x3b, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+	0x33,
+}
+
+var (
+	file_tvix_store_protos_pathinfo_proto_rawDescOnce sync.Once
+	file_tvix_store_protos_pathinfo_proto_rawDescData = file_tvix_store_protos_pathinfo_proto_rawDesc
+)
+
+func file_tvix_store_protos_pathinfo_proto_rawDescGZIP() []byte {
+	file_tvix_store_protos_pathinfo_proto_rawDescOnce.Do(func() {
+		file_tvix_store_protos_pathinfo_proto_rawDescData = protoimpl.X.CompressGZIP(file_tvix_store_protos_pathinfo_proto_rawDescData)
+	})
+	return file_tvix_store_protos_pathinfo_proto_rawDescData
+}
+
+var file_tvix_store_protos_pathinfo_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
+var file_tvix_store_protos_pathinfo_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
+var file_tvix_store_protos_pathinfo_proto_goTypes = []interface{}{
+	(NARInfo_CA_Hash)(0),      // 0: tvix.store.v1.NARInfo.CA.Hash
+	(*PathInfo)(nil),          // 1: tvix.store.v1.PathInfo
+	(*StorePath)(nil),         // 2: tvix.store.v1.StorePath
+	(*NARInfo)(nil),           // 3: tvix.store.v1.NARInfo
+	(*NARInfo_Signature)(nil), // 4: tvix.store.v1.NARInfo.Signature
+	(*NARInfo_CA)(nil),        // 5: tvix.store.v1.NARInfo.CA
+	(*castore_go.Node)(nil),   // 6: tvix.castore.v1.Node
+}
+var file_tvix_store_protos_pathinfo_proto_depIdxs = []int32{
+	6, // 0: tvix.store.v1.PathInfo.node:type_name -> tvix.castore.v1.Node
+	3, // 1: tvix.store.v1.PathInfo.narinfo:type_name -> tvix.store.v1.NARInfo
+	4, // 2: tvix.store.v1.NARInfo.signatures:type_name -> tvix.store.v1.NARInfo.Signature
+	2, // 3: tvix.store.v1.NARInfo.deriver:type_name -> tvix.store.v1.StorePath
+	5, // 4: tvix.store.v1.NARInfo.ca:type_name -> tvix.store.v1.NARInfo.CA
+	0, // 5: tvix.store.v1.NARInfo.CA.type:type_name -> tvix.store.v1.NARInfo.CA.Hash
+	6, // [6:6] is the sub-list for method output_type
+	6, // [6:6] is the sub-list for method input_type
+	6, // [6:6] is the sub-list for extension type_name
+	6, // [6:6] is the sub-list for extension extendee
+	0, // [0:6] is the sub-list for field type_name
+}
+
+func init() { file_tvix_store_protos_pathinfo_proto_init() }
+func file_tvix_store_protos_pathinfo_proto_init() {
+	if File_tvix_store_protos_pathinfo_proto != nil {
+		return
+	}
+	if !protoimpl.UnsafeEnabled {
+		file_tvix_store_protos_pathinfo_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*PathInfo); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_tvix_store_protos_pathinfo_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*StorePath); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_tvix_store_protos_pathinfo_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*NARInfo); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_tvix_store_protos_pathinfo_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*NARInfo_Signature); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_tvix_store_protos_pathinfo_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*NARInfo_CA); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+	}
+	type x struct{}
+	out := protoimpl.TypeBuilder{
+		File: protoimpl.DescBuilder{
+			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+			RawDescriptor: file_tvix_store_protos_pathinfo_proto_rawDesc,
+			NumEnums:      1,
+			NumMessages:   5,
+			NumExtensions: 0,
+			NumServices:   0,
+		},
+		GoTypes:           file_tvix_store_protos_pathinfo_proto_goTypes,
+		DependencyIndexes: file_tvix_store_protos_pathinfo_proto_depIdxs,
+		EnumInfos:         file_tvix_store_protos_pathinfo_proto_enumTypes,
+		MessageInfos:      file_tvix_store_protos_pathinfo_proto_msgTypes,
+	}.Build()
+	File_tvix_store_protos_pathinfo_proto = out.File
+	file_tvix_store_protos_pathinfo_proto_rawDesc = nil
+	file_tvix_store_protos_pathinfo_proto_goTypes = nil
+	file_tvix_store_protos_pathinfo_proto_depIdxs = nil
+}
diff --git a/tvix/store-go/pathinfo_test.go b/tvix/store-go/pathinfo_test.go
new file mode 100644
index 0000000000..e248f52c8d
--- /dev/null
+++ b/tvix/store-go/pathinfo_test.go
@@ -0,0 +1,149 @@
+package storev1_test
+
+import (
+	"path"
+	"testing"
+
+	"github.com/nix-community/go-nix/pkg/storepath"
+	"github.com/stretchr/testify/assert"
+
+	castorev1pb "code.tvl.fyi/tvix/castore-go"
+	storev1pb "code.tvl.fyi/tvix/store-go"
+)
+
+const (
+	EXAMPLE_STORE_PATH = "00bgd045z0d4icpbc2yyz4gx48ak44la-net-tools-1.60_p2017022118243"
+)
+
+var (
+	exampleStorePathDigest = []byte{
+		0x8a, 0x12, 0x32, 0x15, 0x22, 0xfd, 0x91, 0xef, 0xbd, 0x60, 0xeb, 0xb2, 0x48, 0x1a, 0xf8, 0x85,
+		0x80, 0xf6, 0x16, 0x00}
+)
+
+func genPathInfoSymlink() *storev1pb.PathInfo {
+	return &storev1pb.PathInfo{
+		Node: &castorev1pb.Node{
+			Node: &castorev1pb.Node_Symlink{
+				Symlink: &castorev1pb.SymlinkNode{
+					Name:   []byte("00000000000000000000000000000000-dummy"),
+					Target: []byte("/nix/store/somewhereelse"),
+				},
+			},
+		},
+		References: [][]byte{exampleStorePathDigest},
+		Narinfo: &storev1pb.NARInfo{
+			NarSize:        0,
+			NarSha256:      []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+			Signatures:     []*storev1pb.NARInfo_Signature{},
+			ReferenceNames: []string{EXAMPLE_STORE_PATH},
+		},
+	}
+}
+
+func genPathInfoSymlinkThin() *storev1pb.PathInfo {
+	pi := genPathInfoSymlink()
+	pi.Narinfo = nil
+
+	return pi
+}
+
+func TestValidate(t *testing.T) {
+	t.Run("happy symlink", func(t *testing.T) {
+		storePath, err := genPathInfoSymlink().Validate()
+		assert.NoError(t, err, "PathInfo must validate")
+		assert.Equal(t, "00000000000000000000000000000000-dummy", storePath.String())
+	})
+
+	t.Run("happy symlink thin", func(t *testing.T) {
+		storePath, err := genPathInfoSymlinkThin().Validate()
+		assert.NoError(t, err, "PathInfo must validate")
+		assert.Equal(t, "00000000000000000000000000000000-dummy", storePath.String())
+	})
+
+	t.Run("invalid nar_sha256", func(t *testing.T) {
+		pi := genPathInfoSymlink()
+
+		// create broken references, where the reference digest is wrong
+		pi.Narinfo.NarSha256 = []byte{0xbe, 0xef}
+
+		_, err := pi.Validate()
+		assert.Error(t, err, "must not validate")
+	})
+
+	t.Run("invalid reference digest", func(t *testing.T) {
+		pi := genPathInfoSymlink()
+
+		// create broken references, where the reference digest is wrong
+		pi.References = append(pi.References, []byte{0x00})
+
+		_, err := pi.Validate()
+		assert.Error(t, err, "must not validate")
+	})
+
+	t.Run("invalid reference name", func(t *testing.T) {
+		pi := genPathInfoSymlink()
+
+		// make the reference name an invalid store path
+		pi.Narinfo.ReferenceNames[0] = "00000000000000000000000000000000-"
+
+		_, err := pi.Validate()
+		assert.Error(t, err, "must not validate")
+	})
+
+	t.Run("reference name digest mismatch", func(t *testing.T) {
+		pi := genPathInfoSymlink()
+
+		// cause the digest for the reference to mismatch
+		pi.Narinfo.ReferenceNames[0] = "11111111111111111111111111111111-dummy"
+
+		_, err := pi.Validate()
+		assert.Error(t, err, "must not validate")
+	})
+
+	t.Run("nil root node", func(t *testing.T) {
+		pi := genPathInfoSymlink()
+
+		pi.Node = nil
+
+		_, err := pi.Validate()
+		assert.Error(t, err, "must not validate")
+	})
+
+	t.Run("invalid root node name", func(t *testing.T) {
+		pi := genPathInfoSymlink()
+
+		// make the reference name an invalid store path - it may not be absolute
+		symlinkNode := pi.Node.GetSymlink()
+		symlinkNode.Name = []byte(path.Join(storepath.StoreDir, "00000000000000000000000000000000-dummy"))
+
+		_, err := pi.Validate()
+		assert.Error(t, err, "must not validate")
+	})
+
+	t.Run("happy deriver", func(t *testing.T) {
+		pi := genPathInfoSymlink()
+
+		// add the Deriver Field.
+		pi.Narinfo.Deriver = &storev1pb.StorePath{
+			Digest: exampleStorePathDigest,
+			Name:   "foo",
+		}
+
+		_, err := pi.Validate()
+		assert.NoError(t, err, "must validate")
+	})
+
+	t.Run("invalid deriver", func(t *testing.T) {
+		pi := genPathInfoSymlink()
+
+		// add the Deriver Field, with a broken digest
+		pi.Narinfo.Deriver = &storev1pb.StorePath{
+			Digest: []byte{},
+			Name:   "foo2",
+		}
+		_, err := pi.Validate()
+		assert.Error(t, err, "must not validate")
+	})
+
+}
diff --git a/tvix/store-go/pick_next_node_test.go b/tvix/store-go/pick_next_node_test.go
new file mode 100644
index 0000000000..55a6b034f1
--- /dev/null
+++ b/tvix/store-go/pick_next_node_test.go
@@ -0,0 +1,51 @@
+package storev1
+
+import (
+	"testing"
+
+	castorev1pb "code.tvl.fyi/tvix/castore-go"
+	"github.com/google/go-cmp/cmp"
+	"github.com/stretchr/testify/require"
+	"google.golang.org/protobuf/testing/protocmp"
+)
+
+func requireProtoEq(t *testing.T, expected interface{}, actual interface{}) {
+	if diff := cmp.Diff(expected, actual, protocmp.Transform()); diff != "" {
+		t.Errorf("unexpected difference:\n%v", diff)
+	}
+}
+
+func TestPopNextNode(t *testing.T) {
+	t.Run("empty directory", func(t *testing.T) {
+		d := &castorev1pb.Directory{
+			Directories: []*castorev1pb.DirectoryNode{},
+			Files:       []*castorev1pb.FileNode{},
+			Symlinks:    []*castorev1pb.SymlinkNode{},
+		}
+
+		n := drainNextNode(d)
+		require.Equal(t, nil, n)
+	})
+	t.Run("only directories", func(t *testing.T) {
+		ds := &castorev1pb.Directory{
+			Directories: []*castorev1pb.DirectoryNode{{
+				Name:   []byte("a"),
+				Digest: []byte{},
+				Size:   0,
+			}, {
+				Name:   []byte("b"),
+				Digest: []byte{},
+				Size:   0,
+			}},
+			Files:    []*castorev1pb.FileNode{},
+			Symlinks: []*castorev1pb.SymlinkNode{},
+		}
+
+		n := drainNextNode(ds)
+		requireProtoEq(t, &castorev1pb.DirectoryNode{
+			Name:   []byte("a"),
+			Digest: []byte{},
+			Size:   0,
+		}, n)
+	})
+}
diff --git a/tvix/store-go/rpc_pathinfo.pb.go b/tvix/store-go/rpc_pathinfo.pb.go
new file mode 100644
index 0000000000..883ffb3f01
--- /dev/null
+++ b/tvix/store-go/rpc_pathinfo.pb.go
@@ -0,0 +1,347 @@
+// SPDX-License-Identifier: MIT
+// Copyright © 2022 The Tvix Authors
+
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// 	protoc-gen-go v1.33.0
+// 	protoc        (unknown)
+// source: tvix/store/protos/rpc_pathinfo.proto
+
+package storev1
+
+import (
+	castore_go "code.tvl.fyi/tvix/castore-go"
+	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+	reflect "reflect"
+	sync "sync"
+)
+
+const (
+	// Verify that this generated code is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+	// Verify that runtime/protoimpl is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+// The parameters that can be used to lookup a (single) PathInfo object.
+// Currently, only a lookup by output hash is supported.
+type GetPathInfoRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// Types that are assignable to ByWhat:
+	//
+	//	*GetPathInfoRequest_ByOutputHash
+	ByWhat isGetPathInfoRequest_ByWhat `protobuf_oneof:"by_what"`
+}
+
+func (x *GetPathInfoRequest) Reset() {
+	*x = GetPathInfoRequest{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_tvix_store_protos_rpc_pathinfo_proto_msgTypes[0]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *GetPathInfoRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*GetPathInfoRequest) ProtoMessage() {}
+
+func (x *GetPathInfoRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_tvix_store_protos_rpc_pathinfo_proto_msgTypes[0]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use GetPathInfoRequest.ProtoReflect.Descriptor instead.
+func (*GetPathInfoRequest) Descriptor() ([]byte, []int) {
+	return file_tvix_store_protos_rpc_pathinfo_proto_rawDescGZIP(), []int{0}
+}
+
+func (m *GetPathInfoRequest) GetByWhat() isGetPathInfoRequest_ByWhat {
+	if m != nil {
+		return m.ByWhat
+	}
+	return nil
+}
+
+func (x *GetPathInfoRequest) GetByOutputHash() []byte {
+	if x, ok := x.GetByWhat().(*GetPathInfoRequest_ByOutputHash); ok {
+		return x.ByOutputHash
+	}
+	return nil
+}
+
+type isGetPathInfoRequest_ByWhat interface {
+	isGetPathInfoRequest_ByWhat()
+}
+
+type GetPathInfoRequest_ByOutputHash struct {
+	// The output hash of a nix path (20 bytes).
+	// This is the nixbase32-decoded portion of a Nix output path, so to substitute
+	// /nix/store/xm35nga2g20mz5sm5l6n8v3bdm86yj83-cowsay-3.04
+	// this field would contain nixbase32dec("xm35nga2g20mz5sm5l6n8v3bdm86yj83").
+	ByOutputHash []byte `protobuf:"bytes,1,opt,name=by_output_hash,json=byOutputHash,proto3,oneof"`
+}
+
+func (*GetPathInfoRequest_ByOutputHash) isGetPathInfoRequest_ByWhat() {}
+
+// The parameters that can be used to lookup (multiple) PathInfo objects.
+// Currently no filtering is possible, all objects are returned.
+type ListPathInfoRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+}
+
+func (x *ListPathInfoRequest) Reset() {
+	*x = ListPathInfoRequest{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_tvix_store_protos_rpc_pathinfo_proto_msgTypes[1]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *ListPathInfoRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ListPathInfoRequest) ProtoMessage() {}
+
+func (x *ListPathInfoRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_tvix_store_protos_rpc_pathinfo_proto_msgTypes[1]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use ListPathInfoRequest.ProtoReflect.Descriptor instead.
+func (*ListPathInfoRequest) Descriptor() ([]byte, []int) {
+	return file_tvix_store_protos_rpc_pathinfo_proto_rawDescGZIP(), []int{1}
+}
+
+// CalculateNARResponse is the response returned by the CalculateNAR request.
+//
+// It contains the size of the NAR representation (in bytes), and the sha56
+// digest.
+type CalculateNARResponse struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// This size of the NAR file, in bytes.
+	NarSize uint64 `protobuf:"varint,1,opt,name=nar_size,json=narSize,proto3" json:"nar_size,omitempty"`
+	// The sha256 of the NAR file representation.
+	NarSha256 []byte `protobuf:"bytes,2,opt,name=nar_sha256,json=narSha256,proto3" json:"nar_sha256,omitempty"`
+}
+
+func (x *CalculateNARResponse) Reset() {
+	*x = CalculateNARResponse{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_tvix_store_protos_rpc_pathinfo_proto_msgTypes[2]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *CalculateNARResponse) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*CalculateNARResponse) ProtoMessage() {}
+
+func (x *CalculateNARResponse) ProtoReflect() protoreflect.Message {
+	mi := &file_tvix_store_protos_rpc_pathinfo_proto_msgTypes[2]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use CalculateNARResponse.ProtoReflect.Descriptor instead.
+func (*CalculateNARResponse) Descriptor() ([]byte, []int) {
+	return file_tvix_store_protos_rpc_pathinfo_proto_rawDescGZIP(), []int{2}
+}
+
+func (x *CalculateNARResponse) GetNarSize() uint64 {
+	if x != nil {
+		return x.NarSize
+	}
+	return 0
+}
+
+func (x *CalculateNARResponse) GetNarSha256() []byte {
+	if x != nil {
+		return x.NarSha256
+	}
+	return nil
+}
+
+var File_tvix_store_protos_rpc_pathinfo_proto protoreflect.FileDescriptor
+
+var file_tvix_store_protos_rpc_pathinfo_proto_rawDesc = []byte{
+	0x0a, 0x24, 0x74, 0x76, 0x69, 0x78, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f,
+	0x74, 0x6f, 0x73, 0x2f, 0x72, 0x70, 0x63, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x69, 0x6e, 0x66, 0x6f,
+	0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0d, 0x74, 0x76, 0x69, 0x78, 0x2e, 0x73, 0x74, 0x6f,
+	0x72, 0x65, 0x2e, 0x76, 0x31, 0x1a, 0x21, 0x74, 0x76, 0x69, 0x78, 0x2f, 0x63, 0x61, 0x73, 0x74,
+	0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x63, 0x61, 0x73, 0x74, 0x6f,
+	0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x20, 0x74, 0x76, 0x69, 0x78, 0x2f, 0x73,
+	0x74, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x61, 0x74, 0x68,
+	0x69, 0x6e, 0x66, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x47, 0x0a, 0x12, 0x47, 0x65,
+	0x74, 0x50, 0x61, 0x74, 0x68, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
+	0x12, 0x26, 0x0a, 0x0e, 0x62, 0x79, 0x5f, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x68, 0x61,
+	0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x0c, 0x62, 0x79, 0x4f, 0x75,
+	0x74, 0x70, 0x75, 0x74, 0x48, 0x61, 0x73, 0x68, 0x42, 0x09, 0x0a, 0x07, 0x62, 0x79, 0x5f, 0x77,
+	0x68, 0x61, 0x74, 0x22, 0x15, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x61, 0x74, 0x68, 0x49,
+	0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x50, 0x0a, 0x14, 0x43, 0x61,
+	0x6c, 0x63, 0x75, 0x6c, 0x61, 0x74, 0x65, 0x4e, 0x41, 0x52, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
+	0x73, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x6e, 0x61, 0x72, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x01,
+	0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x6e, 0x61, 0x72, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1d, 0x0a,
+	0x0a, 0x6e, 0x61, 0x72, 0x5f, 0x73, 0x68, 0x61, 0x32, 0x35, 0x36, 0x18, 0x02, 0x20, 0x01, 0x28,
+	0x0c, 0x52, 0x09, 0x6e, 0x61, 0x72, 0x53, 0x68, 0x61, 0x32, 0x35, 0x36, 0x32, 0xa0, 0x02, 0x0a,
+	0x0f, 0x50, 0x61, 0x74, 0x68, 0x49, 0x6e, 0x66, 0x6f, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
+	0x12, 0x41, 0x0a, 0x03, 0x47, 0x65, 0x74, 0x12, 0x21, 0x2e, 0x74, 0x76, 0x69, 0x78, 0x2e, 0x73,
+	0x74, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x61, 0x74, 0x68, 0x49,
+	0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x74, 0x76, 0x69,
+	0x78, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x61, 0x74, 0x68, 0x49,
+	0x6e, 0x66, 0x6f, 0x12, 0x37, 0x0a, 0x03, 0x50, 0x75, 0x74, 0x12, 0x17, 0x2e, 0x74, 0x76, 0x69,
+	0x78, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x61, 0x74, 0x68, 0x49,
+	0x6e, 0x66, 0x6f, 0x1a, 0x17, 0x2e, 0x74, 0x76, 0x69, 0x78, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x65,
+	0x2e, 0x76, 0x31, 0x2e, 0x50, 0x61, 0x74, 0x68, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x4a, 0x0a, 0x0c,
+	0x43, 0x61, 0x6c, 0x63, 0x75, 0x6c, 0x61, 0x74, 0x65, 0x4e, 0x41, 0x52, 0x12, 0x15, 0x2e, 0x74,
+	0x76, 0x69, 0x78, 0x2e, 0x63, 0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4e,
+	0x6f, 0x64, 0x65, 0x1a, 0x23, 0x2e, 0x74, 0x76, 0x69, 0x78, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x65,
+	0x2e, 0x76, 0x31, 0x2e, 0x43, 0x61, 0x6c, 0x63, 0x75, 0x6c, 0x61, 0x74, 0x65, 0x4e, 0x41, 0x52,
+	0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x45, 0x0a, 0x04, 0x4c, 0x69, 0x73, 0x74,
+	0x12, 0x22, 0x2e, 0x74, 0x76, 0x69, 0x78, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31,
+	0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x61, 0x74, 0x68, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71,
+	0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x74, 0x76, 0x69, 0x78, 0x2e, 0x73, 0x74, 0x6f, 0x72,
+	0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x61, 0x74, 0x68, 0x49, 0x6e, 0x66, 0x6f, 0x30, 0x01, 0x42,
+	0x24, 0x5a, 0x22, 0x63, 0x6f, 0x64, 0x65, 0x2e, 0x74, 0x76, 0x6c, 0x2e, 0x66, 0x79, 0x69, 0x2f,
+	0x74, 0x76, 0x69, 0x78, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2d, 0x67, 0x6f, 0x3b, 0x73, 0x74,
+	0x6f, 0x72, 0x65, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+}
+
+var (
+	file_tvix_store_protos_rpc_pathinfo_proto_rawDescOnce sync.Once
+	file_tvix_store_protos_rpc_pathinfo_proto_rawDescData = file_tvix_store_protos_rpc_pathinfo_proto_rawDesc
+)
+
+func file_tvix_store_protos_rpc_pathinfo_proto_rawDescGZIP() []byte {
+	file_tvix_store_protos_rpc_pathinfo_proto_rawDescOnce.Do(func() {
+		file_tvix_store_protos_rpc_pathinfo_proto_rawDescData = protoimpl.X.CompressGZIP(file_tvix_store_protos_rpc_pathinfo_proto_rawDescData)
+	})
+	return file_tvix_store_protos_rpc_pathinfo_proto_rawDescData
+}
+
+var file_tvix_store_protos_rpc_pathinfo_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
+var file_tvix_store_protos_rpc_pathinfo_proto_goTypes = []interface{}{
+	(*GetPathInfoRequest)(nil),   // 0: tvix.store.v1.GetPathInfoRequest
+	(*ListPathInfoRequest)(nil),  // 1: tvix.store.v1.ListPathInfoRequest
+	(*CalculateNARResponse)(nil), // 2: tvix.store.v1.CalculateNARResponse
+	(*PathInfo)(nil),             // 3: tvix.store.v1.PathInfo
+	(*castore_go.Node)(nil),      // 4: tvix.castore.v1.Node
+}
+var file_tvix_store_protos_rpc_pathinfo_proto_depIdxs = []int32{
+	0, // 0: tvix.store.v1.PathInfoService.Get:input_type -> tvix.store.v1.GetPathInfoRequest
+	3, // 1: tvix.store.v1.PathInfoService.Put:input_type -> tvix.store.v1.PathInfo
+	4, // 2: tvix.store.v1.PathInfoService.CalculateNAR:input_type -> tvix.castore.v1.Node
+	1, // 3: tvix.store.v1.PathInfoService.List:input_type -> tvix.store.v1.ListPathInfoRequest
+	3, // 4: tvix.store.v1.PathInfoService.Get:output_type -> tvix.store.v1.PathInfo
+	3, // 5: tvix.store.v1.PathInfoService.Put:output_type -> tvix.store.v1.PathInfo
+	2, // 6: tvix.store.v1.PathInfoService.CalculateNAR:output_type -> tvix.store.v1.CalculateNARResponse
+	3, // 7: tvix.store.v1.PathInfoService.List:output_type -> tvix.store.v1.PathInfo
+	4, // [4:8] is the sub-list for method output_type
+	0, // [0:4] is the sub-list for method input_type
+	0, // [0:0] is the sub-list for extension type_name
+	0, // [0:0] is the sub-list for extension extendee
+	0, // [0:0] is the sub-list for field type_name
+}
+
+func init() { file_tvix_store_protos_rpc_pathinfo_proto_init() }
+func file_tvix_store_protos_rpc_pathinfo_proto_init() {
+	if File_tvix_store_protos_rpc_pathinfo_proto != nil {
+		return
+	}
+	file_tvix_store_protos_pathinfo_proto_init()
+	if !protoimpl.UnsafeEnabled {
+		file_tvix_store_protos_rpc_pathinfo_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*GetPathInfoRequest); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_tvix_store_protos_rpc_pathinfo_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*ListPathInfoRequest); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_tvix_store_protos_rpc_pathinfo_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*CalculateNARResponse); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+	}
+	file_tvix_store_protos_rpc_pathinfo_proto_msgTypes[0].OneofWrappers = []interface{}{
+		(*GetPathInfoRequest_ByOutputHash)(nil),
+	}
+	type x struct{}
+	out := protoimpl.TypeBuilder{
+		File: protoimpl.DescBuilder{
+			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+			RawDescriptor: file_tvix_store_protos_rpc_pathinfo_proto_rawDesc,
+			NumEnums:      0,
+			NumMessages:   3,
+			NumExtensions: 0,
+			NumServices:   1,
+		},
+		GoTypes:           file_tvix_store_protos_rpc_pathinfo_proto_goTypes,
+		DependencyIndexes: file_tvix_store_protos_rpc_pathinfo_proto_depIdxs,
+		MessageInfos:      file_tvix_store_protos_rpc_pathinfo_proto_msgTypes,
+	}.Build()
+	File_tvix_store_protos_rpc_pathinfo_proto = out.File
+	file_tvix_store_protos_rpc_pathinfo_proto_rawDesc = nil
+	file_tvix_store_protos_rpc_pathinfo_proto_goTypes = nil
+	file_tvix_store_protos_rpc_pathinfo_proto_depIdxs = nil
+}
diff --git a/tvix/store-go/rpc_pathinfo_grpc.pb.go b/tvix/store-go/rpc_pathinfo_grpc.pb.go
new file mode 100644
index 0000000000..8d6c0ff841
--- /dev/null
+++ b/tvix/store-go/rpc_pathinfo_grpc.pb.go
@@ -0,0 +1,308 @@
+// SPDX-License-Identifier: MIT
+// Copyright © 2022 The Tvix Authors
+
+// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
+// versions:
+// - protoc-gen-go-grpc v1.3.0
+// - protoc             (unknown)
+// source: tvix/store/protos/rpc_pathinfo.proto
+
+package storev1
+
+import (
+	castore_go "code.tvl.fyi/tvix/castore-go"
+	context "context"
+	grpc "google.golang.org/grpc"
+	codes "google.golang.org/grpc/codes"
+	status "google.golang.org/grpc/status"
+)
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the grpc package it is being compiled against.
+// Requires gRPC-Go v1.32.0 or later.
+const _ = grpc.SupportPackageIsVersion7
+
+const (
+	PathInfoService_Get_FullMethodName          = "/tvix.store.v1.PathInfoService/Get"
+	PathInfoService_Put_FullMethodName          = "/tvix.store.v1.PathInfoService/Put"
+	PathInfoService_CalculateNAR_FullMethodName = "/tvix.store.v1.PathInfoService/CalculateNAR"
+	PathInfoService_List_FullMethodName         = "/tvix.store.v1.PathInfoService/List"
+)
+
+// PathInfoServiceClient is the client API for PathInfoService service.
+//
+// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
+type PathInfoServiceClient interface {
+	// Return a PathInfo message matching the criteria specified in the
+	// GetPathInfoRequest message.
+	Get(ctx context.Context, in *GetPathInfoRequest, opts ...grpc.CallOption) (*PathInfo, error)
+	// Upload a PathInfo object to the remote end. It MUST not return until the
+	// PathInfo object has been written on the the remote end.
+	//
+	// The remote end MAY check if a potential DirectoryNode has already been
+	// uploaded.
+	//
+	// Uploading clients SHOULD obviously not steer other machines to try to
+	// substitute before from the remote end before having finished uploading
+	// PathInfo, Directories and Blobs.
+	// The returned PathInfo object MAY contain additional narinfo signatures, but
+	// is otherwise left untouched.
+	Put(ctx context.Context, in *PathInfo, opts ...grpc.CallOption) (*PathInfo, error)
+	// Calculate the NAR representation of the contents specified by the
+	// root_node. The calculation SHOULD be cached server-side for subsequent
+	// requests.
+	//
+	// All references (to blobs or Directory messages) MUST already exist in the
+	// store.
+	//
+	// The method can be used to produce a Nix fixed-output path, which contains
+	// the (compressed) sha256 of the NAR content representation in the root_node
+	// name (suffixed with the name).
+	//
+	// It can also be used to calculate arbitrary NAR hashes of output paths, in
+	// case a legacy Nix Binary Cache frontend is provided.
+	CalculateNAR(ctx context.Context, in *castore_go.Node, opts ...grpc.CallOption) (*CalculateNARResponse, error)
+	// Return a stream of PathInfo messages matching the criteria specified in
+	// ListPathInfoRequest.
+	List(ctx context.Context, in *ListPathInfoRequest, opts ...grpc.CallOption) (PathInfoService_ListClient, error)
+}
+
+type pathInfoServiceClient struct {
+	cc grpc.ClientConnInterface
+}
+
+func NewPathInfoServiceClient(cc grpc.ClientConnInterface) PathInfoServiceClient {
+	return &pathInfoServiceClient{cc}
+}
+
+func (c *pathInfoServiceClient) Get(ctx context.Context, in *GetPathInfoRequest, opts ...grpc.CallOption) (*PathInfo, error) {
+	out := new(PathInfo)
+	err := c.cc.Invoke(ctx, PathInfoService_Get_FullMethodName, in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+func (c *pathInfoServiceClient) Put(ctx context.Context, in *PathInfo, opts ...grpc.CallOption) (*PathInfo, error) {
+	out := new(PathInfo)
+	err := c.cc.Invoke(ctx, PathInfoService_Put_FullMethodName, in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+func (c *pathInfoServiceClient) CalculateNAR(ctx context.Context, in *castore_go.Node, opts ...grpc.CallOption) (*CalculateNARResponse, error) {
+	out := new(CalculateNARResponse)
+	err := c.cc.Invoke(ctx, PathInfoService_CalculateNAR_FullMethodName, in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+func (c *pathInfoServiceClient) List(ctx context.Context, in *ListPathInfoRequest, opts ...grpc.CallOption) (PathInfoService_ListClient, error) {
+	stream, err := c.cc.NewStream(ctx, &PathInfoService_ServiceDesc.Streams[0], PathInfoService_List_FullMethodName, opts...)
+	if err != nil {
+		return nil, err
+	}
+	x := &pathInfoServiceListClient{stream}
+	if err := x.ClientStream.SendMsg(in); err != nil {
+		return nil, err
+	}
+	if err := x.ClientStream.CloseSend(); err != nil {
+		return nil, err
+	}
+	return x, nil
+}
+
+type PathInfoService_ListClient interface {
+	Recv() (*PathInfo, error)
+	grpc.ClientStream
+}
+
+type pathInfoServiceListClient struct {
+	grpc.ClientStream
+}
+
+func (x *pathInfoServiceListClient) Recv() (*PathInfo, error) {
+	m := new(PathInfo)
+	if err := x.ClientStream.RecvMsg(m); err != nil {
+		return nil, err
+	}
+	return m, nil
+}
+
+// PathInfoServiceServer is the server API for PathInfoService service.
+// All implementations must embed UnimplementedPathInfoServiceServer
+// for forward compatibility
+type PathInfoServiceServer interface {
+	// Return a PathInfo message matching the criteria specified in the
+	// GetPathInfoRequest message.
+	Get(context.Context, *GetPathInfoRequest) (*PathInfo, error)
+	// Upload a PathInfo object to the remote end. It MUST not return until the
+	// PathInfo object has been written on the the remote end.
+	//
+	// The remote end MAY check if a potential DirectoryNode has already been
+	// uploaded.
+	//
+	// Uploading clients SHOULD obviously not steer other machines to try to
+	// substitute before from the remote end before having finished uploading
+	// PathInfo, Directories and Blobs.
+	// The returned PathInfo object MAY contain additional narinfo signatures, but
+	// is otherwise left untouched.
+	Put(context.Context, *PathInfo) (*PathInfo, error)
+	// Calculate the NAR representation of the contents specified by the
+	// root_node. The calculation SHOULD be cached server-side for subsequent
+	// requests.
+	//
+	// All references (to blobs or Directory messages) MUST already exist in the
+	// store.
+	//
+	// The method can be used to produce a Nix fixed-output path, which contains
+	// the (compressed) sha256 of the NAR content representation in the root_node
+	// name (suffixed with the name).
+	//
+	// It can also be used to calculate arbitrary NAR hashes of output paths, in
+	// case a legacy Nix Binary Cache frontend is provided.
+	CalculateNAR(context.Context, *castore_go.Node) (*CalculateNARResponse, error)
+	// Return a stream of PathInfo messages matching the criteria specified in
+	// ListPathInfoRequest.
+	List(*ListPathInfoRequest, PathInfoService_ListServer) error
+	mustEmbedUnimplementedPathInfoServiceServer()
+}
+
+// UnimplementedPathInfoServiceServer must be embedded to have forward compatible implementations.
+type UnimplementedPathInfoServiceServer struct {
+}
+
+func (UnimplementedPathInfoServiceServer) Get(context.Context, *GetPathInfoRequest) (*PathInfo, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method Get not implemented")
+}
+func (UnimplementedPathInfoServiceServer) Put(context.Context, *PathInfo) (*PathInfo, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method Put not implemented")
+}
+func (UnimplementedPathInfoServiceServer) CalculateNAR(context.Context, *castore_go.Node) (*CalculateNARResponse, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method CalculateNAR not implemented")
+}
+func (UnimplementedPathInfoServiceServer) List(*ListPathInfoRequest, PathInfoService_ListServer) error {
+	return status.Errorf(codes.Unimplemented, "method List not implemented")
+}
+func (UnimplementedPathInfoServiceServer) mustEmbedUnimplementedPathInfoServiceServer() {}
+
+// UnsafePathInfoServiceServer may be embedded to opt out of forward compatibility for this service.
+// Use of this interface is not recommended, as added methods to PathInfoServiceServer will
+// result in compilation errors.
+type UnsafePathInfoServiceServer interface {
+	mustEmbedUnimplementedPathInfoServiceServer()
+}
+
+func RegisterPathInfoServiceServer(s grpc.ServiceRegistrar, srv PathInfoServiceServer) {
+	s.RegisterService(&PathInfoService_ServiceDesc, srv)
+}
+
+func _PathInfoService_Get_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(GetPathInfoRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(PathInfoServiceServer).Get(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: PathInfoService_Get_FullMethodName,
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(PathInfoServiceServer).Get(ctx, req.(*GetPathInfoRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+func _PathInfoService_Put_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(PathInfo)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(PathInfoServiceServer).Put(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: PathInfoService_Put_FullMethodName,
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(PathInfoServiceServer).Put(ctx, req.(*PathInfo))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+func _PathInfoService_CalculateNAR_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(castore_go.Node)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(PathInfoServiceServer).CalculateNAR(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: PathInfoService_CalculateNAR_FullMethodName,
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(PathInfoServiceServer).CalculateNAR(ctx, req.(*castore_go.Node))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+func _PathInfoService_List_Handler(srv interface{}, stream grpc.ServerStream) error {
+	m := new(ListPathInfoRequest)
+	if err := stream.RecvMsg(m); err != nil {
+		return err
+	}
+	return srv.(PathInfoServiceServer).List(m, &pathInfoServiceListServer{stream})
+}
+
+type PathInfoService_ListServer interface {
+	Send(*PathInfo) error
+	grpc.ServerStream
+}
+
+type pathInfoServiceListServer struct {
+	grpc.ServerStream
+}
+
+func (x *pathInfoServiceListServer) Send(m *PathInfo) error {
+	return x.ServerStream.SendMsg(m)
+}
+
+// PathInfoService_ServiceDesc is the grpc.ServiceDesc for PathInfoService service.
+// It's only intended for direct use with grpc.RegisterService,
+// and not to be introspected or modified (even as a copy)
+var PathInfoService_ServiceDesc = grpc.ServiceDesc{
+	ServiceName: "tvix.store.v1.PathInfoService",
+	HandlerType: (*PathInfoServiceServer)(nil),
+	Methods: []grpc.MethodDesc{
+		{
+			MethodName: "Get",
+			Handler:    _PathInfoService_Get_Handler,
+		},
+		{
+			MethodName: "Put",
+			Handler:    _PathInfoService_Put_Handler,
+		},
+		{
+			MethodName: "CalculateNAR",
+			Handler:    _PathInfoService_CalculateNAR_Handler,
+		},
+	},
+	Streams: []grpc.StreamDesc{
+		{
+			StreamName:    "List",
+			Handler:       _PathInfoService_List_Handler,
+			ServerStreams: true,
+		},
+	},
+	Metadata: "tvix/store/protos/rpc_pathinfo.proto",
+}
diff --git a/tvix/store-go/testdata/emptydirectory.nar b/tvix/store-go/testdata/emptydirectory.nar
new file mode 100644
index 0000000000..baba558622
--- /dev/null
+++ b/tvix/store-go/testdata/emptydirectory.nar
Binary files differdiff --git a/tvix/store-go/testdata/onebyteregular.nar b/tvix/store-go/testdata/onebyteregular.nar
new file mode 100644
index 0000000000..b8c94932bf
--- /dev/null
+++ b/tvix/store-go/testdata/onebyteregular.nar
Binary files differdiff --git a/tvix/store-go/testdata/symlink.nar b/tvix/store-go/testdata/symlink.nar
new file mode 100644
index 0000000000..7990e4ad5b
--- /dev/null
+++ b/tvix/store-go/testdata/symlink.nar
Binary files differdiff --git a/tvix/store/Cargo.toml b/tvix/store/Cargo.toml
new file mode 100644
index 0000000000..f82cdef300
--- /dev/null
+++ b/tvix/store/Cargo.toml
@@ -0,0 +1,79 @@
+[package]
+name = "tvix-store"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+anyhow = "1.0.68"
+async-compression = { version = "0.4.9", features = ["tokio", "bzip2", "gzip", "xz", "zstd"]}
+async-stream = "0.3.5"
+blake3 = { version = "1.3.1", features = ["rayon", "std"] }
+bstr = "1.6.0"
+bytes = "1.4.0"
+clap = { version = "4.0", features = ["derive", "env"] }
+count-write = "0.1.0"
+data-encoding = "2.3.3"
+futures = "0.3.30"
+lazy_static = "1.4.0"
+nix-compat = { path = "../nix-compat", features = ["async"] }
+pin-project-lite = "0.2.13"
+prost = "0.12.1"
+opentelemetry = { version = "0.21.0", optional = true}
+opentelemetry-otlp = { version = "0.14.0", optional = true }
+opentelemetry_sdk = { version = "0.21.0", features = ["rt-tokio"], optional = true}
+serde = { version = "1.0.197", features = [ "derive" ] }
+serde_json = "1.0"
+serde_with = "3.7.0"
+serde_qs = "0.12.0"
+sha2 = "0.10.6"
+sled = { version = "0.34.7" }
+thiserror = "1.0.38"
+tokio = { version = "1.32.0", features = ["fs", "macros", "net", "rt", "rt-multi-thread", "signal"] }
+tokio-listener = { version = "0.3.2", features = [ "tonic011" ] }
+tokio-stream = { version = "0.1.14", features = ["fs"] }
+tokio-util = { version = "0.7.9", features = ["io", "io-util", "compat"] }
+tonic = { version = "0.11.0", features = ["tls", "tls-roots"] }
+tower = "0.4.13"
+tracing = "0.1.37"
+tracing-opentelemetry = "0.22.0"
+tracing-subscriber = { version = "0.3.16", features = ["env-filter", "json"] }
+tvix-castore = { path = "../castore" }
+url = "2.4.0"
+walkdir = "2.4.0"
+reqwest = { version = "0.11.22", features = ["rustls-tls-native-roots", "stream"], default-features = false }
+
+[dependencies.tonic-reflection]
+optional = true
+version = "0.11.0"
+
+[dependencies.bigtable_rs]
+optional = true
+# https://github.com/liufuyang/bigtable_rs/pull/72
+git = "https://github.com/flokli/bigtable_rs"
+rev = "0af404741dfc40eb9fa99cf4d4140a09c5c20df7"
+
+[build-dependencies]
+prost-build = "0.12.1"
+tonic-build = "0.11.0"
+
+[dev-dependencies]
+async-process = "2.1.0"
+rstest = "0.19.0"
+rstest_reuse = "0.6.0"
+tempfile = "3.3.0"
+tokio-retry = "0.3.0"
+
+[features]
+default = ["cloud", "fuse", "otlp", "tonic-reflection"]
+cloud = [
+  "dep:bigtable_rs",
+  "tvix-castore/cloud"
+]
+fuse = ["tvix-castore/fuse"]
+otlp = ["dep:opentelemetry", "dep:opentelemetry-otlp", "dep:opentelemetry_sdk"]
+tonic-reflection = ["dep:tonic-reflection", "tvix-castore/tonic-reflection"]
+virtiofs = ["tvix-castore/virtiofs"]
+# Whether to run the integration tests.
+# Requires the following packages in $PATH:
+# cbtemulator, google-cloud-bigtable-tool
+integration = []
diff --git a/tvix/store/README.md b/tvix/store/README.md
new file mode 100644
index 0000000000..a9d29671d8
--- /dev/null
+++ b/tvix/store/README.md
@@ -0,0 +1,63 @@
+# //tvix/store
+
+This contains the code hosting the tvix-store.
+
+For the local store, Nix realizes files on the filesystem in `/nix/store` (and
+maintains some metadata in a SQLite database). For "remote stores", it
+communicates this metadata in NAR (Nix ARchive) and NARInfo format.
+
+Compared to the Nix model, `tvix-store` stores data on a much more granular
+level than that, which provides more deduplication possibilities, and more
+granular copying.
+
+However, enough information is preserved to still be able to render NAR and
+NARInfo when needed.
+
+## More Information
+The store consists out of two different gRPC services, `tvix.castore.v1` for
+the low-level content-addressed bits, and `tvix.store.v1` for the Nix and
+`StorePath`-specific bits.
+
+Check the `protos/` subfolder both here and in `castore` for the definition of
+the exact RPC methods and messages.
+
+## Interacting with the GRPC service manually
+The shell environment in `//tvix` provides `evans`, which is an interactive
+REPL-based gPRC client.
+
+You can use it to connect to a `tvix-store` and call the various RPC methods.
+
+```shell
+$ cargo run -- daemon &
+$ evans --host localhost --port 8000 -r repl
+  ______
+ |  ____|
+ | |__    __   __   __ _   _ __    ___
+ |  __|   \ \ / /  / _. | | '_ \  / __|
+ | |____   \ V /  | (_| | | | | | \__ \
+ |______|   \_/    \__,_| |_| |_| |___/
+
+ more expressive universal gRPC client
+
+
+localhost:8000> package tvix.castore.v1
+tvix.castore.v1@localhost:8000> service BlobService
+
+tvix.castore.v1.BlobService@localhost:8000> call Put --bytes-from-file
+data (TYPE_BYTES) => /run/current-system/system
+{
+  "digest": "KOM3/IHEx7YfInAnlJpAElYezq0Sxn9fRz7xuClwNfA="
+}
+
+tvix.castore.v1.BlobService@localhost:8000> call Read --bytes-as-base64
+digest (TYPE_BYTES) => KOM3/IHEx7YfInAnlJpAElYezq0Sxn9fRz7xuClwNfA=
+{
+  "data": "eDg2XzY0LWxpbnV4"
+}
+
+$ echo eDg2XzY0LWxpbnV4 | base64 -d
+x86_64-linux
+```
+
+Thanks to `tvix-store` providing gRPC Server Reflection (with `reflection`
+feature), you don't need to point `evans` to the `.proto` files.
diff --git a/tvix/store/build.rs b/tvix/store/build.rs
new file mode 100644
index 0000000000..809fa29578
--- /dev/null
+++ b/tvix/store/build.rs
@@ -0,0 +1,38 @@
+use std::io::Result;
+
+fn main() -> Result<()> {
+    #[allow(unused_mut)]
+    let mut builder = tonic_build::configure();
+
+    #[cfg(feature = "tonic-reflection")]
+    {
+        let out_dir = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap());
+        let descriptor_path = out_dir.join("tvix.store.v1.bin");
+
+        builder = builder.file_descriptor_set_path(descriptor_path);
+    };
+
+    // https://github.com/hyperium/tonic/issues/908
+    let mut config = prost_build::Config::new();
+    config.bytes(["."]);
+    config.extern_path(".tvix.castore.v1", "::tvix_castore::proto");
+
+    builder
+        .build_server(true)
+        .build_client(true)
+        .emit_rerun_if_changed(false)
+        .compile_with_config(
+            config,
+            &[
+                "tvix/store/protos/pathinfo.proto",
+                "tvix/store/protos/rpc_pathinfo.proto",
+            ],
+            // If we are in running `cargo build` manually, using `../..` works fine,
+            // but in case we run inside a nix build, we need to instead point PROTO_ROOT
+            // to a sparseTree containing that structure.
+            &[match std::env::var_os("PROTO_ROOT") {
+                Some(proto_root) => proto_root.to_str().unwrap().to_owned(),
+                None => "../..".to_string(),
+            }],
+        )
+}
diff --git a/tvix/store/default.nix b/tvix/store/default.nix
new file mode 100644
index 0000000000..ad47994f24
--- /dev/null
+++ b/tvix/store/default.nix
@@ -0,0 +1,52 @@
+{ depot, pkgs, ... }:
+
+let
+  mkImportCheck = p: expectedPath: {
+    label = ":nix :import ${p} with tvix-store import";
+    needsOutput = true;
+    command = pkgs.writeShellScript "tvix-import-check" ''
+      export BLOB_SERVICE_ADDR=memory://
+      export DIRECTORY_SERVICE_ADDR=memory://
+      export PATH_INFO_SERVICE_ADDR=memory://
+      TVIX_STORE_OUTPUT=$(result/bin/tvix-store import ${p})
+      EXPECTED='${/* the vebatim expected Tvix output: */expectedPath}'
+
+      echo "tvix-store output: ''${TVIX_STORE_OUTPUT}"
+      if [ "$TVIX_STORE_OUTPUT" != "$EXPECTED" ]; then
+        echo "Correct would have been ''${EXPECTED}"
+        exit 1
+      fi
+
+      echo "Output was correct."
+    '';
+  };
+in
+
+(depot.tvix.crates.workspaceMembers.tvix-store.build.override {
+  runTests = true;
+  testPreRun = ''
+    export SSL_CERT_FILE=${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt
+  '';
+
+  # enable some optional features.
+  features = [ "default" "cloud" ]
+    # virtiofs feature currently fails to build on Darwin.
+    ++ pkgs.lib.optional pkgs.stdenv.isLinux "virtiofs";
+}).overrideAttrs (_: {
+  meta.ci.targets = [ "integration-tests" ];
+  meta.ci.extraSteps = {
+    import-docs = (mkImportCheck "tvix/store/docs" ./docs);
+  };
+  passthru.integration-tests = depot.tvix.crates.workspaceMembers.tvix-store.build.override {
+    runTests = true;
+    testPreRun = ''
+      export SSL_CERT_FILE=${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt
+      export PATH="$PATH:${pkgs.lib.makeBinPath [pkgs.cbtemulator pkgs.google-cloud-bigtable-tool]}"
+    '';
+
+    # enable some optional features.
+    features = [ "default" "cloud" "integration" ]
+      # virtiofs feature currently fails to build on Darwin.
+      ++ pkgs.lib.optional pkgs.stdenv.isLinux "virtiofs";
+  };
+})
diff --git a/tvix/store/docs/api.md b/tvix/store/docs/api.md
new file mode 100644
index 0000000000..c1dacc89a5
--- /dev/null
+++ b/tvix/store/docs/api.md
@@ -0,0 +1,288 @@
+tvix-[ca]store API
+==============
+
+This document outlines the design of the API exposed by tvix-castore and tvix-
+store, as well as other implementations of this store protocol.
+
+This document is meant to be read side-by-side with
+[castore.md](../../tvix-castore/docs/castore.md) which describes the data model
+in more detail.
+
+The store API has four main consumers:
+
+1. The evaluator (or more correctly, the CLI/coordinator, in the Tvix
+   case) communicates with the store to:
+
+   * Upload files and directories (e.g. from `builtins.path`, or `src = ./path`
+     Nix expressions).
+   * Read files from the store where necessary (e.g. when `nixpkgs` is
+     located in the store, or for IFD).
+
+2. The builder communicates with the store to:
+
+   * Upload files and directories after a build, to persist build artifacts in
+     the store.
+
+3. Tvix clients (such as users that have Tvix installed, or, depending
+   on perspective, builder environments) expect the store to
+   "materialise" on disk to provide a directory layout with store
+   paths.
+
+4. Stores may communicate with other stores, to substitute already built store
+   paths, i.e. a store acts as a binary cache for other stores.
+
+The store API attempts to reuse parts of its API between these three
+consumers by making similarities explicit in the protocol. This leads
+to a protocol that is slightly more complex than a simple "file
+upload/download"-system, but at significantly greater efficiency, both in terms
+of deduplication opportunities as well as granularity.
+
+## The Store model
+
+Contents inside a tvix-store can be grouped into three different message types:
+
+ * Blobs
+ * Directories
+ * PathInfo (see further down)
+
+(check `castore.md` for more detailed field descriptions)
+
+### Blobs
+A blob object contains the literal file contents of regular (or executable)
+files.
+
+### Directory
+A directory object describes the direct children of a directory.
+
+It contains:
+ - name of child (regular or executable) files, and their [blake3][blake3] hash.
+ - name of child symlinks, and their target (as string)
+ - name of child directories, and their [blake3][blake3] hash (forming a Merkle DAG)
+
+### Content-addressed Store Model
+For example, lets consider a directory layout like this, with some
+imaginary hashes of file contents:
+
+```
+.
+├── file-1.txt        hash: 5891b5b522d5df086d0ff0b110fb
+└── nested
+    └── file-2.txt    hash: abc6fd595fc079d3114d4b71a4d8
+```
+
+A hash for the *directory* `nested` can be created by creating the `Directory`
+object:
+
+```json
+{
+  "directories": [],
+  "files": [{
+    "name": "file-2.txt",
+    "digest": "abc6fd595fc079d3114d4b71a4d8",
+    "size": 123,
+  }],
+  "symlink": [],
+}
+```
+
+And then hashing a serialised form of that data structure. We use the blake3
+hash of the canonical protobuf representation. Let's assume the hash was
+`ff0029485729bcde993720749232`.
+
+To create the directory object one layer up, we now refer to our `nested`
+directory object in `directories`, and to `file-1.txt` in `files`:
+
+```json
+{
+  "directories": [{
+    "name": "nested",
+    "digest": "ff0029485729bcde993720749232",
+    "size": 1,
+  }],
+  "files": [{
+    "name": "file-1.txt",
+    "digest": "5891b5b522d5df086d0ff0b110fb",
+    "size": 124,
+  }]
+}
+```
+
+This Merkle DAG of Directory objects, and flat store of blobs can be used to
+describe any file/directory/symlink inside a store path. Due to its content-
+addressed nature, it'll automatically deduplicate (re-)used (sub)directories,
+and allow substitution from any (untrusted) source.
+
+The thing that's now only missing is the metadata to map/"mount" from the
+content-addressed world to a physical path.
+
+### PathInfo
+As most paths in the Nix store currently are input-addressed [^input-addressed],
+and the `tvix-castore` data model is also not intrinsically using NAR hashes,
+we need something mapping from an input-addressed "output path hash" (or a Nix-
+specific content-addressed path) to the contents in the `tvix-castore` world.
+
+That's what `PathInfo` provides. It embeds the root node (Directory, File or
+Symlink) at a given store path.
+
+The root nodes' `name` field is populated with the (base)name inside
+`/nix/store`, so `xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-pname-1.2.3`.
+
+The `PathInfo` message also stores references to other store paths, and some
+more NARInfo-specific metadata (signatures, narhash, narsize).
+
+
+## API overview
+
+There's three different services:
+
+### BlobService
+`BlobService` can be used to store and retrieve blobs of data, used to host
+regular file contents.
+
+It is content-addressed, using [blake3][blake3]
+as a hashing function.
+
+As blake3 is a tree hash, there's an opportunity to do
+[verified streaming][bao] of parts of the file,
+which doesn't need to trust any more information than the root hash itself.
+Future extensions of the `BlobService` protocol will enable this.
+
+### DirectoryService
+`DirectoryService` allows lookups (and uploads) of `Directory` messages, and
+whole reference graphs of them.
+
+
+### PathInfoService
+The PathInfo service provides lookups from a store path hash to a `PathInfo`
+message.
+
+## Example flows
+
+Below there are some common use cases of tvix-store, and how the different
+services are used.
+
+###  Upload files and directories
+This is needed for `builtins.path` or `src = ./path` in Nix expressions (A), as
+well as for uploading build artifacts to a store (B).
+
+The path specified needs to be (recursively, BFS-style) traversed.
+ * All file contents need to be hashed with blake3, and submitted to the
+   *BlobService* if not already present.
+   A reference to them needs to be added to the parent Directory object that's
+   constructed.
+ * All symlinks need to be added to the parent directory they reside in.
+ * Whenever a Directory has been fully traversed, it needs to be uploaded to
+   the *DirectoryService* and a reference to it needs to be added to the parent
+   Directory object.
+
+Most of the hashing / directory traversal/uploading can happen in parallel,
+as long as Directory objects only refer to Directory objects and Blobs that
+have already been uploaded.
+
+When reaching the root, a `PathInfo` object needs to be constructed.
+
+ * In the case of content-addressed paths (A), the name of the root node is
+   based on the NAR representation of the contents.
+   It might make sense to be able to offload the NAR calculation to the store,
+   which can cache it.
+ * In the case of build artifacts (B), the output path is input-addressed and
+   known upfront.
+
+Contrary to Nix, this has the advantage of not having to upload a lot of things
+to the store that didn't change.
+
+### Reading files from the store from the evaluator
+This is the case when `nixpkgs` is located in the store, or IFD in general.
+
+The store client asks the `PathInfoService` for the `PathInfo` of the output
+path in the request, and looks at the root node.
+
+If something other than the root of the store path is requested, like for
+example `maintainers/maintainer-list.nix`, the root_node Directory is inspected
+and potentially a chain of `Directory` objects requested from
+*DirectoryService*. [^n+1query].
+
+When the desired file is reached, the *BlobService* can be used to read the
+contents of this file, and return it back to the evaluator.
+
+FUTUREWORK: define how importing from symlinks should/does work.
+
+Contrary to Nix, this has the advantage of not having to copy all of the
+contents of a store path to the evaluating machine, but really only fetching
+the files the evaluator currently cares about.
+
+### Materializing store paths on disk
+This is useful for people running a Tvix-only system, or running builds on a
+"Tvix remote builder" in its own mount namespace.
+
+In a system with Nix installed, we can't simply manually "extract" things to
+`/nix/store`, as Nix assumes to own all writes to this location.
+In these use cases, we're probably better off exposing a tvix-store as a local
+binary cache (that's what `//tvix/nar-bridge` does).
+
+Assuming we are in an environment where we control `/nix/store` exclusively, a
+"realize to disk" would either "extract" things from the `tvix-store` to a
+filesystem, or expose a `FUSE`/`virtio-fs` filesystem.
+
+The latter is already implemented, and particularly interesting for (remote)
+build workloads, as build inputs can be realized on-demand, which saves copying
+around a lot of never- accessed files.
+
+In both cases, the API interactions are similar.
+ * The *PathInfoService* is asked for the `PathInfo` of the requested store path.
+ * If everything should be "extracted", the *DirectoryService* is asked for all
+   `Directory` objects in the closure, the file structure is created, all Blobs
+   are downloaded and placed in their corresponding location and all symlinks
+   are created accordingly.
+ * If this is a FUSE filesystem, we can decide to only request a subset,
+   similar to the "Reading files from the store from the evaluator" use case,
+   even though it might make sense to keep all Directory objects around.
+   (See the caveat in "Trust model" though!)
+
+### Stores communicating with other stores
+The gRPC API exposed by the tvix-store allows composing multiple stores, and
+implementing some caching strategies, that store clients don't need to be aware
+of.
+
+ * For example, a caching strategy could have a fast local tvix-store, that's
+   asked first and filled with data from a slower remote tvix-store.
+
+ * Multiple stores could be asked for the same data, and whatever store returns
+   the right data first wins.
+
+
+## Trust model / Distribution
+As already described above, the only non-content-addressed service is the
+`PathInfo` service.
+
+This means, all other messages (such as `Blob` and `Directory` messages) can be
+substituted from many different, untrusted sources/mirrors, which will make
+plugging in additional substitution strategies like IPFS, local network
+neighbors super simple. That's also why it's living in the `tvix-castore` crate.
+
+As for `PathInfo`, we don't specify an additional signature mechanism yet, but
+carry the NAR-based signatures from Nix along.
+
+This means, if we don't trust a remote `PathInfo` object, we currently need to
+"stream" the NAR representation to validate these signatures.
+
+However, the slow part is downloading of NAR files, and considering we have
+more granularity available, we might only need to download some small blobs,
+rather than a whole NAR file.
+
+A future signature mechanism, that is only signing (parts of) the `PathInfo`
+message, which only points to content-addressed data will enable verified
+partial access into a store path, opening up opportunities for lazy filesystem
+access etc.
+
+
+
+[blake3]: https://github.com/BLAKE3-team/BLAKE3
+[bao]: https://github.com/oconnor663/bao
+[^input-addressed]: Nix hashes the A-Term representation of a .drv, after doing
+                    some replacements on refered Input Derivations to calculate
+                    output paths.
+[^n+1query]: This would expose an N+1 query problem. However it's not a problem
+             in practice, as there's usually always a "local" caching store in
+             the loop, and *DirectoryService* supports a recursive lookup for
+             all `Directory` children of a `Directory`
diff --git a/tvix/store/protos/LICENSE b/tvix/store/protos/LICENSE
new file mode 100644
index 0000000000..2034ada6fd
--- /dev/null
+++ b/tvix/store/protos/LICENSE
@@ -0,0 +1,21 @@
+Copyright © The Tvix Authors
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+“Software”), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
diff --git a/tvix/store/protos/default.nix b/tvix/store/protos/default.nix
new file mode 100644
index 0000000000..56345d9338
--- /dev/null
+++ b/tvix/store/protos/default.nix
@@ -0,0 +1,55 @@
+{ depot, pkgs, ... }:
+let
+  protos = depot.nix.sparseTree {
+    name = "store-protos";
+    root = depot.path.origSrc;
+    paths = [
+      # We need to include castore.proto (only), as it's referred.
+      ../../castore/protos/castore.proto
+      ./pathinfo.proto
+      ./rpc_pathinfo.proto
+      ../../../buf.yaml
+      ../../../buf.gen.yaml
+    ];
+  };
+in
+depot.nix.readTree.drvTargets {
+  inherit protos;
+
+  # Lints and ensures formatting of the proto files.
+  check = pkgs.stdenv.mkDerivation {
+    name = "proto-check";
+    src = protos;
+
+    nativeBuildInputs = [
+      pkgs.buf
+    ];
+
+    buildPhase = ''
+      export HOME=$TMPDIR
+      buf lint
+      buf format -d --exit-code
+      touch $out
+    '';
+  };
+
+  # Produces the golang bindings.
+  go-bindings = pkgs.stdenv.mkDerivation {
+    name = "go-bindings";
+    src = protos;
+
+    nativeBuildInputs = [
+      pkgs.buf
+      pkgs.protoc-gen-go
+      pkgs.protoc-gen-go-grpc
+    ];
+
+    buildPhase = ''
+      export HOME=$TMPDIR
+      buf generate
+
+      mkdir -p $out
+      cp tvix/store/protos/*.pb.go $out/
+    '';
+  };
+}
diff --git a/tvix/store/protos/pathinfo.proto b/tvix/store/protos/pathinfo.proto
new file mode 100644
index 0000000000..b03e7e938e
--- /dev/null
+++ b/tvix/store/protos/pathinfo.proto
@@ -0,0 +1,128 @@
+// SPDX-License-Identifier: MIT
+// Copyright © 2022 The Tvix Authors
+syntax = "proto3";
+
+package tvix.store.v1;
+
+import "tvix/castore/protos/castore.proto";
+
+option go_package = "code.tvl.fyi/tvix/store-go;storev1";
+
+// PathInfo shows information about a Nix Store Path.
+// That's a single element inside /nix/store.
+message PathInfo {
+  // The path can be a directory, file or symlink.
+  tvix.castore.v1.Node node = 1;
+
+  // List of references (output path hashes)
+  // This really is the raw *bytes*, after decoding nixbase32, and not a
+  // base32-encoded string.
+  repeated bytes references = 2;
+
+  // see below.
+  NARInfo narinfo = 3;
+}
+
+// Represents a path in the Nix store (a direct child of STORE_DIR).
+// It is commonly formatted by a nixbase32-encoding the digest, and
+// concatenating the name, separated by a `-`.
+message StorePath {
+  // The string after digest and `-`.
+  string name = 1;
+
+  // The digest (20 bytes).
+  bytes digest = 2;
+}
+
+// Nix C++ uses NAR (Nix Archive) as a format to transfer store paths,
+// and stores metadata and signatures in NARInfo files.
+// Store all these attributes in a separate message.
+//
+// This is useful to render .narinfo files to clients, or to preserve/validate
+// these signatures.
+// As verifying these signatures requires the whole NAR file to be synthesized,
+// moving to another signature scheme is desired.
+// Even then, it still makes sense to hold this data, for old clients.
+message NARInfo {
+  // This represents a (parsed) signature line in a .narinfo file.
+  message Signature {
+    string name = 1;
+    bytes data = 2;
+  }
+
+  // This size of the NAR file, in bytes.
+  uint64 nar_size = 1;
+
+  // The sha256 of the NAR file representation.
+  bytes nar_sha256 = 2;
+
+  // The signatures in a .narinfo file.
+  repeated Signature signatures = 3;
+
+  // A list of references. To validate .narinfo signatures, a fingerprint needs
+  // to be constructed.
+  // This fingerprint doesn't just contain the hashes of the output paths of all
+  // references (like PathInfo.references), but their whole (base)names, so we
+  // need to keep them somewhere.
+  repeated string reference_names = 4;
+
+  // The StorePath of the .drv file producing this output.
+  // The .drv suffix is omitted in its `name` field.
+  StorePath deriver = 5;
+
+  // The CA field in the .narinfo.
+  // Its textual representations seen in the wild are one of the following:
+  //  - `fixed:r:sha256:1gcky5hlf5vqfzpyhihydmm54grhc94mcs8w7xr8613qsqb1v2j6`
+  //    fixed-output derivations using "recursive" `outputHashMode`.
+  //  - `fixed:sha256:19xqkh72crbcba7flwxyi3n293vav6d7qkzkh2v4zfyi4iia8vj8
+  //    fixed-output derivations using "flat" `outputHashMode`
+  //  - `text:sha256:19xqkh72crbcba7flwxyi3n293vav6d7qkzkh2v4zfyi4iia8vj8`
+  //    Text hashing, used for uploaded .drv files and outputs produced by
+  //    builtins.toFile.
+  //
+  // Semantically, they can be split into the following components:
+  //  - "content address prefix". Currently, "fixed" and "text" are supported.
+  //  - "hash mode". Currently, "flat" and "recursive" are supported.
+  //  - "hash type". The underlying hash function used.
+  //    Currently, sha1, md5, sha256, sha512.
+  //  - "digest". The digest itself.
+  //
+  // There are some restrictions on the possible combinations.
+  // For example, `text` and `fixed:recursive` always imply sha256.
+  //
+  // We use an enum to encode the possible combinations, and optimize for the
+  // common case, `fixed:recursive`, identified as `NAR_SHA256`.
+  CA ca = 6;
+
+  message CA {
+    enum Hash {
+      // produced when uploading fixed-output store paths using NAR-based
+      // hashing (`outputHashMode = "recursive"`).
+      NAR_SHA256 = 0;
+      NAR_SHA1 = 1;
+      NAR_SHA512 = 2;
+      NAR_MD5 = 3;
+
+      // Produced when uploading .drv files or outputs produced by
+      // builtins.toFile.
+      // Produces equivalent digests as FLAT_SHA256, but is a separate
+      // hashing type in Nix, affecting output path calculation.
+      TEXT_SHA256 = 4;
+
+      // Produced when using fixed-output derivations with
+      // `outputHashMode = "flat"`.
+      FLAT_SHA1 = 5;
+      FLAT_MD5 = 6;
+      FLAT_SHA256 = 7;
+      FLAT_SHA512 = 8;
+
+      // TODO: what happens in Rust if we introduce a new enum kind here?
+    }
+
+    // The hashing type used.
+    Hash type = 1;
+
+    // The digest, in raw bytes.
+    bytes digest = 2;
+  }
+}
diff --git a/tvix/store/protos/rpc_pathinfo.proto b/tvix/store/protos/rpc_pathinfo.proto
new file mode 100644
index 0000000000..c1c91658ad
--- /dev/null
+++ b/tvix/store/protos/rpc_pathinfo.proto
@@ -0,0 +1,76 @@
+// SPDX-License-Identifier: MIT
+// Copyright © 2022 The Tvix Authors
+syntax = "proto3";
+
+package tvix.store.v1;
+
+import "tvix/castore/protos/castore.proto";
+import "tvix/store/protos/pathinfo.proto";
+
+option go_package = "code.tvl.fyi/tvix/store-go;storev1";
+
+service PathInfoService {
+  // Return a PathInfo message matching the criteria specified in the
+  // GetPathInfoRequest message.
+  rpc Get(GetPathInfoRequest) returns (PathInfo);
+
+  // Upload a PathInfo object to the remote end. It MUST not return until the
+  // PathInfo object has been written on the the remote end.
+  //
+  // The remote end MAY check if a potential DirectoryNode has already been
+  // uploaded.
+  //
+  // Uploading clients SHOULD obviously not steer other machines to try to
+  // substitute before from the remote end before having finished uploading
+  // PathInfo, Directories and Blobs.
+  // The returned PathInfo object MAY contain additional narinfo signatures, but
+  // is otherwise left untouched.
+  rpc Put(PathInfo) returns (PathInfo);
+
+  // Calculate the NAR representation of the contents specified by the
+  // root_node. The calculation SHOULD be cached server-side for subsequent
+  // requests.
+  //
+  // All references (to blobs or Directory messages) MUST already exist in the
+  // store.
+  //
+  // The method can be used to produce a Nix fixed-output path, which contains
+  // the (compressed) sha256 of the NAR content representation in the root_node
+  // name (suffixed with the name).
+  //
+  // It can also be used to calculate arbitrary NAR hashes of output paths, in
+  // case a legacy Nix Binary Cache frontend is provided.
+  rpc CalculateNAR(tvix.castore.v1.Node) returns (CalculateNARResponse);
+
+  // Return a stream of PathInfo messages matching the criteria specified in
+  // ListPathInfoRequest.
+  rpc List(ListPathInfoRequest) returns (stream PathInfo);
+}
+
+// The parameters that can be used to lookup a (single) PathInfo object.
+// Currently, only a lookup by output hash is supported.
+message GetPathInfoRequest {
+  oneof by_what {
+    // The output hash of a nix path (20 bytes).
+    // This is the nixbase32-decoded portion of a Nix output path, so to substitute
+    // /nix/store/xm35nga2g20mz5sm5l6n8v3bdm86yj83-cowsay-3.04
+    // this field would contain nixbase32dec("xm35nga2g20mz5sm5l6n8v3bdm86yj83").
+    bytes by_output_hash = 1;
+  }
+}
+
+// The parameters that can be used to lookup (multiple) PathInfo objects.
+// Currently no filtering is possible, all objects are returned.
+message ListPathInfoRequest {}
+
+// CalculateNARResponse is the response returned by the CalculateNAR request.
+//
+// It contains the size of the NAR representation (in bytes), and the sha56
+// digest.
+message CalculateNARResponse {
+  // This size of the NAR file, in bytes.
+  uint64 nar_size = 1;
+
+  // The sha256 of the NAR file representation.
+  bytes nar_sha256 = 2;
+}
diff --git a/tvix/store/src/bin/tvix-store.rs b/tvix/store/src/bin/tvix-store.rs
new file mode 100644
index 0000000000..fa30501e78
--- /dev/null
+++ b/tvix/store/src/bin/tvix-store.rs
@@ -0,0 +1,563 @@
+use clap::Parser;
+use clap::Subcommand;
+
+use futures::future::try_join_all;
+use futures::StreamExt;
+use futures::TryStreamExt;
+use nix_compat::path_info::ExportedPathInfo;
+use serde::Deserialize;
+use serde::Serialize;
+use std::path::PathBuf;
+use std::sync::Arc;
+use tokio_listener::Listener;
+use tokio_listener::SystemOptions;
+use tokio_listener::UserOptions;
+use tonic::transport::Server;
+use tracing::info;
+use tracing::Level;
+use tracing_subscriber::EnvFilter;
+use tracing_subscriber::Layer;
+use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
+use tvix_castore::import::fs::ingest_path;
+use tvix_store::proto::NarInfo;
+use tvix_store::proto::PathInfo;
+
+use tvix_castore::proto::blob_service_server::BlobServiceServer;
+use tvix_castore::proto::directory_service_server::DirectoryServiceServer;
+use tvix_castore::proto::GRPCBlobServiceWrapper;
+use tvix_castore::proto::GRPCDirectoryServiceWrapper;
+use tvix_store::pathinfoservice::PathInfoService;
+use tvix_store::proto::path_info_service_server::PathInfoServiceServer;
+use tvix_store::proto::GRPCPathInfoServiceWrapper;
+
+#[cfg(any(feature = "fuse", feature = "virtiofs"))]
+use tvix_store::pathinfoservice::make_fs;
+
+#[cfg(feature = "fuse")]
+use tvix_castore::fs::fuse::FuseDaemon;
+
+#[cfg(feature = "otlp")]
+use opentelemetry::KeyValue;
+#[cfg(feature = "otlp")]
+use opentelemetry_sdk::{
+    resource::{ResourceDetector, SdkProvidedResourceDetector},
+    trace::BatchConfig,
+    Resource,
+};
+
+#[cfg(feature = "virtiofs")]
+use tvix_castore::fs::virtiofs::start_virtiofs_daemon;
+
+#[cfg(feature = "tonic-reflection")]
+use tvix_castore::proto::FILE_DESCRIPTOR_SET as CASTORE_FILE_DESCRIPTOR_SET;
+#[cfg(feature = "tonic-reflection")]
+use tvix_store::proto::FILE_DESCRIPTOR_SET;
+
+#[derive(Parser)]
+#[command(author, version, about, long_about = None)]
+struct Cli {
+    /// Whether to configure OTLP. Set --otlp=false to disable.
+    #[arg(long, default_missing_value = "true", default_value = "true", num_args(0..=1), require_equals(true), action(clap::ArgAction::Set))]
+    otlp: bool,
+
+    /// A global log level to use when printing logs.
+    /// It's also possible to set `RUST_LOG` according to
+    /// `tracing_subscriber::filter::EnvFilter`, which will always have
+    /// priority.
+    #[arg(long)]
+    log_level: Option<Level>,
+
+    #[command(subcommand)]
+    command: Commands,
+}
+
+#[derive(Subcommand)]
+enum Commands {
+    /// Runs the tvix-store daemon.
+    Daemon {
+        #[arg(long, short = 'l')]
+        listen_address: Option<String>,
+
+        #[arg(
+            long,
+            env,
+            default_value = "objectstore+file:///var/lib/tvix-store/blobs.object_store"
+        )]
+        blob_service_addr: String,
+
+        #[arg(
+            long,
+            env,
+            default_value = "sled:///var/lib/tvix-store/directories.sled"
+        )]
+        directory_service_addr: String,
+
+        #[arg(long, env, default_value = "sled:///var/lib/tvix-store/pathinfo.sled")]
+        path_info_service_addr: String,
+    },
+    /// Imports a list of paths into the store, print the store path for each of them.
+    Import {
+        #[clap(value_name = "PATH")]
+        paths: Vec<PathBuf>,
+
+        #[arg(long, env, default_value = "grpc+http://[::1]:8000")]
+        blob_service_addr: String,
+
+        #[arg(long, env, default_value = "grpc+http://[::1]:8000")]
+        directory_service_addr: String,
+
+        #[arg(long, env, default_value = "grpc+http://[::1]:8000")]
+        path_info_service_addr: String,
+    },
+
+    /// Copies a list of store paths on the system into tvix-store.
+    Copy {
+        #[arg(long, env, default_value = "grpc+http://[::1]:8000")]
+        blob_service_addr: String,
+
+        #[arg(long, env, default_value = "grpc+http://[::1]:8000")]
+        directory_service_addr: String,
+
+        #[arg(long, env, default_value = "grpc+http://[::1]:8000")]
+        path_info_service_addr: String,
+
+        /// A path pointing to a JSON file produced by the Nix
+        /// `__structuredAttrs` containing reference graph information provided
+        /// by the `exportReferencesGraph` feature.
+        ///
+        /// This can be used to invoke tvix-store inside a Nix derivation
+        /// copying to a Tvix store (or outside, if the JSON file is copied
+        /// out).
+        ///
+        /// Currently limited to the `closure` key inside that JSON file.
+        #[arg(value_name = "NIX_ATTRS_JSON_FILE", env = "NIX_ATTRS_JSON_FILE")]
+        reference_graph_path: PathBuf,
+    },
+    /// Mounts a tvix-store at the given mountpoint
+    #[cfg(feature = "fuse")]
+    Mount {
+        #[clap(value_name = "PATH")]
+        dest: PathBuf,
+
+        #[arg(long, env, default_value = "grpc+http://[::1]:8000")]
+        blob_service_addr: String,
+
+        #[arg(long, env, default_value = "grpc+http://[::1]:8000")]
+        directory_service_addr: String,
+
+        #[arg(long, env, default_value = "grpc+http://[::1]:8000")]
+        path_info_service_addr: String,
+
+        /// Number of FUSE threads to spawn.
+        #[arg(long, env, default_value_t = default_threads())]
+        threads: usize,
+
+        #[arg(long, env, default_value_t = false)]
+        /// Whether to configure the mountpoint with allow_other.
+        /// Requires /etc/fuse.conf to contain the `user_allow_other`
+        /// option, configured via `programs.fuse.userAllowOther` on NixOS.
+        allow_other: bool,
+
+        /// Whether to list elements at the root of the mount point.
+        /// This is useful if your PathInfoService doesn't provide an
+        /// (exhaustive) listing.
+        #[clap(long, short, action)]
+        list_root: bool,
+
+        #[arg(long, default_value_t = true)]
+        /// Whether to expose blob and directory digests as extended attributes.
+        show_xattr: bool,
+    },
+    /// Starts a tvix-store virtiofs daemon at the given socket path.
+    #[cfg(feature = "virtiofs")]
+    #[command(name = "virtiofs")]
+    VirtioFs {
+        #[clap(value_name = "PATH")]
+        socket: PathBuf,
+
+        #[arg(long, env, default_value = "grpc+http://[::1]:8000")]
+        blob_service_addr: String,
+
+        #[arg(long, env, default_value = "grpc+http://[::1]:8000")]
+        directory_service_addr: String,
+
+        #[arg(long, env, default_value = "grpc+http://[::1]:8000")]
+        path_info_service_addr: String,
+
+        /// Whether to list elements at the root of the mount point.
+        /// This is useful if your PathInfoService doesn't provide an
+        /// (exhaustive) listing.
+        #[clap(long, short, action)]
+        list_root: bool,
+
+        #[arg(long, default_value_t = true)]
+        /// Whether to expose blob and directory digests as extended attributes.
+        show_xattr: bool,
+    },
+}
+
+#[cfg(all(feature = "fuse", not(target_os = "macos")))]
+fn default_threads() -> usize {
+    std::thread::available_parallelism()
+        .map(|threads| threads.into())
+        .unwrap_or(4)
+}
+// On MacFUSE only a single channel will receive ENODEV when the file system is
+// unmounted and so all the other channels will block forever.
+// See https://github.com/osxfuse/osxfuse/issues/974
+#[cfg(all(feature = "fuse", target_os = "macos"))]
+fn default_threads() -> usize {
+    1
+}
+
+#[tokio::main]
+async fn main() -> Result<(), Box<dyn std::error::Error>> {
+    let cli = Cli::parse();
+
+    // configure log settings
+    let level = cli.log_level.unwrap_or(Level::INFO);
+
+    // Set up the tracing subscriber.
+    let subscriber = tracing_subscriber::registry().with(
+        tracing_subscriber::fmt::Layer::new()
+            .with_writer(std::io::stderr)
+            .compact()
+            .with_filter(
+                EnvFilter::builder()
+                    .with_default_directive(level.into())
+                    .from_env()
+                    .expect("invalid RUST_LOG"),
+            ),
+    );
+
+    // Add the otlp layer (when otlp is enabled, and it's not disabled in the CLI)
+    // then init the registry.
+    // If the feature is feature-flagged out, just init without adding the layer.
+    // It's necessary to do this separately, as every with() call chains the
+    // layer into the type of the registry.
+    #[cfg(feature = "otlp")]
+    {
+        let subscriber = if cli.otlp {
+            let tracer = opentelemetry_otlp::new_pipeline()
+                .tracing()
+                .with_exporter(opentelemetry_otlp::new_exporter().tonic())
+                .with_batch_config(BatchConfig::default())
+                .with_trace_config(opentelemetry_sdk::trace::config().with_resource({
+                    // use SdkProvidedResourceDetector.detect to detect resources,
+                    // but replace the default service name with our default.
+                    // https://github.com/open-telemetry/opentelemetry-rust/issues/1298
+                    let resources =
+                        SdkProvidedResourceDetector.detect(std::time::Duration::from_secs(0));
+                    // SdkProvidedResourceDetector currently always sets
+                    // `service.name`, but we don't like its default.
+                    if resources.get("service.name".into()).unwrap() == "unknown_service".into() {
+                        resources.merge(&Resource::new([KeyValue::new(
+                            "service.name",
+                            "tvix.store",
+                        )]))
+                    } else {
+                        resources
+                    }
+                }))
+                .install_batch(opentelemetry_sdk::runtime::Tokio)?;
+
+            // Create a tracing layer with the configured tracer
+            let layer = tracing_opentelemetry::layer().with_tracer(tracer);
+
+            subscriber.with(Some(layer))
+        } else {
+            subscriber.with(None)
+        };
+
+        subscriber.try_init()?;
+    }
+
+    // Init the registry (when otlp is not enabled)
+    #[cfg(not(feature = "otlp"))]
+    {
+        subscriber.try_init()?;
+    }
+
+    match cli.command {
+        Commands::Daemon {
+            listen_address,
+            blob_service_addr,
+            directory_service_addr,
+            path_info_service_addr,
+        } => {
+            // initialize stores
+            let (blob_service, directory_service, path_info_service) =
+                tvix_store::utils::construct_services(
+                    blob_service_addr,
+                    directory_service_addr,
+                    path_info_service_addr,
+                )
+                .await?;
+
+            let listen_address = listen_address
+                .unwrap_or_else(|| "[::]:8000".to_string())
+                .parse()
+                .unwrap();
+
+            let mut server = Server::builder();
+
+            #[allow(unused_mut)]
+            let mut router = server
+                .add_service(BlobServiceServer::new(GRPCBlobServiceWrapper::new(
+                    blob_service,
+                )))
+                .add_service(DirectoryServiceServer::new(
+                    GRPCDirectoryServiceWrapper::new(directory_service),
+                ))
+                .add_service(PathInfoServiceServer::new(GRPCPathInfoServiceWrapper::new(
+                    Arc::from(path_info_service),
+                )));
+
+            #[cfg(feature = "tonic-reflection")]
+            {
+                let reflection_svc = tonic_reflection::server::Builder::configure()
+                    .register_encoded_file_descriptor_set(CASTORE_FILE_DESCRIPTOR_SET)
+                    .register_encoded_file_descriptor_set(FILE_DESCRIPTOR_SET)
+                    .build()?;
+                router = router.add_service(reflection_svc);
+            }
+
+            info!(listen_address=%listen_address, "starting daemon");
+
+            let listener = Listener::bind(
+                &listen_address,
+                &SystemOptions::default(),
+                &UserOptions::default(),
+            )
+            .await?;
+
+            router.serve_with_incoming(listener).await?;
+        }
+        Commands::Import {
+            paths,
+            blob_service_addr,
+            directory_service_addr,
+            path_info_service_addr,
+        } => {
+            // FUTUREWORK: allow flat for single files?
+            let (blob_service, directory_service, path_info_service) =
+                tvix_store::utils::construct_services(
+                    blob_service_addr,
+                    directory_service_addr,
+                    path_info_service_addr,
+                )
+                .await?;
+
+            // Arc the PathInfoService, as we clone it .
+            let path_info_service: Arc<dyn PathInfoService> = path_info_service.into();
+
+            let tasks = paths
+                .into_iter()
+                .map(|path| {
+                    tokio::task::spawn({
+                        let blob_service = blob_service.clone();
+                        let directory_service = directory_service.clone();
+                        let path_info_service = path_info_service.clone();
+
+                        async move {
+                            if let Ok(name) = tvix_store::import::path_to_name(&path) {
+                                let resp = tvix_store::import::import_path_as_nar_ca(
+                                    &path,
+                                    name,
+                                    blob_service,
+                                    directory_service,
+                                    path_info_service,
+                                )
+                                .await;
+                                if let Ok(output_path) = resp {
+                                    // If the import was successful, print the path to stdout.
+                                    println!("{}", output_path.to_absolute_path());
+                                }
+                            }
+                        }
+                    })
+                })
+                .collect::<Vec<_>>();
+
+            try_join_all(tasks).await?;
+        }
+        Commands::Copy {
+            blob_service_addr,
+            directory_service_addr,
+            path_info_service_addr,
+            reference_graph_path,
+        } => {
+            let (blob_service, directory_service, path_info_service) =
+                tvix_store::utils::construct_services(
+                    blob_service_addr,
+                    directory_service_addr,
+                    path_info_service_addr,
+                )
+                .await?;
+
+            // Parse the file at reference_graph_path.
+            let reference_graph_json = tokio::fs::read(&reference_graph_path).await?;
+
+            #[derive(Deserialize, Serialize)]
+            struct ReferenceGraph<'a> {
+                #[serde(borrow)]
+                closure: Vec<ExportedPathInfo<'a>>,
+            }
+
+            let reference_graph: ReferenceGraph<'_> =
+                serde_json::from_slice(reference_graph_json.as_slice())?;
+
+            // Arc the PathInfoService, as we clone it .
+            let path_info_service: Arc<dyn PathInfoService> = path_info_service.into();
+
+            // From our reference graph, lookup all pathinfos that might exist.
+            let elems: Vec<_> = futures::stream::iter(reference_graph.closure)
+                .map(|elem| {
+                    let path_info_service = path_info_service.clone();
+                    async move {
+                        path_info_service
+                            .get(*elem.path.digest())
+                            .await
+                            .map(|resp| (elem, resp))
+                    }
+                })
+                .buffer_unordered(50)
+                // Filter out all that are already uploaded.
+                // TODO: check if there's a better combinator for this
+                .try_filter_map(|(elem, path_info)| {
+                    std::future::ready(if path_info.is_none() {
+                        Ok(Some(elem))
+                    } else {
+                        Ok(None)
+                    })
+                })
+                .try_collect()
+                .await?;
+
+            // Run ingest_path on all of them.
+            let uploads: Vec<_> = futures::stream::iter(elems)
+                .map(|elem| {
+                    // Map to a future returning the root node, alongside with the closure info.
+                    let blob_service = blob_service.clone();
+                    let directory_service = directory_service.clone();
+                    async move {
+                        // Ingest the given path.
+
+                        ingest_path(
+                            blob_service,
+                            directory_service,
+                            PathBuf::from(elem.path.to_absolute_path()),
+                        )
+                        .await
+                        .map(|root_node| (elem, root_node))
+                    }
+                })
+                .buffer_unordered(10)
+                .try_collect()
+                .await?;
+
+            // Insert them into the PathInfoService.
+            // FUTUREWORK: do this properly respecting the reference graph.
+            for (elem, root_node) in uploads {
+                // Create and upload a PathInfo pointing to the root_node,
+                // annotated with information we have from the reference graph.
+                let path_info = PathInfo {
+                    node: Some(tvix_castore::proto::Node {
+                        node: Some(root_node),
+                    }),
+                    references: Vec::from_iter(
+                        elem.references.iter().map(|e| e.digest().to_vec().into()),
+                    ),
+                    narinfo: Some(NarInfo {
+                        nar_size: elem.nar_size,
+                        nar_sha256: elem.nar_sha256.to_vec().into(),
+                        signatures: vec![],
+                        reference_names: Vec::from_iter(
+                            elem.references.iter().map(|e| e.to_string()),
+                        ),
+                        deriver: None,
+                        ca: None,
+                    }),
+                };
+
+                path_info_service.put(path_info).await?;
+            }
+        }
+        #[cfg(feature = "fuse")]
+        Commands::Mount {
+            dest,
+            blob_service_addr,
+            directory_service_addr,
+            path_info_service_addr,
+            list_root,
+            threads,
+            allow_other,
+            show_xattr,
+        } => {
+            let (blob_service, directory_service, path_info_service) =
+                tvix_store::utils::construct_services(
+                    blob_service_addr,
+                    directory_service_addr,
+                    path_info_service_addr,
+                )
+                .await?;
+
+            let mut fuse_daemon = tokio::task::spawn_blocking(move || {
+                let fs = make_fs(
+                    blob_service,
+                    directory_service,
+                    Arc::from(path_info_service),
+                    list_root,
+                    show_xattr,
+                );
+                info!(mount_path=?dest, "mounting");
+
+                FuseDaemon::new(fs, &dest, threads, allow_other)
+            })
+            .await??;
+
+            // grab a handle to unmount the file system, and register a signal
+            // handler.
+            tokio::spawn(async move {
+                tokio::signal::ctrl_c().await.unwrap();
+                info!("interrupt received, unmounting…");
+                tokio::task::spawn_blocking(move || fuse_daemon.unmount()).await??;
+                info!("unmount occured, terminating…");
+                Ok::<_, std::io::Error>(())
+            })
+            .await??;
+        }
+        #[cfg(feature = "virtiofs")]
+        Commands::VirtioFs {
+            socket,
+            blob_service_addr,
+            directory_service_addr,
+            path_info_service_addr,
+            list_root,
+            show_xattr,
+        } => {
+            let (blob_service, directory_service, path_info_service) =
+                tvix_store::utils::construct_services(
+                    blob_service_addr,
+                    directory_service_addr,
+                    path_info_service_addr,
+                )
+                .await?;
+
+            tokio::task::spawn_blocking(move || {
+                let fs = make_fs(
+                    blob_service,
+                    directory_service,
+                    Arc::from(path_info_service),
+                    list_root,
+                    show_xattr,
+                );
+                info!(socket_path=?socket, "starting virtiofs-daemon");
+
+                start_virtiofs_daemon(fs, socket)
+            })
+            .await??;
+        }
+    };
+    Ok(())
+}
diff --git a/tvix/store/src/import.rs b/tvix/store/src/import.rs
new file mode 100644
index 0000000000..2331fd77ea
--- /dev/null
+++ b/tvix/store/src/import.rs
@@ -0,0 +1,180 @@
+use std::path::Path;
+use tracing::{debug, instrument};
+use tvix_castore::{
+    blobservice::BlobService, directoryservice::DirectoryService, import::fs::ingest_path,
+    proto::node::Node, B3Digest,
+};
+
+use nix_compat::{
+    nixhash::{CAHash, NixHash},
+    store_path::{self, StorePath},
+};
+
+use crate::{
+    pathinfoservice::PathInfoService,
+    proto::{nar_info, NarInfo, PathInfo},
+};
+
+impl From<CAHash> for nar_info::Ca {
+    fn from(value: CAHash) -> Self {
+        let hash_type: nar_info::ca::Hash = (&value).into();
+        let digest: bytes::Bytes = value.hash().to_string().into();
+        nar_info::Ca {
+            r#type: hash_type.into(),
+            digest,
+        }
+    }
+}
+
+pub fn log_node(node: &Node, path: &Path) {
+    match node {
+        Node::Directory(directory_node) => {
+            debug!(
+                path = ?path,
+                name = ?directory_node.name,
+                digest = %B3Digest::try_from(directory_node.digest.clone()).unwrap(),
+                "import successful",
+            )
+        }
+        Node::File(file_node) => {
+            debug!(
+                path = ?path,
+                name = ?file_node.name,
+                digest = %B3Digest::try_from(file_node.digest.clone()).unwrap(),
+                "import successful"
+            )
+        }
+        Node::Symlink(symlink_node) => {
+            debug!(
+                path = ?path,
+                name = ?symlink_node.name,
+                target = ?symlink_node.target,
+                "import successful"
+            )
+        }
+    }
+}
+
+/// Transform a path into its base name and returns an [`std::io::Error`] if it is `..` or if the
+/// basename is not valid unicode.
+#[inline]
+pub fn path_to_name(path: &Path) -> std::io::Result<&str> {
+    path.file_name()
+        .and_then(|file_name| file_name.to_str())
+        .ok_or_else(|| {
+            std::io::Error::new(
+                std::io::ErrorKind::InvalidInput,
+                "path must not be .. and the basename valid unicode",
+            )
+        })
+}
+
+/// Takes the NAR size, SHA-256 of the NAR representation, the root node and optionally
+/// a CA hash information.
+///
+/// Returns the path information object for a NAR-style object.
+///
+/// This [`PathInfo`] can be further filled for signatures, deriver or verified for the expected
+/// hashes.
+#[inline]
+pub fn derive_nar_ca_path_info(
+    nar_size: u64,
+    nar_sha256: [u8; 32],
+    ca: Option<CAHash>,
+    root_node: Node,
+) -> PathInfo {
+    // assemble the [crate::proto::PathInfo] object.
+    PathInfo {
+        node: Some(tvix_castore::proto::Node {
+            node: Some(root_node),
+        }),
+        // There's no reference scanning on path contents ingested like this.
+        references: vec![],
+        narinfo: Some(NarInfo {
+            nar_size,
+            nar_sha256: nar_sha256.to_vec().into(),
+            signatures: vec![],
+            reference_names: vec![],
+            deriver: None,
+            ca: ca.map(|ca_hash| ca_hash.into()),
+        }),
+    }
+}
+
+/// Ingest the given path `path` and register the resulting output path in the
+/// [`PathInfoService`] as a recursive fixed output NAR.
+#[instrument(skip_all, fields(store_name=name, path=?path), err)]
+pub async fn import_path_as_nar_ca<BS, DS, PS, P>(
+    path: P,
+    name: &str,
+    blob_service: BS,
+    directory_service: DS,
+    path_info_service: PS,
+) -> Result<StorePath, std::io::Error>
+where
+    P: AsRef<Path> + std::fmt::Debug,
+    BS: BlobService + Clone,
+    DS: DirectoryService,
+    PS: AsRef<dyn PathInfoService>,
+{
+    let root_node = ingest_path(blob_service, directory_service, path.as_ref())
+        .await
+        .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
+
+    // Ask the PathInfoService for the NAR size and sha256
+    let (nar_size, nar_sha256) = path_info_service.as_ref().calculate_nar(&root_node).await?;
+
+    // Calculate the output path. This might still fail, as some names are illegal.
+    // FUTUREWORK: express the `name` at the type level to be valid and move the conversion
+    // at the caller level.
+    let output_path = store_path::build_nar_based_store_path(&nar_sha256, name).map_err(|_| {
+        std::io::Error::new(
+            std::io::ErrorKind::InvalidData,
+            format!("invalid name: {}", name),
+        )
+    })?;
+
+    // assemble a new root_node with a name that is derived from the nar hash.
+    let root_node = root_node.rename(output_path.to_string().into_bytes().into());
+    log_node(&root_node, path.as_ref());
+
+    let path_info = derive_nar_ca_path_info(
+        nar_size,
+        nar_sha256,
+        Some(CAHash::Nar(NixHash::Sha256(nar_sha256))),
+        root_node,
+    );
+
+    // This new [`PathInfo`] that we get back from there might contain additional signatures or
+    // information set by the service itself. In this function, we silently swallow it because
+    // callers doesn't really need it.
+    let _path_info = path_info_service.as_ref().put(path_info).await?;
+
+    Ok(output_path.to_owned())
+}
+
+#[cfg(test)]
+mod tests {
+    use std::{ffi::OsStr, path::PathBuf};
+
+    use crate::import::path_to_name;
+    use rstest::rstest;
+
+    #[rstest]
+    #[case::simple_path("a/b/c", "c")]
+    #[case::simple_path_containing_dotdot("a/b/../c", "c")]
+    #[case::path_containing_multiple_dotdot("a/b/../c/d/../e", "e")]
+
+    fn test_path_to_name(#[case] path: &str, #[case] expected_name: &str) {
+        let path: PathBuf = path.into();
+        assert_eq!(path_to_name(&path).expect("must succeed"), expected_name);
+    }
+
+    #[rstest]
+    #[case::path_ending_in_dotdot(b"a/b/..")]
+    #[case::non_unicode_path(b"\xf8\xa1\xa1\xa1\xa1")]
+    fn test_invalid_path_to_name(#[case] invalid_path: &[u8]) {
+        let path: PathBuf = unsafe { OsStr::from_encoded_bytes_unchecked(invalid_path) }.into();
+        path_to_name(&path).expect_err("must fail");
+    }
+}
diff --git a/tvix/store/src/lib.rs b/tvix/store/src/lib.rs
new file mode 100644
index 0000000000..8c32aaf885
--- /dev/null
+++ b/tvix/store/src/lib.rs
@@ -0,0 +1,14 @@
+pub mod import;
+pub mod nar;
+pub mod pathinfoservice;
+pub mod proto;
+pub mod utils;
+
+#[cfg(test)]
+mod tests;
+
+// That's what the rstest_reuse README asks us do, and fails about being unable
+// to find rstest_reuse in crate root.
+#[cfg(test)]
+#[allow(clippy::single_component_path_imports)]
+use rstest_reuse;
diff --git a/tvix/store/src/nar/import.rs b/tvix/store/src/nar/import.rs
new file mode 100644
index 0000000000..cc62c1a4e9
--- /dev/null
+++ b/tvix/store/src/nar/import.rs
@@ -0,0 +1,228 @@
+use nix_compat::nar::reader::r#async as nar_reader;
+use tokio::{io::AsyncBufRead, sync::mpsc, try_join};
+use tvix_castore::{
+    blobservice::BlobService,
+    directoryservice::DirectoryService,
+    import::{ingest_entries, IngestionEntry, IngestionError},
+    proto::{node::Node, NamedNode},
+    PathBuf,
+};
+
+/// Ingests the contents from a [AsyncRead] providing NAR into the tvix store,
+/// interacting with a [BlobService] and [DirectoryService].
+/// It returns the castore root node or an error.
+pub async fn ingest_nar<R, BS, DS>(
+    blob_service: BS,
+    directory_service: DS,
+    r: &mut R,
+) -> Result<Node, IngestionError<Error>>
+where
+    R: AsyncBufRead + Unpin + Send,
+    BS: BlobService + Clone,
+    DS: DirectoryService,
+{
+    // open the NAR for reading.
+    // The NAR reader emits nodes in DFS preorder.
+    let root_node = nar_reader::open(r).await.map_err(Error::IO)?;
+
+    let (tx, rx) = mpsc::channel(1);
+    let rx = tokio_stream::wrappers::ReceiverStream::new(rx);
+
+    let produce = async move {
+        let res = produce_nar_inner(
+            blob_service,
+            root_node,
+            "root".parse().unwrap(), // HACK: the root node sent to ingest_entries may not be ROOT.
+            tx.clone(),
+        )
+        .await;
+
+        tx.send(res)
+            .await
+            .map_err(|e| Error::IO(std::io::Error::new(std::io::ErrorKind::BrokenPipe, e)))?;
+
+        Ok(())
+    };
+
+    let consume = ingest_entries(directory_service, rx);
+
+    let (_, node) = try_join!(produce, consume)?;
+
+    // remove the fake "root" name again
+    debug_assert_eq!(&node.get_name(), b"root");
+    Ok(node.rename("".into()))
+}
+
+async fn produce_nar_inner<BS>(
+    blob_service: BS,
+    node: nar_reader::Node<'_, '_>,
+    path: PathBuf,
+    tx: mpsc::Sender<Result<IngestionEntry, Error>>,
+) -> Result<IngestionEntry, Error>
+where
+    BS: BlobService + Clone,
+{
+    Ok(match node {
+        nar_reader::Node::Symlink { target } => IngestionEntry::Symlink { path, target },
+        nar_reader::Node::File {
+            executable,
+            mut reader,
+        } => {
+            let (digest, size) = {
+                let mut blob_writer = blob_service.open_write().await;
+                // TODO(edef): fix the AsyncBufRead implementation of nix_compat::wire::BytesReader
+                let size = tokio::io::copy(&mut reader, &mut blob_writer).await?;
+
+                (blob_writer.close().await?, size)
+            };
+
+            IngestionEntry::Regular {
+                path,
+                size,
+                executable,
+                digest,
+            }
+        }
+        nar_reader::Node::Directory(mut dir_reader) => {
+            while let Some(entry) = dir_reader.next().await? {
+                let mut path = path.clone();
+
+                // valid NAR names are valid castore names
+                path.try_push(&entry.name)
+                    .expect("Tvix bug: failed to join name");
+
+                let entry = Box::pin(produce_nar_inner(
+                    blob_service.clone(),
+                    entry.node,
+                    path,
+                    tx.clone(),
+                ))
+                .await?;
+
+                tx.send(Ok(entry)).await.map_err(|e| {
+                    Error::IO(std::io::Error::new(std::io::ErrorKind::BrokenPipe, e))
+                })?;
+            }
+
+            IngestionEntry::Dir { path }
+        }
+    })
+}
+
+#[derive(Debug, thiserror::Error)]
+pub enum Error {
+    #[error(transparent)]
+    IO(#[from] std::io::Error),
+}
+
+#[cfg(test)]
+mod test {
+    use crate::nar::ingest_nar;
+    use std::io::Cursor;
+    use std::sync::Arc;
+
+    use rstest::*;
+    use tokio_stream::StreamExt;
+    use tvix_castore::blobservice::BlobService;
+    use tvix_castore::directoryservice::DirectoryService;
+    use tvix_castore::fixtures::{
+        DIRECTORY_COMPLICATED, DIRECTORY_WITH_KEEP, EMPTY_BLOB_DIGEST, HELLOWORLD_BLOB_CONTENTS,
+        HELLOWORLD_BLOB_DIGEST,
+    };
+    use tvix_castore::proto as castorepb;
+
+    use crate::tests::fixtures::{
+        blob_service, directory_service, NAR_CONTENTS_COMPLICATED, NAR_CONTENTS_HELLOWORLD,
+        NAR_CONTENTS_SYMLINK,
+    };
+
+    #[rstest]
+    #[tokio::test]
+    async fn single_symlink(
+        blob_service: Arc<dyn BlobService>,
+        directory_service: Arc<dyn DirectoryService>,
+    ) {
+        let root_node = ingest_nar(
+            blob_service,
+            directory_service,
+            &mut Cursor::new(&NAR_CONTENTS_SYMLINK.clone()),
+        )
+        .await
+        .expect("must parse");
+
+        assert_eq!(
+            castorepb::node::Node::Symlink(castorepb::SymlinkNode {
+                name: "".into(), // name must be empty
+                target: "/nix/store/somewhereelse".into(),
+            }),
+            root_node
+        );
+    }
+
+    #[rstest]
+    #[tokio::test]
+    async fn single_file(
+        blob_service: Arc<dyn BlobService>,
+        directory_service: Arc<dyn DirectoryService>,
+    ) {
+        let root_node = ingest_nar(
+            blob_service.clone(),
+            directory_service,
+            &mut Cursor::new(&NAR_CONTENTS_HELLOWORLD.clone()),
+        )
+        .await
+        .expect("must parse");
+
+        assert_eq!(
+            castorepb::node::Node::File(castorepb::FileNode {
+                name: "".into(), // name must be empty
+                digest: HELLOWORLD_BLOB_DIGEST.clone().into(),
+                size: HELLOWORLD_BLOB_CONTENTS.len() as u64,
+                executable: false,
+            }),
+            root_node
+        );
+
+        // blobservice must contain the blob
+        assert!(blob_service.has(&HELLOWORLD_BLOB_DIGEST).await.unwrap());
+    }
+
+    #[rstest]
+    #[tokio::test]
+    async fn complicated(
+        blob_service: Arc<dyn BlobService>,
+        directory_service: Arc<dyn DirectoryService>,
+    ) {
+        let root_node = ingest_nar(
+            blob_service.clone(),
+            directory_service.clone(),
+            &mut Cursor::new(&NAR_CONTENTS_COMPLICATED.clone()),
+        )
+        .await
+        .expect("must parse");
+
+        assert_eq!(
+            castorepb::node::Node::Directory(castorepb::DirectoryNode {
+                name: "".into(), // name must be empty
+                digest: DIRECTORY_COMPLICATED.digest().into(),
+                size: DIRECTORY_COMPLICATED.size(),
+            }),
+            root_node,
+        );
+
+        // blobservice must contain the blob
+        assert!(blob_service.has(&EMPTY_BLOB_DIGEST).await.unwrap());
+
+        // directoryservice must contain the directories, at least with get_recursive.
+        let resp: Result<Vec<castorepb::Directory>, _> = directory_service
+            .get_recursive(&DIRECTORY_COMPLICATED.digest())
+            .collect()
+            .await;
+
+        let directories = resp.unwrap();
+
+        assert_eq!(2, directories.len());
+        assert_eq!(DIRECTORY_COMPLICATED.clone(), directories[0]);
+        assert_eq!(DIRECTORY_WITH_KEEP.clone(), directories[1]);
+    }
+}
diff --git a/tvix/store/src/nar/mod.rs b/tvix/store/src/nar/mod.rs
new file mode 100644
index 0000000000..4d5101f9d5
--- /dev/null
+++ b/tvix/store/src/nar/mod.rs
@@ -0,0 +1,26 @@
+use tvix_castore::B3Digest;
+
+mod import;
+mod renderer;
+pub use import::ingest_nar;
+pub use renderer::calculate_size_and_sha256;
+pub use renderer::write_nar;
+
+/// Errors that can encounter while rendering NARs.
+#[derive(Debug, thiserror::Error)]
+pub enum RenderError {
+    #[error("failure talking to a backing store client: {0}")]
+    StoreError(#[source] std::io::Error),
+
+    #[error("unable to find directory {}, referred from {:?}", .0, .1)]
+    DirectoryNotFound(B3Digest, bytes::Bytes),
+
+    #[error("unable to find blob {}, referred from {:?}", .0, .1)]
+    BlobNotFound(B3Digest, bytes::Bytes),
+
+    #[error("unexpected size in metadata for blob {}, referred from {:?} returned, expected {}, got {}", .0, .1, .2, .3)]
+    UnexpectedBlobMeta(B3Digest, bytes::Bytes, u32, u32),
+
+    #[error("failure using the NAR writer: {0}")]
+    NARWriterError(std::io::Error),
+}
diff --git a/tvix/store/src/nar/renderer.rs b/tvix/store/src/nar/renderer.rs
new file mode 100644
index 0000000000..3b39f700bd
--- /dev/null
+++ b/tvix/store/src/nar/renderer.rs
@@ -0,0 +1,187 @@
+use crate::utils::AsyncIoBridge;
+
+use super::RenderError;
+use count_write::CountWrite;
+use nix_compat::nar::writer::r#async as nar_writer;
+use sha2::{Digest, Sha256};
+use tokio::io::{self, AsyncWrite, BufReader};
+use tvix_castore::{
+    blobservice::BlobService,
+    directoryservice::DirectoryService,
+    proto::{self as castorepb, NamedNode},
+};
+
+/// Invoke [write_nar], and return the size and sha256 digest of the produced
+/// NAR output.
+pub async fn calculate_size_and_sha256<BS, DS>(
+    root_node: &castorepb::node::Node,
+    blob_service: BS,
+    directory_service: DS,
+) -> Result<(u64, [u8; 32]), RenderError>
+where
+    BS: BlobService + Send,
+    DS: DirectoryService + Send,
+{
+    let mut h = Sha256::new();
+    let mut cw = CountWrite::from(&mut h);
+
+    write_nar(
+        // The hasher doesn't speak async. It doesn't
+        // actually do any I/O, so it's fine to wrap.
+        AsyncIoBridge(&mut cw),
+        root_node,
+        blob_service,
+        directory_service,
+    )
+    .await?;
+
+    Ok((cw.count(), h.finalize().into()))
+}
+
+/// Accepts a [castorepb::node::Node] pointing to the root of a (store) path,
+/// and uses the passed blob_service and directory_service to perform the
+/// necessary lookups as it traverses the structure.
+/// The contents in NAR serialization are writen to the passed [AsyncWrite].
+pub async fn write_nar<W, BS, DS>(
+    mut w: W,
+    proto_root_node: &castorepb::node::Node,
+    blob_service: BS,
+    directory_service: DS,
+) -> Result<(), RenderError>
+where
+    W: AsyncWrite + Unpin + Send,
+    BS: BlobService + Send,
+    DS: DirectoryService + Send,
+{
+    // Initialize NAR writer
+    let nar_root_node = nar_writer::open(&mut w)
+        .await
+        .map_err(RenderError::NARWriterError)?;
+
+    walk_node(
+        nar_root_node,
+        proto_root_node,
+        blob_service,
+        directory_service,
+    )
+    .await?;
+
+    Ok(())
+}
+
+/// Process an intermediate node in the structure.
+/// This consumes the node.
+async fn walk_node<BS, DS>(
+    nar_node: nar_writer::Node<'_, '_>,
+    proto_node: &castorepb::node::Node,
+    blob_service: BS,
+    directory_service: DS,
+) -> Result<(BS, DS), RenderError>
+where
+    BS: BlobService + Send,
+    DS: DirectoryService + Send,
+{
+    match proto_node {
+        castorepb::node::Node::Symlink(proto_symlink_node) => {
+            nar_node
+                .symlink(&proto_symlink_node.target)
+                .await
+                .map_err(RenderError::NARWriterError)?;
+        }
+        castorepb::node::Node::File(proto_file_node) => {
+            let digest_len = proto_file_node.digest.len();
+            let digest = proto_file_node.digest.clone().try_into().map_err(|_| {
+                RenderError::StoreError(io::Error::new(
+                    io::ErrorKind::Other,
+                    format!("invalid digest len {} in file node", digest_len),
+                ))
+            })?;
+
+            let mut blob_reader = match blob_service
+                .open_read(&digest)
+                .await
+                .map_err(RenderError::StoreError)?
+            {
+                Some(blob_reader) => Ok(BufReader::new(blob_reader)),
+                None => Err(RenderError::NARWriterError(io::Error::new(
+                    io::ErrorKind::NotFound,
+                    format!("blob with digest {} not found", &digest),
+                ))),
+            }?;
+
+            nar_node
+                .file(
+                    proto_file_node.executable,
+                    proto_file_node.size,
+                    &mut blob_reader,
+                )
+                .await
+                .map_err(RenderError::NARWriterError)?;
+        }
+        castorepb::node::Node::Directory(proto_directory_node) => {
+            let digest_len = proto_directory_node.digest.len();
+            let digest = proto_directory_node
+                .digest
+                .clone()
+                .try_into()
+                .map_err(|_| {
+                    RenderError::StoreError(io::Error::new(
+                        io::ErrorKind::InvalidData,
+                        format!("invalid digest len {} in directory node", digest_len),
+                    ))
+                })?;
+
+            // look it up with the directory service
+            match directory_service
+                .get(&digest)
+                .await
+                .map_err(|e| RenderError::StoreError(e.into()))?
+            {
+                // if it's None, that's an error!
+                None => Err(RenderError::DirectoryNotFound(
+                    digest,
+                    proto_directory_node.name.clone(),
+                ))?,
+                Some(proto_directory) => {
+                    // start a directory node
+                    let mut nar_node_directory = nar_node
+                        .directory()
+                        .await
+                        .map_err(RenderError::NARWriterError)?;
+
+                    // We put blob_service, directory_service back here whenever we come up from
+                    // the recursion.
+                    let mut blob_service = blob_service;
+                    let mut directory_service = directory_service;
+
+                    // for each node in the directory, create a new entry with its name,
+                    // and then recurse on that entry.
+                    for proto_node in proto_directory.nodes() {
+                        let child_node = nar_node_directory
+                            .entry(proto_node.get_name())
+                            .await
+                            .map_err(RenderError::NARWriterError)?;
+
+                        (blob_service, directory_service) = Box::pin(walk_node(
+                            child_node,
+                            &proto_node,
+                            blob_service,
+                            directory_service,
+                        ))
+                        .await?;
+                    }
+
+                    // close the directory
+                    nar_node_directory
+                        .close()
+                        .await
+                        .map_err(RenderError::NARWriterError)?;
+
+                    return Ok((blob_service, directory_service));
+                }
+            }
+        }
+    }
+
+    Ok((blob_service, directory_service))
+}
diff --git a/tvix/store/src/pathinfoservice/bigtable.rs b/tvix/store/src/pathinfoservice/bigtable.rs
new file mode 100644
index 0000000000..6fb52abbfd
--- /dev/null
+++ b/tvix/store/src/pathinfoservice/bigtable.rs
@@ -0,0 +1,401 @@
+use super::PathInfoService;
+use crate::proto;
+use crate::proto::PathInfo;
+use async_stream::try_stream;
+use bigtable_rs::{bigtable, google::bigtable::v2 as bigtable_v2};
+use bytes::Bytes;
+use data_encoding::HEXLOWER;
+use futures::stream::BoxStream;
+use prost::Message;
+use serde::{Deserialize, Serialize};
+use serde_with::{serde_as, DurationSeconds};
+use tonic::async_trait;
+use tracing::trace;
+use tvix_castore::proto as castorepb;
+use tvix_castore::Error;
+
+/// There should not be more than 10 MiB in a single cell.
+/// https://cloud.google.com/bigtable/docs/schema-design#cells
+const CELL_SIZE_LIMIT: u64 = 10 * 1024 * 1024;
+
+/// Provides a [DirectoryService] implementation using
+/// [Bigtable](https://cloud.google.com/bigtable/docs/)
+/// as an underlying K/V store.
+///
+/// # Data format
+/// We use Bigtable as a plain K/V store.
+/// The row key is the digest of the store path, in hexlower.
+/// Inside the row, we currently have a single column/cell, again using the
+/// hexlower store path digest.
+/// Its value is the PathInfo message, serialized in canonical protobuf.
+/// We currently only populate this column.
+///
+/// Listing is ranging over all rows, and calculate_nar is returning a
+/// "unimplemented" error.
+#[derive(Clone)]
+pub struct BigtablePathInfoService {
+    client: bigtable::BigTable,
+    params: BigtableParameters,
+
+    #[cfg(test)]
+    #[allow(dead_code)]
+    /// Holds the temporary directory containing the unix socket, and the
+    /// spawned emulator process.
+    emulator: std::sync::Arc<(tempfile::TempDir, async_process::Child)>,
+}
+
+/// Represents configuration of [BigtablePathInfoService].
+/// This currently conflates both connect parameters and data model/client
+/// behaviour parameters.
+#[serde_as]
+#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
+pub struct BigtableParameters {
+    project_id: String,
+    instance_name: String,
+    #[serde(default)]
+    is_read_only: bool,
+    #[serde(default = "default_channel_size")]
+    channel_size: usize,
+
+    #[serde_as(as = "Option<DurationSeconds<String>>")]
+    #[serde(default = "default_timeout")]
+    timeout: Option<std::time::Duration>,
+    table_name: String,
+    family_name: String,
+
+    #[serde(default = "default_app_profile_id")]
+    app_profile_id: String,
+}
+
+fn default_app_profile_id() -> String {
+    "default".to_owned()
+}
+
+fn default_channel_size() -> usize {
+    4
+}
+
+fn default_timeout() -> Option<std::time::Duration> {
+    Some(std::time::Duration::from_secs(4))
+}
+
+impl BigtablePathInfoService {
+    #[cfg(not(test))]
+    pub async fn connect(params: BigtableParameters) -> Result<Self, bigtable::Error> {
+        let connection = bigtable::BigTableConnection::new(
+            &params.project_id,
+            &params.instance_name,
+            params.is_read_only,
+            params.channel_size,
+            params.timeout,
+        )
+        .await?;
+
+        Ok(Self {
+            client: connection.client(),
+            params,
+        })
+    }
+
+    #[cfg(test)]
+    pub async fn connect(params: BigtableParameters) -> Result<Self, bigtable::Error> {
+        use std::time::Duration;
+
+        use async_process::{Command, Stdio};
+        use tempfile::TempDir;
+        use tokio_retry::{strategy::ExponentialBackoff, Retry};
+
+        let tmpdir = TempDir::new().unwrap();
+
+        let socket_path = tmpdir.path().join("cbtemulator.sock");
+
+        let emulator_process = Command::new("cbtemulator")
+            .arg("-address")
+            .arg(socket_path.clone())
+            .stderr(Stdio::piped())
+            .stdout(Stdio::piped())
+            .kill_on_drop(true)
+            .spawn()
+            .expect("failed to spawn emulator");
+
+        Retry::spawn(
+            ExponentialBackoff::from_millis(20)
+                .max_delay(Duration::from_secs(1))
+                .take(3),
+            || async {
+                if socket_path.exists() {
+                    Ok(())
+                } else {
+                    Err(())
+                }
+            },
+        )
+        .await
+        .expect("failed to wait for socket");
+
+        // populate the emulator
+        for cmd in &[
+            vec!["createtable", &params.table_name],
+            vec!["createfamily", &params.table_name, &params.family_name],
+        ] {
+            Command::new("cbt")
+                .args({
+                    let mut args = vec![
+                        "-instance",
+                        &params.instance_name,
+                        "-project",
+                        &params.project_id,
+                    ];
+                    args.extend_from_slice(cmd);
+                    args
+                })
+                .env(
+                    "BIGTABLE_EMULATOR_HOST",
+                    format!("unix://{}", socket_path.to_string_lossy()),
+                )
+                .output()
+                .await
+                .expect("failed to run cbt setup command");
+        }
+
+        let connection = bigtable_rs::bigtable::BigTableConnection::new_with_emulator(
+            &format!("unix://{}", socket_path.to_string_lossy()),
+            &params.project_id,
+            &params.instance_name,
+            false,
+            None,
+        )?;
+
+        Ok(Self {
+            client: connection.client(),
+            params,
+            emulator: (tmpdir, emulator_process).into(),
+        })
+    }
+}
+
+/// Derives the row/column key for a given output path.
+/// We use hexlower encoding, also because it can't be misinterpreted as RE2.
+fn derive_pathinfo_key(digest: &[u8; 20]) -> String {
+    HEXLOWER.encode(digest)
+}
+
+#[async_trait]
+impl PathInfoService for BigtablePathInfoService {
+    async fn get(&self, digest: [u8; 20]) -> Result<Option<PathInfo>, Error> {
+        let mut client = self.client.clone();
+        let path_info_key = derive_pathinfo_key(&digest);
+
+        let request = bigtable_v2::ReadRowsRequest {
+            app_profile_id: self.params.app_profile_id.to_string(),
+            table_name: client.get_full_table_name(&self.params.table_name),
+            rows_limit: 1,
+            rows: Some(bigtable_v2::RowSet {
+                row_keys: vec![path_info_key.clone().into()],
+                row_ranges: vec![],
+            }),
+            // Filter selected family name, and column qualifier matching the digest.
+            // The latter is to ensure we don't fail once we start adding more metadata.
+            filter: Some(bigtable_v2::RowFilter {
+                filter: Some(bigtable_v2::row_filter::Filter::Chain(
+                    bigtable_v2::row_filter::Chain {
+                        filters: vec![
+                            bigtable_v2::RowFilter {
+                                filter: Some(
+                                    bigtable_v2::row_filter::Filter::FamilyNameRegexFilter(
+                                        self.params.family_name.to_string(),
+                                    ),
+                                ),
+                            },
+                            bigtable_v2::RowFilter {
+                                filter: Some(
+                                    bigtable_v2::row_filter::Filter::ColumnQualifierRegexFilter(
+                                        path_info_key.clone().into(),
+                                    ),
+                                ),
+                            },
+                        ],
+                    },
+                )),
+            }),
+            ..Default::default()
+        };
+
+        let mut response = client
+            .read_rows(request)
+            .await
+            .map_err(|e| Error::StorageError(format!("unable to read rows: {}", e)))?;
+
+        if response.len() != 1 {
+            if response.len() > 1 {
+                // This shouldn't happen, we limit number of rows to 1
+                return Err(Error::StorageError(
+                    "got more than one row from bigtable".into(),
+                ));
+            }
+            // else, this is simply a "not found".
+            return Ok(None);
+        }
+
+        let (row_key, mut cells) = response.pop().unwrap();
+        if row_key != path_info_key.as_bytes() {
+            // This shouldn't happen, we requested this row key.
+            return Err(Error::StorageError(
+                "got wrong row key from bigtable".into(),
+            ));
+        }
+
+        let cell = cells
+            .pop()
+            .ok_or_else(|| Error::StorageError("found no cells".into()))?;
+
+        // Ensure there's only one cell (so no more left after the pop())
+        // This shouldn't happen, We filter out other cells in our query.
+        if !cells.is_empty() {
+            return Err(Error::StorageError(
+                "more than one cell returned from bigtable".into(),
+            ));
+        }
+
+        // We also require the qualifier to be correct in the filter above,
+        // so this shouldn't happen.
+        if path_info_key.as_bytes() != cell.qualifier {
+            return Err(Error::StorageError("unexpected cell qualifier".into()));
+        }
+
+        // Try to parse the value into a PathInfo message
+        let path_info = proto::PathInfo::decode(Bytes::from(cell.value))
+            .map_err(|e| Error::StorageError(format!("unable to decode pathinfo proto: {}", e)))?;
+
+        let store_path = path_info
+            .validate()
+            .map_err(|e| Error::StorageError(format!("invalid PathInfo: {}", e)))?;
+
+        if store_path.digest() != &digest {
+            return Err(Error::StorageError("PathInfo has unexpected digest".into()));
+        }
+
+        Ok(Some(path_info))
+    }
+
+    async fn put(&self, path_info: PathInfo) -> Result<PathInfo, Error> {
+        let store_path = path_info
+            .validate()
+            .map_err(|e| Error::InvalidRequest(format!("pathinfo failed validation: {}", e)))?;
+
+        let mut client = self.client.clone();
+        let path_info_key = derive_pathinfo_key(store_path.digest());
+
+        let data = path_info.encode_to_vec();
+        if data.len() as u64 > CELL_SIZE_LIMIT {
+            return Err(Error::StorageError(
+                "PathInfo exceeds cell limit on Bigtable".into(),
+            ));
+        }
+
+        let resp = client
+            .check_and_mutate_row(bigtable_v2::CheckAndMutateRowRequest {
+                table_name: client.get_full_table_name(&self.params.table_name),
+                app_profile_id: self.params.app_profile_id.to_string(),
+                row_key: path_info_key.clone().into(),
+                predicate_filter: Some(bigtable_v2::RowFilter {
+                    filter: Some(bigtable_v2::row_filter::Filter::ColumnQualifierRegexFilter(
+                        path_info_key.clone().into(),
+                    )),
+                }),
+                // If the column was already found, do nothing.
+                true_mutations: vec![],
+                // Else, do the insert.
+                false_mutations: vec![
+                    // https://cloud.google.com/bigtable/docs/writes
+                    bigtable_v2::Mutation {
+                        mutation: Some(bigtable_v2::mutation::Mutation::SetCell(
+                            bigtable_v2::mutation::SetCell {
+                                family_name: self.params.family_name.to_string(),
+                                column_qualifier: path_info_key.clone().into(),
+                                timestamp_micros: -1, // use server time to fill timestamp
+                                value: data,
+                            },
+                        )),
+                    },
+                ],
+            })
+            .await
+            .map_err(|e| Error::StorageError(format!("unable to mutate rows: {}", e)))?;
+
+        if resp.predicate_matched {
+            trace!("already existed")
+        }
+
+        Ok(path_info)
+    }
+
+    async fn calculate_nar(
+        &self,
+        _root_node: &castorepb::node::Node,
+    ) -> Result<(u64, [u8; 32]), Error> {
+        return Err(Error::StorageError("unimplemented".into()));
+    }
+
+    fn list(&self) -> BoxStream<'static, Result<PathInfo, Error>> {
+        let mut client = self.client.clone();
+
+        let request = bigtable_v2::ReadRowsRequest {
+            app_profile_id: self.params.app_profile_id.to_string(),
+            table_name: client.get_full_table_name(&self.params.table_name),
+            filter: Some(bigtable_v2::RowFilter {
+                filter: Some(bigtable_v2::row_filter::Filter::FamilyNameRegexFilter(
+                    self.params.family_name.to_string(),
+                )),
+            }),
+            ..Default::default()
+        };
+
+        let stream = try_stream! {
+            // TODO: add pagination, we don't want to hold all of this in memory.
+            let response = client
+                .read_rows(request)
+                .await
+                .map_err(|e| Error::StorageError(format!("unable to read rows: {}", e)))?;
+
+            for (row_key, mut cells) in response {
+                let cell = cells
+                    .pop()
+                    .ok_or_else(|| Error::StorageError("found no cells".into()))?;
+
+                // The cell must have the same qualifier as the row key
+                if row_key != cell.qualifier {
+                    Err(Error::StorageError("unexpected cell qualifier".into()))?;
+                }
+
+                // Ensure there's only one cell (so no more left after the pop())
+                // This shouldn't happen, We filter out other cells in our query.
+                if !cells.is_empty() {
+
+                    Err(Error::StorageError(
+                        "more than one cell returned from bigtable".into(),
+                    ))?
+                }
+
+                // Try to parse the value into a PathInfo message.
+                let path_info = proto::PathInfo::decode(Bytes::from(cell.value))
+                    .map_err(|e| Error::StorageError(format!("unable to decode pathinfo proto: {}", e)))?;
+
+                // Validate the containing PathInfo, ensure its StorePath digest
+                // matches row key.
+                let store_path = path_info
+                    .validate()
+                    .map_err(|e| Error::StorageError(format!("invalid PathInfo: {}", e)))?;
+
+                if store_path.digest().as_slice() != row_key.as_slice() {
+                    Err(Error::StorageError("PathInfo has unexpected digest".into()))?
+                }
+
+
+                yield path_info
+            }
+        };
+
+        Box::pin(stream)
+    }
+}
diff --git a/tvix/store/src/pathinfoservice/from_addr.rs b/tvix/store/src/pathinfoservice/from_addr.rs
new file mode 100644
index 0000000000..f22884ca47
--- /dev/null
+++ b/tvix/store/src/pathinfoservice/from_addr.rs
@@ -0,0 +1,236 @@
+use crate::proto::path_info_service_client::PathInfoServiceClient;
+
+use super::{
+    GRPCPathInfoService, MemoryPathInfoService, NixHTTPPathInfoService, PathInfoService,
+    SledPathInfoService,
+};
+
+use nix_compat::narinfo;
+use std::sync::Arc;
+use tvix_castore::{blobservice::BlobService, directoryservice::DirectoryService, Error};
+use url::Url;
+
+/// Constructs a new instance of a [PathInfoService] from an URI.
+///
+/// The following URIs are supported:
+/// - `memory:`
+///   Uses a in-memory implementation.
+/// - `sled:`
+///   Uses a in-memory sled implementation.
+/// - `sled:///absolute/path/to/somewhere`
+///   Uses sled, using a path on the disk for persistency. Can be only opened
+///   from one process at the same time.
+/// - `nix+https://cache.nixos.org?trusted-public-keys=cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=`
+///   Exposes the Nix binary cache as a PathInfoService, ingesting NARs into the
+///   {Blob,Directory}Service. You almost certainly want to use this with some cache.
+///   The `trusted-public-keys` URL parameter can be provided, which will then
+///   enable signature verification.
+/// - `grpc+unix:///absolute/path/to/somewhere`
+///   Connects to a local tvix-store gRPC service via Unix socket.
+/// - `grpc+http://host:port`, `grpc+https://host:port`
+///    Connects to a (remote) tvix-store gRPC service.
+///
+/// As the [PathInfoService] needs to talk to [BlobService] and [DirectoryService],
+/// these also need to be passed in.
+pub async fn from_addr(
+    uri: &str,
+    blob_service: Arc<dyn BlobService>,
+    directory_service: Arc<dyn DirectoryService>,
+) -> Result<Box<dyn PathInfoService>, Error> {
+    #[allow(unused_mut)]
+    let mut url =
+        Url::parse(uri).map_err(|e| Error::StorageError(format!("unable to parse url: {}", e)))?;
+
+    let path_info_service: Box<dyn PathInfoService> = match url.scheme() {
+        "memory" => {
+            // memory doesn't support host or path in the URL.
+            if url.has_host() || !url.path().is_empty() {
+                return Err(Error::StorageError("invalid url".to_string()));
+            }
+            Box::new(MemoryPathInfoService::new(blob_service, directory_service))
+        }
+        "sled" => {
+            // sled doesn't support host, and a path can be provided (otherwise
+            // it'll live in memory only).
+            if url.has_host() {
+                return Err(Error::StorageError("no host allowed".to_string()));
+            }
+
+            if url.path() == "/" {
+                return Err(Error::StorageError(
+                    "cowardly refusing to open / with sled".to_string(),
+                ));
+            }
+
+            // TODO: expose other parameters as URL parameters?
+
+            Box::new(if url.path().is_empty() {
+                SledPathInfoService::new_temporary(blob_service, directory_service)
+                    .map_err(|e| Error::StorageError(e.to_string()))?
+            } else {
+                SledPathInfoService::new(url.path(), blob_service, directory_service)
+                    .map_err(|e| Error::StorageError(e.to_string()))?
+            })
+        }
+        "nix+http" | "nix+https" => {
+            // Stringify the URL and remove the nix+ prefix.
+            // We can't use `url.set_scheme(rest)`, as it disallows
+            // setting something http(s) that previously wasn't.
+            let new_url = Url::parse(url.to_string().strip_prefix("nix+").unwrap()).unwrap();
+
+            let mut nix_http_path_info_service =
+                NixHTTPPathInfoService::new(new_url, blob_service, directory_service);
+
+            let pairs = &url.query_pairs();
+            for (k, v) in pairs.into_iter() {
+                if k == "trusted-public-keys" {
+                    let pubkey_strs: Vec<_> = v.split_ascii_whitespace().collect();
+
+                    let mut pubkeys: Vec<narinfo::PubKey> = Vec::with_capacity(pubkey_strs.len());
+                    for pubkey_str in pubkey_strs {
+                        pubkeys.push(narinfo::PubKey::parse(pubkey_str).map_err(|e| {
+                            Error::StorageError(format!("invalid public key: {e}"))
+                        })?);
+                    }
+
+                    nix_http_path_info_service.set_public_keys(pubkeys);
+                }
+            }
+
+            Box::new(nix_http_path_info_service)
+        }
+        scheme if scheme.starts_with("grpc+") => {
+            // schemes starting with grpc+ go to the GRPCPathInfoService.
+            //   That's normally grpc+unix for unix sockets, and grpc+http(s) for the HTTP counterparts.
+            // - In the case of unix sockets, there must be a path, but may not be a host.
+            // - In the case of non-unix sockets, there must be a host, but no path.
+            // Constructing the channel is handled by tvix_castore::channel::from_url.
+            let client =
+                PathInfoServiceClient::new(tvix_castore::tonic::channel_from_url(&url).await?);
+            Box::new(GRPCPathInfoService::from_client(client))
+        }
+        #[cfg(feature = "cloud")]
+        "bigtable" => {
+            use super::bigtable::BigtableParameters;
+            use super::BigtablePathInfoService;
+
+            // parse the instance name from the hostname.
+            let instance_name = url
+                .host_str()
+                .ok_or_else(|| Error::StorageError("instance name missing".into()))?
+                .to_string();
+
+            // … but add it to the query string now, so we just need to parse that.
+            url.query_pairs_mut()
+                .append_pair("instance_name", &instance_name);
+
+            let params: BigtableParameters = serde_qs::from_str(url.query().unwrap_or_default())
+                .map_err(|e| Error::InvalidRequest(format!("failed to parse parameters: {}", e)))?;
+
+            Box::new(
+                BigtablePathInfoService::connect(params)
+                    .await
+                    .map_err(|e| Error::StorageError(e.to_string()))?,
+            )
+        }
+        _ => Err(Error::StorageError(format!(
+            "unknown scheme: {}",
+            url.scheme()
+        )))?,
+    };
+
+    Ok(path_info_service)
+}
+
+#[cfg(test)]
+mod tests {
+    use super::from_addr;
+    use lazy_static::lazy_static;
+    use rstest::rstest;
+    use std::sync::Arc;
+    use tempfile::TempDir;
+    use tvix_castore::{
+        blobservice::{BlobService, MemoryBlobService},
+        directoryservice::{DirectoryService, MemoryDirectoryService},
+    };
+
+    lazy_static! {
+        static ref TMPDIR_SLED_1: TempDir = TempDir::new().unwrap();
+        static ref TMPDIR_SLED_2: TempDir = TempDir::new().unwrap();
+    }
+
+    // the gRPC tests below don't fail, because we connect lazily.
+
+    #[rstest]
+    /// This uses a unsupported scheme.
+    #[case::unsupported_scheme("http://foo.example/test", false)]
+    /// This configures sled in temporary mode.
+    #[case::sled_temporary("sled://", true)]
+    /// This configures sled with /, which should fail.
+    #[case::sled_invalid_root("sled:///", false)]
+    /// This configures sled with a host, not path, which should fail.
+    #[case::sled_invalid_host("sled://foo.example", false)]
+    /// This configures sled with a valid path path, which should succeed.
+    #[case::sled_valid_path(&format!("sled://{}", &TMPDIR_SLED_1.path().to_str().unwrap()), true)]
+    /// This configures sled with a host, and a valid path path, which should fail.
+    #[case::sled_invalid_host_with_valid_path(&format!("sled://foo.example{}", &TMPDIR_SLED_2.path().to_str().unwrap()), false)]
+    /// This correctly sets the scheme, and doesn't set a path.
+    #[case::memory_valid("memory://", true)]
+    /// This sets a memory url host to `foo`
+    #[case::memory_invalid_host("memory://foo", false)]
+    /// This sets a memory url path to "/", which is invalid.
+    #[case::memory_invalid_root_path("memory:///", false)]
+    /// This sets a memory url path to "/foo", which is invalid.
+    #[case::memory_invalid_root_path_foo("memory:///foo", false)]
+    /// Correct Scheme for the cache.nixos.org binary cache.
+    #[case::correct_nix_https("nix+https://cache.nixos.org", true)]
+    /// Correct Scheme for the cache.nixos.org binary cache (HTTP URL).
+    #[case::correct_nix_http("nix+http://cache.nixos.org", true)]
+    /// Correct Scheme for Nix HTTP Binary cache, with a subpath.
+    #[case::correct_nix_http_with_subpath("nix+http://192.0.2.1/foo", true)]
+    /// Correct Scheme for Nix HTTP Binary cache, with a subpath and port.
+    #[case::correct_nix_http_with_subpath_and_port("nix+http://[::1]:8080/foo", true)]
+    /// Correct Scheme for the cache.nixos.org binary cache, and correct trusted public key set
+    #[case::correct_nix_https_with_trusted_public_key("nix+https://cache.nixos.org?trusted-public-keys=cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=", true)]
+    /// Correct Scheme for the cache.nixos.org binary cache, and two correct trusted public keys set
+    #[case::correct_nix_https_with_two_trusted_public_keys("nix+https://cache.nixos.org?trusted-public-keys=cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=%20foo:jp4fCEx9tBEId/L0ZsVJ26k0wC0fu7vJqLjjIGFkup8=", true)]
+    /// Correct scheme to connect to a unix socket.
+    #[case::grpc_valid_unix_socket("grpc+unix:///path/to/somewhere", true)]
+    /// Correct scheme for unix socket, but setting a host too, which is invalid.
+    #[case::grpc_invalid_unix_socket_and_host("grpc+unix://host.example/path/to/somewhere", false)]
+    /// Correct scheme to connect to localhost, with port 12345
+    #[case::grpc_valid_ipv6_localhost_port_12345("grpc+http://[::1]:12345", true)]
+    /// Correct scheme to connect to localhost over http, without specifying a port.
+    #[case::grpc_valid_http_host_without_port("grpc+http://localhost", true)]
+    /// Correct scheme to connect to localhost over http, without specifying a port.
+    #[case::grpc_valid_https_host_without_port("grpc+https://localhost", true)]
+    /// Correct scheme to connect to localhost over http, but with additional path, which is invalid.
+    #[case::grpc_invalid_host_and_path("grpc+http://localhost/some-path", false)]
+    /// A valid example for Bigtable.
+    #[cfg_attr(
+        all(feature = "cloud", feature = "integration"),
+        case::bigtable_valid(
+            "bigtable://instance-1?project_id=project-1&table_name=table-1&family_name=cf1",
+            true
+        )
+    )]
+    /// An invalid example for Bigtable, missing fields
+    #[cfg_attr(
+        all(feature = "cloud", feature = "integration"),
+        case::bigtable_invalid_missing_fields("bigtable://instance-1", false)
+    )]
+    #[tokio::test]
+    async fn test_from_addr_tokio(#[case] uri_str: &str, #[case] exp_succeed: bool) {
+        let blob_service: Arc<dyn BlobService> = Arc::from(MemoryBlobService::default());
+        let directory_service: Arc<dyn DirectoryService> =
+            Arc::from(MemoryDirectoryService::default());
+
+        let resp = from_addr(uri_str, blob_service, directory_service).await;
+
+        if exp_succeed {
+            resp.expect("should succeed");
+        } else {
+            assert!(resp.is_err(), "should fail");
+        }
+    }
+}
diff --git a/tvix/store/src/pathinfoservice/fs/mod.rs b/tvix/store/src/pathinfoservice/fs/mod.rs
new file mode 100644
index 0000000000..aa64b1c01f
--- /dev/null
+++ b/tvix/store/src/pathinfoservice/fs/mod.rs
@@ -0,0 +1,81 @@
+use futures::stream::BoxStream;
+use futures::StreamExt;
+use tonic::async_trait;
+use tvix_castore::fs::{RootNodes, TvixStoreFs};
+use tvix_castore::proto as castorepb;
+use tvix_castore::Error;
+use tvix_castore::{blobservice::BlobService, directoryservice::DirectoryService};
+
+use super::PathInfoService;
+
+/// Helper to construct a [TvixStoreFs] from a [BlobService], [DirectoryService]
+/// and [PathInfoService].
+/// This avoids users to have to interact with the wrapper struct directly, as
+/// it leaks into the type signature of TvixStoreFS.
+pub fn make_fs<BS, DS, PS>(
+    blob_service: BS,
+    directory_service: DS,
+    path_info_service: PS,
+    list_root: bool,
+    show_xattr: bool,
+) -> TvixStoreFs<BS, DS, RootNodesWrapper<PS>>
+where
+    BS: AsRef<dyn BlobService> + Send + Clone + 'static,
+    DS: AsRef<dyn DirectoryService> + Send + Clone + 'static,
+    PS: AsRef<dyn PathInfoService> + Send + Sync + Clone + 'static,
+{
+    TvixStoreFs::new(
+        blob_service,
+        directory_service,
+        RootNodesWrapper(path_info_service),
+        list_root,
+        show_xattr,
+    )
+}
+
+/// Wrapper to satisfy Rust's orphan rules for trait implementations, as
+/// RootNodes is coming from the [tvix-castore] crate.
+#[doc(hidden)]
+#[derive(Clone, Debug)]
+pub struct RootNodesWrapper<T>(pub(crate) T);
+
+/// Implements root node lookup for any [PathInfoService]. This represents a flat
+/// directory structure like /nix/store where each entry in the root filesystem
+/// directory corresponds to a CA node.
+#[cfg(any(feature = "fuse", feature = "virtiofs"))]
+#[async_trait]
+impl<T> RootNodes for RootNodesWrapper<T>
+where
+    T: AsRef<dyn PathInfoService> + Send + Sync,
+{
+    async fn get_by_basename(&self, name: &[u8]) -> Result<Option<castorepb::node::Node>, Error> {
+        let Ok(store_path) = nix_compat::store_path::StorePath::from_bytes(name) else {
+            return Ok(None);
+        };
+
+        Ok(self
+            .0
+            .as_ref()
+            .get(*store_path.digest())
+            .await?
+            .map(|path_info| {
+                path_info
+                    .node
+                    .expect("missing root node")
+                    .node
+                    .expect("empty node")
+            }))
+    }
+
+    fn list(&self) -> BoxStream<Result<castorepb::node::Node, Error>> {
+        Box::pin(self.0.as_ref().list().map(|result| {
+            result.map(|path_info| {
+                path_info
+                    .node
+                    .expect("missing root node")
+                    .node
+                    .expect("empty node")
+            })
+        }))
+    }
+}
diff --git a/tvix/store/src/pathinfoservice/grpc.rs b/tvix/store/src/pathinfoservice/grpc.rs
new file mode 100644
index 0000000000..1138ebdc19
--- /dev/null
+++ b/tvix/store/src/pathinfoservice/grpc.rs
@@ -0,0 +1,216 @@
+use super::PathInfoService;
+use crate::proto::{self, ListPathInfoRequest, PathInfo};
+use async_stream::try_stream;
+use data_encoding::BASE64;
+use futures::stream::BoxStream;
+use tonic::{async_trait, transport::Channel, Code};
+use tracing::instrument;
+use tvix_castore::{proto as castorepb, Error};
+
+/// Connects to a (remote) tvix-store PathInfoService over gRPC.
+#[derive(Clone)]
+pub struct GRPCPathInfoService {
+    /// The internal reference to a gRPC client.
+    /// Cloning it is cheap, and it internally handles concurrent requests.
+    grpc_client: proto::path_info_service_client::PathInfoServiceClient<Channel>,
+}
+
+impl GRPCPathInfoService {
+    /// construct a [GRPCPathInfoService] from a [proto::path_info_service_client::PathInfoServiceClient].
+    /// panics if called outside the context of a tokio runtime.
+    pub fn from_client(
+        grpc_client: proto::path_info_service_client::PathInfoServiceClient<Channel>,
+    ) -> Self {
+        Self { grpc_client }
+    }
+}
+
+#[async_trait]
+impl PathInfoService for GRPCPathInfoService {
+    #[instrument(level = "trace", skip_all, fields(path_info.digest = BASE64.encode(&digest)))]
+    async fn get(&self, digest: [u8; 20]) -> Result<Option<PathInfo>, Error> {
+        let path_info = self
+            .grpc_client
+            .clone()
+            .get(proto::GetPathInfoRequest {
+                by_what: Some(proto::get_path_info_request::ByWhat::ByOutputHash(
+                    digest.to_vec().into(),
+                )),
+            })
+            .await;
+
+        match path_info {
+            Ok(path_info) => {
+                let path_info = path_info.into_inner();
+
+                path_info
+                    .validate()
+                    .map_err(|e| Error::StorageError(format!("invalid pathinfo: {}", e)))?;
+
+                Ok(Some(path_info))
+            }
+            Err(e) if e.code() == Code::NotFound => Ok(None),
+            Err(e) => Err(Error::StorageError(e.to_string())),
+        }
+    }
+
+    #[instrument(level = "trace", skip_all, fields(path_info.root_node = ?path_info.node))]
+    async fn put(&self, path_info: PathInfo) -> Result<PathInfo, Error> {
+        let path_info = self
+            .grpc_client
+            .clone()
+            .put(path_info)
+            .await
+            .map_err(|e| Error::StorageError(e.to_string()))?
+            .into_inner();
+
+        Ok(path_info)
+    }
+
+    #[instrument(level = "trace", skip_all, fields(root_node = ?root_node))]
+    async fn calculate_nar(
+        &self,
+        root_node: &castorepb::node::Node,
+    ) -> Result<(u64, [u8; 32]), Error> {
+        let path_info = self
+            .grpc_client
+            .clone()
+            .calculate_nar(castorepb::Node {
+                node: Some(root_node.clone()),
+            })
+            .await
+            .map_err(|e| Error::StorageError(e.to_string()))?
+            .into_inner();
+
+        let nar_sha256: [u8; 32] = path_info
+            .nar_sha256
+            .to_vec()
+            .try_into()
+            .map_err(|_e| Error::StorageError("invalid digest length".to_string()))?;
+
+        Ok((path_info.nar_size, nar_sha256))
+    }
+
+    #[instrument(level = "trace", skip_all)]
+    fn list(&self) -> BoxStream<'static, Result<PathInfo, Error>> {
+        let mut grpc_client = self.grpc_client.clone();
+
+        let stream = try_stream! {
+            let resp = grpc_client.list(ListPathInfoRequest::default()).await;
+
+            let mut stream = resp.map_err(|e| Error::StorageError(e.to_string()))?.into_inner();
+
+            loop {
+                match stream.message().await {
+                    Ok(o) => match o {
+                        Some(pathinfo) => {
+                            // validate the pathinfo
+                            if let Err(e) = pathinfo.validate() {
+                                Err(Error::StorageError(format!(
+                                    "pathinfo {:?} failed validation: {}",
+                                    pathinfo, e
+                                )))?;
+                            }
+                            yield pathinfo
+                        }
+                        None => {
+                            return;
+                        },
+                    },
+                    Err(e) => Err(Error::StorageError(e.to_string()))?,
+                }
+            }
+        };
+
+        Box::pin(stream)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use std::sync::Arc;
+    use std::time::Duration;
+
+    use rstest::*;
+    use tempfile::TempDir;
+    use tokio::net::UnixListener;
+    use tokio_retry::strategy::ExponentialBackoff;
+    use tokio_retry::Retry;
+    use tokio_stream::wrappers::UnixListenerStream;
+    use tvix_castore::blobservice::BlobService;
+    use tvix_castore::directoryservice::DirectoryService;
+
+    use crate::pathinfoservice::MemoryPathInfoService;
+    use crate::proto::path_info_service_client::PathInfoServiceClient;
+    use crate::proto::GRPCPathInfoServiceWrapper;
+    use crate::tests::fixtures::{self, blob_service, directory_service};
+
+    use super::GRPCPathInfoService;
+    use super::PathInfoService;
+
+    /// This ensures connecting via gRPC works as expected.
+    #[rstest]
+    #[tokio::test]
+    async fn test_valid_unix_path_ping_pong(
+        blob_service: Arc<dyn BlobService>,
+        directory_service: Arc<dyn DirectoryService>,
+    ) {
+        let tmpdir = TempDir::new().unwrap();
+        let socket_path = tmpdir.path().join("daemon");
+
+        let path_clone = socket_path.clone();
+
+        // Spin up a server
+        tokio::spawn(async {
+            let uds = UnixListener::bind(path_clone).unwrap();
+            let uds_stream = UnixListenerStream::new(uds);
+
+            // spin up a new server
+            let mut server = tonic::transport::Server::builder();
+            let router = server.add_service(
+                crate::proto::path_info_service_server::PathInfoServiceServer::new(
+                    GRPCPathInfoServiceWrapper::new(Box::new(MemoryPathInfoService::new(
+                        blob_service,
+                        directory_service,
+                    ))
+                        as Box<dyn PathInfoService>),
+                ),
+            );
+            router.serve_with_incoming(uds_stream).await
+        });
+
+        // wait for the socket to be created
+        Retry::spawn(
+            ExponentialBackoff::from_millis(20).max_delay(Duration::from_secs(10)),
+            || async {
+                if socket_path.exists() {
+                    Ok(())
+                } else {
+                    Err(())
+                }
+            },
+        )
+        .await
+        .expect("failed to wait for socket");
+
+        // prepare a client
+        let grpc_client = {
+            let url = url::Url::parse(&format!("grpc+unix://{}", socket_path.display()))
+                .expect("must parse");
+            let client = PathInfoServiceClient::new(
+                tvix_castore::tonic::channel_from_url(&url)
+                    .await
+                    .expect("must succeed"),
+            );
+
+            GRPCPathInfoService::from_client(client)
+        };
+
+        let path_info = grpc_client
+            .get(fixtures::DUMMY_PATH_DIGEST)
+            .await
+            .expect("must not be error");
+
+        assert!(path_info.is_none());
+    }
+}
diff --git a/tvix/store/src/pathinfoservice/memory.rs b/tvix/store/src/pathinfoservice/memory.rs
new file mode 100644
index 0000000000..f8435dbbf8
--- /dev/null
+++ b/tvix/store/src/pathinfoservice/memory.rs
@@ -0,0 +1,84 @@
+use super::PathInfoService;
+use crate::{nar::calculate_size_and_sha256, proto::PathInfo};
+use futures::stream::{iter, BoxStream};
+use std::{
+    collections::HashMap,
+    sync::{Arc, RwLock},
+};
+use tonic::async_trait;
+use tvix_castore::proto as castorepb;
+use tvix_castore::Error;
+use tvix_castore::{blobservice::BlobService, directoryservice::DirectoryService};
+
+pub struct MemoryPathInfoService<BS, DS> {
+    db: Arc<RwLock<HashMap<[u8; 20], PathInfo>>>,
+
+    blob_service: BS,
+    directory_service: DS,
+}
+
+impl<BS, DS> MemoryPathInfoService<BS, DS> {
+    pub fn new(blob_service: BS, directory_service: DS) -> Self {
+        Self {
+            db: Default::default(),
+            blob_service,
+            directory_service,
+        }
+    }
+}
+
+#[async_trait]
+impl<BS, DS> PathInfoService for MemoryPathInfoService<BS, DS>
+where
+    BS: AsRef<dyn BlobService> + Send + Sync,
+    DS: AsRef<dyn DirectoryService> + Send + Sync,
+{
+    async fn get(&self, digest: [u8; 20]) -> Result<Option<PathInfo>, Error> {
+        let db = self.db.read().unwrap();
+
+        match db.get(&digest) {
+            None => Ok(None),
+            Some(path_info) => Ok(Some(path_info.clone())),
+        }
+    }
+
+    async fn put(&self, path_info: PathInfo) -> Result<PathInfo, Error> {
+        // Call validate on the received PathInfo message.
+        match path_info.validate() {
+            Err(e) => Err(Error::InvalidRequest(format!(
+                "failed to validate PathInfo: {}",
+                e
+            ))),
+
+            // In case the PathInfo is valid, and we were able to extract a NixPath, store it in the database.
+            // This overwrites existing PathInfo objects.
+            Ok(nix_path) => {
+                let mut db = self.db.write().unwrap();
+                db.insert(*nix_path.digest(), path_info.clone());
+
+                Ok(path_info)
+            }
+        }
+    }
+
+    async fn calculate_nar(
+        &self,
+        root_node: &castorepb::node::Node,
+    ) -> Result<(u64, [u8; 32]), Error> {
+        calculate_size_and_sha256(root_node, &self.blob_service, &self.directory_service)
+            .await
+            .map_err(|e| Error::StorageError(e.to_string()))
+    }
+
+    fn list(&self) -> BoxStream<'static, Result<PathInfo, Error>> {
+        let db = self.db.read().unwrap();
+
+        // Copy all elements into a list.
+        // This is a bit ugly, because we can't have db escape the lifetime
+        // of this function, but elements need to be returned owned anyways, and this in-
+        // memory impl is only for testing purposes anyways.
+        let items: Vec<_> = db.iter().map(|(_k, v)| Ok(v.clone())).collect();
+
+        Box::pin(iter(items))
+    }
+}
diff --git a/tvix/store/src/pathinfoservice/mod.rs b/tvix/store/src/pathinfoservice/mod.rs
new file mode 100644
index 0000000000..c1a482bbb5
--- /dev/null
+++ b/tvix/store/src/pathinfoservice/mod.rs
@@ -0,0 +1,85 @@
+mod from_addr;
+mod grpc;
+mod memory;
+mod nix_http;
+mod sled;
+
+#[cfg(any(feature = "fuse", feature = "virtiofs"))]
+mod fs;
+
+#[cfg(test)]
+mod tests;
+
+use futures::stream::BoxStream;
+use tonic::async_trait;
+use tvix_castore::proto as castorepb;
+use tvix_castore::Error;
+
+use crate::proto::PathInfo;
+
+pub use self::from_addr::from_addr;
+pub use self::grpc::GRPCPathInfoService;
+pub use self::memory::MemoryPathInfoService;
+pub use self::nix_http::NixHTTPPathInfoService;
+pub use self::sled::SledPathInfoService;
+
+#[cfg(feature = "cloud")]
+mod bigtable;
+#[cfg(feature = "cloud")]
+pub use self::bigtable::BigtablePathInfoService;
+
+#[cfg(any(feature = "fuse", feature = "virtiofs"))]
+pub use self::fs::make_fs;
+
+/// The base trait all PathInfo services need to implement.
+#[async_trait]
+pub trait PathInfoService: Send + Sync {
+    /// Retrieve a PathInfo message by the output digest.
+    async fn get(&self, digest: [u8; 20]) -> Result<Option<PathInfo>, Error>;
+
+    /// Store a PathInfo message. Implementations MUST call validate and reject
+    /// invalid messages.
+    async fn put(&self, path_info: PathInfo) -> Result<PathInfo, Error>;
+
+    /// Return the nar size and nar sha256 digest for a given root node.
+    /// This can be used to calculate NAR-based output paths,
+    /// and implementations are encouraged to cache it.
+    async fn calculate_nar(
+        &self,
+        root_node: &castorepb::node::Node,
+    ) -> Result<(u64, [u8; 32]), Error>;
+
+    /// Iterate over all PathInfo objects in the store.
+    /// Implementations can decide to disallow listing.
+    ///
+    /// This returns a pinned, boxed stream. The pinning allows for it to be polled easily,
+    /// and the box allows different underlying stream implementations to be returned since
+    /// Rust doesn't support this as a generic in traits yet. This is the same thing that
+    /// [async_trait] generates, but for streams instead of futures.
+    fn list(&self) -> BoxStream<'static, Result<PathInfo, Error>>;
+}
+
+#[async_trait]
+impl<A> PathInfoService for A
+where
+    A: AsRef<dyn PathInfoService> + Send + Sync,
+{
+    async fn get(&self, digest: [u8; 20]) -> Result<Option<PathInfo>, Error> {
+        self.as_ref().get(digest).await
+    }
+
+    async fn put(&self, path_info: PathInfo) -> Result<PathInfo, Error> {
+        self.as_ref().put(path_info).await
+    }
+
+    async fn calculate_nar(
+        &self,
+        root_node: &castorepb::node::Node,
+    ) -> Result<(u64, [u8; 32]), Error> {
+        self.as_ref().calculate_nar(root_node).await
+    }
+
+    fn list(&self) -> BoxStream<'static, Result<PathInfo, Error>> {
+        self.as_ref().list()
+    }
+}
diff --git a/tvix/store/src/pathinfoservice/nix_http.rs b/tvix/store/src/pathinfoservice/nix_http.rs
new file mode 100644
index 0000000000..581eb7ca7a
--- /dev/null
+++ b/tvix/store/src/pathinfoservice/nix_http.rs
@@ -0,0 +1,278 @@
+use data_encoding::BASE64;
+use futures::{stream::BoxStream, TryStreamExt};
+use nix_compat::{
+    narinfo::{self, NarInfo},
+    nixbase32,
+    nixhash::NixHash,
+};
+use reqwest::StatusCode;
+use sha2::Digest;
+use std::io::{self, Write};
+use tokio::io::{AsyncRead, BufReader};
+use tokio_util::io::InspectReader;
+use tonic::async_trait;
+use tracing::{debug, instrument, warn};
+use tvix_castore::{
+    blobservice::BlobService, directoryservice::DirectoryService, proto as castorepb, Error,
+};
+
+use crate::proto::PathInfo;
+
+use super::PathInfoService;
+
+/// NixHTTPPathInfoService acts as a bridge in between the Nix HTTP Binary cache
+/// protocol provided by Nix binary caches such as cache.nixos.org, and the Tvix
+/// Store Model.
+/// It implements the [PathInfoService] trait in an interesting way:
+/// Every [PathInfoService::get] fetches the .narinfo and referred NAR file,
+/// inserting components into a [BlobService] and [DirectoryService], then
+/// returning a [PathInfo] struct with the root.
+///
+/// Due to this being quite a costly operation, clients are expected to layer
+/// this service with store composition, so they're only ingested once.
+///
+/// The client is expected to be (indirectly) using the same [BlobService] and
+/// [DirectoryService], so able to fetch referred Directories and Blobs.
+/// [PathInfoService::put] and [PathInfoService::calculate_nar] are not
+/// implemented and return an error if called.
+/// TODO: what about reading from nix-cache-info?
+pub struct NixHTTPPathInfoService<BS, DS> {
+    base_url: url::Url,
+    http_client: reqwest::Client,
+
+    blob_service: BS,
+    directory_service: DS,
+
+    /// An optional list of [narinfo::PubKey].
+    /// If set, the .narinfo files received need to have correct signature by at least one of these.
+    public_keys: Option<Vec<narinfo::PubKey>>,
+}
+
+impl<BS, DS> NixHTTPPathInfoService<BS, DS> {
+    pub fn new(base_url: url::Url, blob_service: BS, directory_service: DS) -> Self {
+        Self {
+            base_url,
+            http_client: reqwest::Client::new(),
+            blob_service,
+            directory_service,
+
+            public_keys: None,
+        }
+    }
+
+    /// Configures [Self] to validate NARInfo fingerprints with the public keys passed.
+    pub fn set_public_keys(&mut self, public_keys: Vec<narinfo::PubKey>) {
+        self.public_keys = Some(public_keys);
+    }
+}
+
+#[async_trait]
+impl<BS, DS> PathInfoService for NixHTTPPathInfoService<BS, DS>
+where
+    BS: AsRef<dyn BlobService> + Send + Sync + Clone + 'static,
+    DS: AsRef<dyn DirectoryService> + Send + Sync + Clone + 'static,
+{
+    #[instrument(skip_all, err, fields(path.digest=BASE64.encode(&digest)))]
+    async fn get(&self, digest: [u8; 20]) -> Result<Option<PathInfo>, Error> {
+        let narinfo_url = self
+            .base_url
+            .join(&format!("{}.narinfo", nixbase32::encode(&digest)))
+            .map_err(|e| {
+                warn!(e = %e, "unable to join URL");
+                io::Error::new(io::ErrorKind::InvalidInput, "unable to join url")
+            })?;
+
+        debug!(narinfo_url= %narinfo_url, "constructed NARInfo url");
+
+        let resp = self
+            .http_client
+            .get(narinfo_url)
+            .send()
+            .await
+            .map_err(|e| {
+                warn!(e=%e,"unable to send NARInfo request");
+                io::Error::new(
+                    io::ErrorKind::InvalidInput,
+                    "unable to send NARInfo request",
+                )
+            })?;
+
+        // In the case of a 404, return a NotFound.
+        // We also return a NotFound in case of a 403 - this is to match the behaviour as Nix,
+        // when querying nix-cache.s3.amazonaws.com directly, rather than cache.nixos.org.
+        if resp.status() == StatusCode::NOT_FOUND || resp.status() == StatusCode::FORBIDDEN {
+            return Ok(None);
+        }
+
+        let narinfo_str = resp.text().await.map_err(|e| {
+            warn!(e=%e,"unable to decode response as string");
+            io::Error::new(
+                io::ErrorKind::InvalidData,
+                "unable to decode response as string",
+            )
+        })?;
+
+        // parse the received narinfo
+        let narinfo = NarInfo::parse(&narinfo_str).map_err(|e| {
+            warn!(e=%e,"unable to parse response as NarInfo");
+            io::Error::new(
+                io::ErrorKind::InvalidData,
+                "unable to parse response as NarInfo",
+            )
+        })?;
+
+        // if [self.public_keys] is set, ensure there's at least one valid signature.
+        if let Some(public_keys) = &self.public_keys {
+            let fingerprint = narinfo.fingerprint();
+
+            if !public_keys.iter().any(|pubkey| {
+                narinfo
+                    .signatures
+                    .iter()
+                    .any(|sig| pubkey.verify(&fingerprint, sig))
+            }) {
+                warn!("no valid signature found");
+                Err(io::Error::new(
+                    io::ErrorKind::InvalidData,
+                    "no valid signature found",
+                ))?;
+            }
+        }
+
+        // Convert to a (sparse) PathInfo. We still need to populate the node field,
+        // and for this we need to download the NAR file.
+        // FUTUREWORK: Keep some database around mapping from narsha256 to
+        // (unnamed) rootnode, so we can use that (and the name from the
+        // StorePath) and avoid downloading the same NAR a second time.
+        let pathinfo: PathInfo = (&narinfo).into();
+
+        // create a request for the NAR file itself.
+        let nar_url = self.base_url.join(narinfo.url).map_err(|e| {
+            warn!(e = %e, "unable to join URL");
+            io::Error::new(io::ErrorKind::InvalidInput, "unable to join url")
+        })?;
+        debug!(nar_url= %nar_url, "constructed NAR url");
+
+        let resp = self
+            .http_client
+            .get(nar_url.clone())
+            .send()
+            .await
+            .map_err(|e| {
+                warn!(e=%e,"unable to send NAR request");
+                io::Error::new(io::ErrorKind::InvalidInput, "unable to send NAR request")
+            })?;
+
+        // if the request is not successful, return an error.
+        if !resp.status().is_success() {
+            return Err(Error::StorageError(format!(
+                "unable to retrieve NAR at {}, status {}",
+                nar_url,
+                resp.status()
+            )));
+        }
+
+        // get a reader of the response body.
+        let r = tokio_util::io::StreamReader::new(resp.bytes_stream().map_err(|e| {
+            let e = e.without_url();
+            warn!(e=%e, "failed to get response body");
+            io::Error::new(io::ErrorKind::BrokenPipe, e.to_string())
+        }));
+
+        // handle decompression, depending on the compression field.
+        let r: Box<dyn AsyncRead + Send + Unpin> = match narinfo.compression {
+            Some("none") => Box::new(r) as Box<dyn AsyncRead + Send + Unpin>,
+            Some("bzip2") | None => Box::new(async_compression::tokio::bufread::BzDecoder::new(r))
+                as Box<dyn AsyncRead + Send + Unpin>,
+            Some("gzip") => Box::new(async_compression::tokio::bufread::GzipDecoder::new(r))
+                as Box<dyn AsyncRead + Send + Unpin>,
+            Some("xz") => Box::new(async_compression::tokio::bufread::XzDecoder::new(r))
+                as Box<dyn AsyncRead + Send + Unpin>,
+            Some("zstd") => Box::new(async_compression::tokio::bufread::ZstdDecoder::new(r))
+                as Box<dyn AsyncRead + Send + Unpin>,
+            Some(comp_str) => {
+                return Err(Error::StorageError(format!(
+                    "unsupported compression: {comp_str}"
+                )));
+            }
+        };
+        let mut nar_hash = sha2::Sha256::new();
+        let mut nar_size = 0;
+
+        // Assemble NarHash and NarSize as we read bytes.
+        let r = InspectReader::new(r, |b| {
+            nar_size += b.len() as u64;
+            nar_hash.write_all(b).unwrap();
+        });
+
+        // HACK: InspectReader doesn't implement AsyncBufRead, but neither do our decompressors.
+        let mut r = BufReader::new(r);
+
+        let root_node = crate::nar::ingest_nar(
+            self.blob_service.clone(),
+            self.directory_service.clone(),
+            &mut r,
+        )
+        .await
+        .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
+
+        // ensure the ingested narhash and narsize do actually match.
+        if narinfo.nar_size != nar_size {
+            warn!(
+                narinfo.nar_size = narinfo.nar_size,
+                http.nar_size = nar_size,
+                "NarSize mismatch"
+            );
+            Err(io::Error::new(
+                io::ErrorKind::InvalidData,
+                "NarSize mismatch".to_string(),
+            ))?;
+        }
+        let nar_hash: [u8; 32] = nar_hash.finalize().into();
+        if narinfo.nar_hash != nar_hash {
+            warn!(
+                narinfo.nar_hash = %NixHash::Sha256(narinfo.nar_hash),
+                http.nar_hash = %NixHash::Sha256(nar_hash),
+                "NarHash mismatch"
+            );
+            Err(io::Error::new(
+                io::ErrorKind::InvalidData,
+                "NarHash mismatch".to_string(),
+            ))?;
+        }
+
+        Ok(Some(PathInfo {
+            node: Some(castorepb::Node {
+                // set the name of the root node to the digest-name of the store path.
+                node: Some(root_node.rename(narinfo.store_path.to_string().to_owned().into())),
+            }),
+            references: pathinfo.references,
+            narinfo: pathinfo.narinfo,
+        }))
+    }
+
+    #[instrument(skip_all, fields(path_info=?_path_info))]
+    async fn put(&self, _path_info: PathInfo) -> Result<PathInfo, Error> {
+        Err(Error::InvalidRequest(
+            "put not supported for this backend".to_string(),
+        ))
+    }
+
+    #[instrument(skip_all, fields(root_node=?root_node))]
+    async fn calculate_nar(
+        &self,
+        root_node: &castorepb::node::Node,
+    ) -> Result<(u64, [u8; 32]), Error> {
+        Err(Error::InvalidRequest(
+            "calculate_nar not supported for this backend".to_string(),
+        ))
+    }
+
+    fn list(&self) -> BoxStream<'static, Result<PathInfo, Error>> {
+        Box::pin(futures::stream::once(async {
+            Err(Error::InvalidRequest(
+                "list not supported for this backend".to_string(),
+            ))
+        }))
+    }
+}
diff --git a/tvix/store/src/pathinfoservice/sled.rs b/tvix/store/src/pathinfoservice/sled.rs
new file mode 100644
index 0000000000..0255c031e2
--- /dev/null
+++ b/tvix/store/src/pathinfoservice/sled.rs
@@ -0,0 +1,125 @@
+use super::PathInfoService;
+use crate::nar::calculate_size_and_sha256;
+use crate::proto::PathInfo;
+use data_encoding::BASE64;
+use futures::stream::iter;
+use futures::stream::BoxStream;
+use prost::Message;
+use std::path::Path;
+use tonic::async_trait;
+use tracing::instrument;
+use tracing::warn;
+use tvix_castore::proto as castorepb;
+use tvix_castore::{blobservice::BlobService, directoryservice::DirectoryService, Error};
+
+/// SledPathInfoService stores PathInfo in a [sled](https://github.com/spacejam/sled).
+///
+/// The PathInfo messages are stored as encoded protos, and keyed by their output hash,
+/// as that's currently the only request type available.
+pub struct SledPathInfoService<BS, DS> {
+    db: sled::Db,
+
+    blob_service: BS,
+    directory_service: DS,
+}
+
+impl<BS, DS> SledPathInfoService<BS, DS> {
+    pub fn new<P: AsRef<Path>>(
+        p: P,
+        blob_service: BS,
+        directory_service: DS,
+    ) -> Result<Self, sled::Error> {
+        let config = sled::Config::default()
+            .use_compression(false) // is a required parameter
+            .path(p);
+        let db = config.open()?;
+
+        Ok(Self {
+            db,
+            blob_service,
+            directory_service,
+        })
+    }
+
+    pub fn new_temporary(blob_service: BS, directory_service: DS) -> Result<Self, sled::Error> {
+        let config = sled::Config::default().temporary(true);
+        let db = config.open()?;
+
+        Ok(Self {
+            db,
+            blob_service,
+            directory_service,
+        })
+    }
+}
+
+#[async_trait]
+impl<BS, DS> PathInfoService for SledPathInfoService<BS, DS>
+where
+    BS: AsRef<dyn BlobService> + Send + Sync,
+    DS: AsRef<dyn DirectoryService> + Send + Sync,
+{
+    #[instrument(level = "trace", skip_all, fields(path_info.digest = BASE64.encode(&digest)))]
+    async fn get(&self, digest: [u8; 20]) -> Result<Option<PathInfo>, Error> {
+        match self.db.get(digest).map_err(|e| {
+            warn!("failed to retrieve PathInfo: {}", e);
+            Error::StorageError(format!("failed to retrieve PathInfo: {}", e))
+        })? {
+            None => Ok(None),
+            Some(data) => {
+                let path_info = PathInfo::decode(&*data).map_err(|e| {
+                    warn!("failed to decode stored PathInfo: {}", e);
+                    Error::StorageError(format!("failed to decode stored PathInfo: {}", e))
+                })?;
+                Ok(Some(path_info))
+            }
+        }
+    }
+
+    #[instrument(level = "trace", skip_all, fields(path_info.root_node = ?path_info.node))]
+    async fn put(&self, path_info: PathInfo) -> Result<PathInfo, Error> {
+        // Call validate on the received PathInfo message.
+        let store_path = path_info
+            .validate()
+            .map_err(|e| Error::InvalidRequest(format!("failed to validate PathInfo: {}", e)))?;
+
+        // In case the PathInfo is valid, we were able to parse a StorePath.
+        // Store it in the database, keyed by its digest.
+        // This overwrites existing PathInfo objects.
+        self.db
+            .insert(store_path.digest(), path_info.encode_to_vec())
+            .map_err(|e| {
+                warn!("failed to insert PathInfo: {}", e);
+                Error::StorageError(format! {
+                    "failed to insert PathInfo: {}", e
+                })
+            })?;
+
+        Ok(path_info)
+    }
+
+    #[instrument(level = "trace", skip_all, fields(root_node = ?root_node))]
+    async fn calculate_nar(
+        &self,
+        root_node: &castorepb::node::Node,
+    ) -> Result<(u64, [u8; 32]), Error> {
+        calculate_size_and_sha256(root_node, &self.blob_service, &self.directory_service)
+            .await
+            .map_err(|e| Error::StorageError(e.to_string()))
+    }
+
+    fn list(&self) -> BoxStream<'static, Result<PathInfo, Error>> {
+        Box::pin(iter(self.db.iter().values().map(|v| {
+            let data = v.map_err(|e| {
+                warn!("failed to retrieve PathInfo: {}", e);
+                Error::StorageError(format!("failed to retrieve PathInfo: {}", e))
+            })?;
+
+            let path_info = PathInfo::decode(&*data).map_err(|e| {
+                warn!("failed to decode stored PathInfo: {}", e);
+                Error::StorageError(format!("failed to decode stored PathInfo: {}", e))
+            })?;
+            Ok(path_info)
+        })))
+    }
+}
diff --git a/tvix/store/src/pathinfoservice/tests/mod.rs b/tvix/store/src/pathinfoservice/tests/mod.rs
new file mode 100644
index 0000000000..9719371592
--- /dev/null
+++ b/tvix/store/src/pathinfoservice/tests/mod.rs
@@ -0,0 +1,113 @@
+//! This contains test scenarios that a given [PathInfoService] needs to pass.
+//! We use [rstest] and [rstest_reuse] to provide all services we want to test
+//! against, and then apply this template to all test functions.
+
+use rstest::*;
+use rstest_reuse::{self, *};
+use std::sync::Arc;
+use tvix_castore::proto as castorepb;
+use tvix_castore::{blobservice::BlobService, directoryservice::DirectoryService};
+
+use super::PathInfoService;
+use crate::proto::PathInfo;
+use crate::tests::fixtures::DUMMY_PATH_DIGEST;
+
+mod utils;
+use self::utils::make_grpc_path_info_service_client;
+
+/// Convenience type alias batching all three servives together.
+#[allow(clippy::upper_case_acronyms)]
+type BSDSPS = (
+    Arc<dyn BlobService>,
+    Arc<dyn DirectoryService>,
+    Box<dyn PathInfoService>,
+);
+
+/// Creates a PathInfoService using a new Memory{Blob,Directory}Service.
+/// We return a 3-tuple containing all of them, as some tests want to interact
+/// with all three.
+pub async fn make_path_info_service(uri: &str) -> BSDSPS {
+    let blob_service: Arc<dyn BlobService> = tvix_castore::blobservice::from_addr("memory://")
+        .await
+        .unwrap()
+        .into();
+    let directory_service: Arc<dyn DirectoryService> =
+        tvix_castore::directoryservice::from_addr("memory://")
+            .await
+            .unwrap()
+            .into();
+
+    (
+        blob_service.clone(),
+        directory_service.clone(),
+        crate::pathinfoservice::from_addr(uri, blob_service, directory_service)
+            .await
+            .unwrap(),
+    )
+}
+
+#[template]
+#[rstest]
+#[case::memory(make_path_info_service("memory://").await)]
+#[case::grpc(make_grpc_path_info_service_client().await)]
+#[case::sled(make_path_info_service("sled://").await)]
+#[cfg_attr(all(feature = "cloud",feature="integration"), case::bigtable(make_path_info_service("bigtable://instance-1?project_id=project-1&table_name=table-1&family_name=cf1").await))]
+pub fn path_info_services(
+    #[case] services: (
+        impl BlobService,
+        impl DirectoryService,
+        impl PathInfoService,
+    ),
+) {
+}
+
+// FUTUREWORK: add more tests rejecting invalid PathInfo messages.
+// A subset of them should also ensure references to other PathInfos, or
+// elements in {Blob,Directory}Service do exist.
+
+/// Trying to get a non-existent PathInfo should return Ok(None).
+#[apply(path_info_services)]
+#[tokio::test]
+async fn not_found(services: BSDSPS) {
+    let (_, _, path_info_service) = services;
+    assert!(path_info_service
+        .get(DUMMY_PATH_DIGEST)
+        .await
+        .expect("must succeed")
+        .is_none());
+}
+
+/// Put a PathInfo into the store, get it back.
+#[apply(path_info_services)]
+#[tokio::test]
+async fn put_get(services: BSDSPS) {
+    let (_, _, path_info_service) = services;
+
+    let path_info = PathInfo {
+        node: Some(castorepb::Node {
+            node: Some(castorepb::node::Node::Symlink(castorepb::SymlinkNode {
+                name: "00000000000000000000000000000000-foo".into(),
+                target: "doesntmatter".into(),
+            })),
+        }),
+        ..Default::default()
+    };
+
+    // insert
+    let resp = path_info_service
+        .put(path_info.clone())
+        .await
+        .expect("must succeed");
+
+    // expect the returned PathInfo to be equal (for now)
+    // in the future, some stores might add additional fields/signatures.
+    assert_eq!(path_info, resp);
+
+    // get it back
+    let resp = path_info_service
+        .get(DUMMY_PATH_DIGEST)
+        .await
+        .expect("must succeed");
+
+    assert_eq!(Some(path_info), resp);
+}
diff --git a/tvix/store/src/pathinfoservice/tests/utils.rs b/tvix/store/src/pathinfoservice/tests/utils.rs
new file mode 100644
index 0000000000..31ec57aade
--- /dev/null
+++ b/tvix/store/src/pathinfoservice/tests/utils.rs
@@ -0,0 +1,60 @@
+use std::sync::Arc;
+
+use tonic::transport::{Endpoint, Server, Uri};
+
+use crate::{
+    pathinfoservice::{GRPCPathInfoService, MemoryPathInfoService, PathInfoService},
+    proto::{
+        path_info_service_client::PathInfoServiceClient,
+        path_info_service_server::PathInfoServiceServer, GRPCPathInfoServiceWrapper,
+    },
+    tests::fixtures::{blob_service, directory_service},
+};
+
+/// Constructs and returns a gRPC PathInfoService.
+/// We also return memory-based {Blob,Directory}Service,
+/// as the consumer of this function accepts a 3-tuple.
+pub async fn make_grpc_path_info_service_client() -> super::BSDSPS {
+    let (left, right) = tokio::io::duplex(64);
+
+    let blob_service = blob_service();
+    let directory_service = directory_service();
+
+    // spin up a server, which will only connect once, to the left side.
+    tokio::spawn({
+        let blob_service = blob_service.clone();
+        let directory_service = directory_service.clone();
+        async move {
+            let path_info_service: Arc<dyn PathInfoService> =
+                Arc::from(MemoryPathInfoService::new(blob_service, directory_service));
+
+            // spin up a new DirectoryService
+            let mut server = Server::builder();
+            let router = server.add_service(PathInfoServiceServer::new(
+                GRPCPathInfoServiceWrapper::new(path_info_service),
+            ));
+
+            router
+                .serve_with_incoming(tokio_stream::once(Ok::<_, std::io::Error>(left)))
+                .await
+        }
+    });
+
+    // Create a client, connecting to the right side. The URI is unused.
+    let mut maybe_right = Some(right);
+
+    let path_info_service = Box::new(GRPCPathInfoService::from_client(
+        PathInfoServiceClient::new(
+            Endpoint::try_from("http://[::]:50051")
+                .unwrap()
+                .connect_with_connector(tower::service_fn(move |_: Uri| {
+                    let right = maybe_right.take().unwrap();
+                    async move { Ok::<_, std::io::Error>(right) }
+                }))
+                .await
+                .unwrap(),
+        ),
+    ));
+
+    (blob_service, directory_service, path_info_service)
+}
diff --git a/tvix/store/src/proto/grpc_pathinfoservice_wrapper.rs b/tvix/store/src/proto/grpc_pathinfoservice_wrapper.rs
new file mode 100644
index 0000000000..9f45818227
--- /dev/null
+++ b/tvix/store/src/proto/grpc_pathinfoservice_wrapper.rs
@@ -0,0 +1,121 @@
+use crate::nar::RenderError;
+use crate::pathinfoservice::PathInfoService;
+use crate::proto;
+use futures::{stream::BoxStream, TryStreamExt};
+use std::ops::Deref;
+use tonic::{async_trait, Request, Response, Result, Status};
+use tracing::{instrument, warn};
+use tvix_castore::proto as castorepb;
+
+pub struct GRPCPathInfoServiceWrapper<PS> {
+    inner: PS,
+    // FUTUREWORK: allow exposing without allowing listing
+}
+
+impl<PS> GRPCPathInfoServiceWrapper<PS> {
+    pub fn new(path_info_service: PS) -> Self {
+        Self {
+            inner: path_info_service,
+        }
+    }
+}
+
+#[async_trait]
+impl<PS> proto::path_info_service_server::PathInfoService for GRPCPathInfoServiceWrapper<PS>
+where
+    PS: Deref<Target = dyn PathInfoService> + Send + Sync + 'static,
+{
+    type ListStream = BoxStream<'static, tonic::Result<proto::PathInfo, Status>>;
+
+    #[instrument(skip_all)]
+    async fn get(
+        &self,
+        request: Request<proto::GetPathInfoRequest>,
+    ) -> Result<Response<proto::PathInfo>> {
+        match request.into_inner().by_what {
+            None => Err(Status::unimplemented("by_what needs to be specified")),
+            Some(proto::get_path_info_request::ByWhat::ByOutputHash(output_digest)) => {
+                let digest: [u8; 20] = output_digest
+                    .to_vec()
+                    .try_into()
+                    .map_err(|_e| Status::invalid_argument("invalid output digest length"))?;
+                match self.inner.get(digest).await {
+                    Ok(None) => Err(Status::not_found("PathInfo not found")),
+                    Ok(Some(path_info)) => Ok(Response::new(path_info)),
+                    Err(e) => {
+                        warn!(err = %e, "failed to get PathInfo");
+                        Err(e.into())
+                    }
+                }
+            }
+        }
+    }
+
+    #[instrument(skip_all)]
+    async fn put(&self, request: Request<proto::PathInfo>) -> Result<Response<proto::PathInfo>> {
+        let path_info = request.into_inner();
+
+        // Store the PathInfo in the client. Clients MUST validate the data
+        // they receive, so we don't validate additionally here.
+        match self.inner.put(path_info).await {
+            Ok(path_info_new) => Ok(Response::new(path_info_new)),
+            Err(e) => {
+                warn!(err = %e, "failed to put PathInfo");
+                Err(e.into())
+            }
+        }
+    }
+
+    #[instrument(skip_all)]
+    async fn calculate_nar(
+        &self,
+        request: Request<castorepb::Node>,
+    ) -> Result<Response<proto::CalculateNarResponse>> {
+        match request.into_inner().node {
+            None => Err(Status::invalid_argument("no root node sent")),
+            Some(root_node) => {
+                if let Err(e) = root_node.validate() {
+                    warn!(err = %e, "invalid root node");
+                    Err(Status::invalid_argument("invalid root node"))?
+                }
+
+                match self.inner.calculate_nar(&root_node).await {
+                    Ok((nar_size, nar_sha256)) => Ok(Response::new(proto::CalculateNarResponse {
+                        nar_size,
+                        nar_sha256: nar_sha256.to_vec().into(),
+                    })),
+                    Err(e) => {
+                        warn!(err = %e, "error during NAR calculation");
+                        Err(e.into())
+                    }
+                }
+            }
+        }
+    }
+
+    #[instrument(skip_all, err)]
+    async fn list(
+        &self,
+        _request: Request<proto::ListPathInfoRequest>,
+    ) -> Result<Response<Self::ListStream>, Status> {
+        let stream = Box::pin(
+            self.inner
+                .list()
+                .map_err(|e| Status::internal(e.to_string())),
+        );
+
+        Ok(Response::new(Box::pin(stream)))
+    }
+}
+
+impl From<RenderError> for tonic::Status {
+    fn from(value: RenderError) -> Self {
+        match value {
+            RenderError::BlobNotFound(_, _) => Self::not_found(value.to_string()),
+            RenderError::DirectoryNotFound(_, _) => Self::not_found(value.to_string()),
+            RenderError::NARWriterError(_) => Self::internal(value.to_string()),
+            RenderError::StoreError(_) => Self::internal(value.to_string()),
+            RenderError::UnexpectedBlobMeta(_, _, _, _) => Self::internal(value.to_string()),
+        }
+    }
+}
diff --git a/tvix/store/src/proto/mod.rs b/tvix/store/src/proto/mod.rs
new file mode 100644
index 0000000000..a09839c8bd
--- /dev/null
+++ b/tvix/store/src/proto/mod.rs
@@ -0,0 +1,374 @@
+#![allow(clippy::derive_partial_eq_without_eq, non_snake_case)]
+use bstr::ByteSlice;
+use bytes::Bytes;
+use data_encoding::BASE64;
+// https://github.com/hyperium/tonic/issues/1056
+use nix_compat::{
+    narinfo::Flags,
+    nixhash::{CAHash, NixHash},
+    store_path::{self, StorePathRef},
+};
+use thiserror::Error;
+use tvix_castore::proto::{self as castorepb, NamedNode, ValidateNodeError};
+
+mod grpc_pathinfoservice_wrapper;
+
+pub use grpc_pathinfoservice_wrapper::GRPCPathInfoServiceWrapper;
+
+tonic::include_proto!("tvix.store.v1");
+
+#[cfg(feature = "tonic-reflection")]
+/// Compiled file descriptors for implementing [gRPC
+/// reflection](https://github.com/grpc/grpc/blob/master/doc/server-reflection.md) with e.g.
+/// [`tonic_reflection`](https://docs.rs/tonic-reflection).
+pub const FILE_DESCRIPTOR_SET: &[u8] = tonic::include_file_descriptor_set!("tvix.store.v1");
+
+#[cfg(test)]
+mod tests;
+
+/// Errors that can occur during the validation of PathInfo messages.
+#[derive(Debug, Error, PartialEq)]
+pub enum ValidatePathInfoError {
+    /// Invalid length of a reference
+    #[error("Invalid length of digest at position {}, expected {}, got {}", .0, store_path::DIGEST_SIZE, .1)]
+    InvalidReferenceDigestLen(usize, usize),
+
+    /// No node present
+    #[error("No node present")]
+    NoNodePresent,
+
+    /// Node fails validation
+    #[error("Invalid root node: {:?}", .0.to_string())]
+    InvalidRootNode(ValidateNodeError),
+
+    /// Invalid node name encountered. Root nodes in PathInfos have more strict name requirements
+    #[error("Failed to parse {} as StorePath: {1}", .0.to_str_lossy())]
+    InvalidNodeName(Vec<u8>, store_path::Error),
+
+    /// The digest in narinfo.nar_sha256 has an invalid len.
+    #[error("Invalid narinfo.nar_sha256 length: expected {}, got {}", 32, .0)]
+    InvalidNarSha256DigestLen(usize),
+
+    /// The number of references in the narinfo.reference_names field does not match
+    /// the number of references in the .references field.
+    #[error("Inconsistent Number of References: {0} (references) vs {1} (narinfo)")]
+    InconsistentNumberOfReferences(usize, usize),
+
+    /// A string in narinfo.reference_names does not parse to a [store_path::StorePath].
+    #[error("Invalid reference_name at position {0}: {1}")]
+    InvalidNarinfoReferenceName(usize, String),
+
+    /// The digest in the parsed `.narinfo.reference_names[i]` does not match
+    /// the one in `.references[i]`.`
+    #[error("digest in reference_name at position {} does not match digest in PathInfo, expected {}, got {}", .0, BASE64.encode(.1), BASE64.encode(.2))]
+    InconsistentNarinfoReferenceNameDigest(
+        usize,
+        [u8; store_path::DIGEST_SIZE],
+        [u8; store_path::DIGEST_SIZE],
+    ),
+
+    /// The deriver field is invalid.
+    #[error("deriver field is invalid: {0}")]
+    InvalidDeriverField(store_path::Error),
+}
+
+/// Parses a root node name.
+///
+/// On success, this returns the parsed [store_path::StorePathRef].
+/// On error, it returns an error generated from the supplied constructor.
+fn parse_node_name_root<E>(
+    name: &[u8],
+    err: fn(Vec<u8>, store_path::Error) -> E,
+) -> Result<store_path::StorePathRef<'_>, E> {
+    store_path::StorePathRef::from_bytes(name).map_err(|e| err(name.to_vec(), e))
+}
+
+impl PathInfo {
+    /// validate performs some checks on the PathInfo struct,
+    /// Returning either a [store_path::StorePath] of the root node, or a
+    /// [ValidatePathInfoError].
+    pub fn validate(&self) -> Result<store_path::StorePathRef<'_>, ValidatePathInfoError> {
+        // ensure the references have the right number of bytes.
+        for (i, reference) in self.references.iter().enumerate() {
+            if reference.len() != store_path::DIGEST_SIZE {
+                return Err(ValidatePathInfoError::InvalidReferenceDigestLen(
+                    i,
+                    reference.len(),
+                ));
+            }
+        }
+
+        // If there is a narinfo field populated…
+        if let Some(narinfo) = &self.narinfo {
+            // ensure the nar_sha256 digest has the correct length.
+            if narinfo.nar_sha256.len() != 32 {
+                return Err(ValidatePathInfoError::InvalidNarSha256DigestLen(
+                    narinfo.nar_sha256.len(),
+                ));
+            }
+
+            // ensure the number of references there matches PathInfo.references count.
+            if narinfo.reference_names.len() != self.references.len() {
+                return Err(ValidatePathInfoError::InconsistentNumberOfReferences(
+                    self.references.len(),
+                    narinfo.reference_names.len(),
+                ));
+            }
+
+            // parse references in reference_names.
+            for (i, reference_name_str) in narinfo.reference_names.iter().enumerate() {
+                // ensure thy parse as (non-absolute) store path
+                let reference_names_store_path = store_path::StorePath::from_bytes(
+                    reference_name_str.as_bytes(),
+                )
+                .map_err(|_| {
+                    ValidatePathInfoError::InvalidNarinfoReferenceName(
+                        i,
+                        reference_name_str.to_owned(),
+                    )
+                })?;
+
+                // ensure their digest matches the one at self.references[i].
+                {
+                    // This is safe, because we ensured the proper length earlier already.
+                    let reference_digest = self.references[i].to_vec().try_into().unwrap();
+
+                    if reference_names_store_path.digest() != &reference_digest {
+                        return Err(
+                            ValidatePathInfoError::InconsistentNarinfoReferenceNameDigest(
+                                i,
+                                reference_digest,
+                                *reference_names_store_path.digest(),
+                            ),
+                        );
+                    }
+                }
+
+                // If the Deriver field is populated, ensure it parses to a
+                // [store_path::StorePath].
+                // We can't check for it to *not* end with .drv, as the .drv files produced by
+                // recursive Nix end with multiple .drv suffixes, and only one is popped when
+                // converting to this field.
+                if let Some(deriver) = &narinfo.deriver {
+                    store_path::StorePathRef::from_name_and_digest(&deriver.name, &deriver.digest)
+                        .map_err(ValidatePathInfoError::InvalidDeriverField)?;
+                }
+            }
+        }
+
+        // Ensure there is a (root) node present, and it properly parses to a [store_path::StorePath].
+        let root_nix_path = match &self.node {
+            None | Some(castorepb::Node { node: None }) => {
+                Err(ValidatePathInfoError::NoNodePresent)?
+            }
+            Some(castorepb::Node { node: Some(node) }) => {
+                node.validate()
+                    .map_err(ValidatePathInfoError::InvalidRootNode)?;
+                // parse the name of the node itself and return
+                parse_node_name_root(node.get_name(), ValidatePathInfoError::InvalidNodeName)?
+            }
+        };
+
+        // return the root nix path
+        Ok(root_nix_path)
+    }
+
+    /// With self and its store path name, this reconstructs a
+    /// [nix_compat::narinfo::NarInfo<'_>].
+    /// It can be used to validate Signatures, or get back a (sparse) NarInfo
+    /// struct to prepare writing it out.
+    ///
+    /// It assumes self to be validated first, and will only return None if the
+    /// `narinfo` field is unpopulated.
+    ///
+    /// It does very little allocation (a Vec each for `signatures` and
+    /// `references`), the rest points to data owned elsewhere.
+    ///
+    /// Keep in mind this is not able to reconstruct all data present in the
+    /// NarInfo<'_>, as some of it is not stored at all:
+    /// - the `system`, `file_hash` and `file_size` fields are set to `None`.
+    /// - the URL is set to an empty string.
+    /// - Compression is set to "none"
+    ///
+    /// If you want to render it out to a string and be able to parse it back
+    /// in, at least URL *must* be set again.
+    pub fn to_narinfo<'a>(
+        &'a self,
+        store_path: store_path::StorePathRef<'a>,
+    ) -> Option<nix_compat::narinfo::NarInfo<'_>> {
+        let narinfo = &self.narinfo.as_ref()?;
+
+        Some(nix_compat::narinfo::NarInfo {
+            flags: Flags::empty(),
+            store_path,
+            nar_hash: narinfo
+                .nar_sha256
+                .as_ref()
+                .try_into()
+                .expect("invalid narhash"),
+            nar_size: narinfo.nar_size,
+            references: narinfo
+                .reference_names
+                .iter()
+                .map(|ref_name| {
+                    // This shouldn't pass validation
+                    StorePathRef::from_bytes(ref_name.as_bytes()).expect("invalid reference")
+                })
+                .collect(),
+            signatures: narinfo
+                .signatures
+                .iter()
+                .map(|sig| {
+                    nix_compat::narinfo::Signature::new(
+                        &sig.name,
+                        // This shouldn't pass validation
+                        sig.data[..].try_into().expect("invalid signature len"),
+                    )
+                })
+                .collect(),
+            ca: narinfo
+                .ca
+                .as_ref()
+                .map(|ca| ca.try_into().expect("invalid ca")),
+            system: None,
+            deriver: narinfo.deriver.as_ref().map(|deriver| {
+                StorePathRef::from_name_and_digest(&deriver.name, &deriver.digest)
+                    .expect("invalid deriver")
+            }),
+            url: "",
+            compression: Some("none"),
+            file_hash: None,
+            file_size: None,
+        })
+    }
+}
+
+/// Errors that can occur when converting from a [nar_info::Ca] to a (stricter)
+/// [nix_compat::nixhash::CAHash].
+#[derive(Debug, Error, PartialEq)]
+pub enum ConvertCAError {
+    /// Invalid length of a reference
+    #[error("Invalid digest length '{0}' for type {1}")]
+    InvalidReferenceDigestLen(usize, &'static str),
+    /// Unknown Hash type
+    #[error("Unknown hash type: {0}")]
+    UnknownHashType(i32),
+}
+
+impl TryFrom<&nar_info::Ca> for nix_compat::nixhash::CAHash {
+    type Error = ConvertCAError;
+
+    fn try_from(value: &nar_info::Ca) -> Result<Self, Self::Error> {
+        Ok(match value.r#type {
+            typ if typ == nar_info::ca::Hash::FlatMd5 as i32 => {
+                Self::Flat(NixHash::Md5(value.digest[..].try_into().map_err(|_| {
+                    ConvertCAError::InvalidReferenceDigestLen(value.digest.len(), "FlatMd5")
+                })?))
+            }
+            typ if typ == nar_info::ca::Hash::FlatSha1 as i32 => {
+                Self::Flat(NixHash::Sha1(value.digest[..].try_into().map_err(
+                    |_| ConvertCAError::InvalidReferenceDigestLen(value.digest.len(), "FlatSha1"),
+                )?))
+            }
+            typ if typ == nar_info::ca::Hash::FlatSha256 as i32 => {
+                Self::Flat(NixHash::Sha256(value.digest[..].try_into().map_err(
+                    |_| ConvertCAError::InvalidReferenceDigestLen(value.digest.len(), "FlatSha256"),
+                )?))
+            }
+            typ if typ == nar_info::ca::Hash::FlatSha512 as i32 => Self::Flat(NixHash::Sha512(
+                Box::new(value.digest[..].try_into().map_err(|_| {
+                    ConvertCAError::InvalidReferenceDigestLen(value.digest.len(), "FlatSha512")
+                })?),
+            )),
+            typ if typ == nar_info::ca::Hash::NarMd5 as i32 => {
+                Self::Nar(NixHash::Md5(value.digest[..].try_into().map_err(|_| {
+                    ConvertCAError::InvalidReferenceDigestLen(value.digest.len(), "NarMd5")
+                })?))
+            }
+            typ if typ == nar_info::ca::Hash::NarSha1 as i32 => {
+                Self::Nar(NixHash::Sha1(value.digest[..].try_into().map_err(
+                    |_| ConvertCAError::InvalidReferenceDigestLen(value.digest.len(), "NarSha1"),
+                )?))
+            }
+            typ if typ == nar_info::ca::Hash::NarSha256 as i32 => {
+                Self::Nar(NixHash::Sha256(value.digest[..].try_into().map_err(
+                    |_| ConvertCAError::InvalidReferenceDigestLen(value.digest.len(), "NarSha256"),
+                )?))
+            }
+            typ if typ == nar_info::ca::Hash::NarSha512 as i32 => Self::Nar(NixHash::Sha512(
+                Box::new(value.digest[..].try_into().map_err(|_| {
+                    ConvertCAError::InvalidReferenceDigestLen(value.digest.len(), "NarSha512")
+                })?),
+            )),
+            typ => return Err(ConvertCAError::UnknownHashType(typ)),
+        })
+    }
+}
+
+impl From<&nix_compat::nixhash::CAHash> for nar_info::ca::Hash {
+    fn from(value: &nix_compat::nixhash::CAHash) -> Self {
+        match value {
+            CAHash::Flat(NixHash::Md5(_)) => nar_info::ca::Hash::FlatMd5,
+            CAHash::Flat(NixHash::Sha1(_)) => nar_info::ca::Hash::FlatSha1,
+            CAHash::Flat(NixHash::Sha256(_)) => nar_info::ca::Hash::FlatSha256,
+            CAHash::Flat(NixHash::Sha512(_)) => nar_info::ca::Hash::FlatSha512,
+            CAHash::Nar(NixHash::Md5(_)) => nar_info::ca::Hash::NarMd5,
+            CAHash::Nar(NixHash::Sha1(_)) => nar_info::ca::Hash::NarSha1,
+            CAHash::Nar(NixHash::Sha256(_)) => nar_info::ca::Hash::NarSha256,
+            CAHash::Nar(NixHash::Sha512(_)) => nar_info::ca::Hash::NarSha512,
+            CAHash::Text(_) => nar_info::ca::Hash::TextSha256,
+        }
+    }
+}
+
+impl From<&nix_compat::nixhash::CAHash> for nar_info::Ca {
+    fn from(value: &nix_compat::nixhash::CAHash) -> Self {
+        nar_info::Ca {
+            r#type: Into::<nar_info::ca::Hash>::into(value) as i32,
+            digest: value.hash().digest_as_bytes().to_vec().into(),
+        }
+    }
+}
+
+impl From<&nix_compat::narinfo::NarInfo<'_>> for NarInfo {
+    /// Converts from a NarInfo (returned from the NARInfo parser) to the proto-
+    /// level NarInfo struct.
+    fn from(value: &nix_compat::narinfo::NarInfo<'_>) -> Self {
+        let signatures = value
+            .signatures
+            .iter()
+            .map(|sig| nar_info::Signature {
+                name: sig.name().to_string(),
+                data: Bytes::copy_from_slice(sig.bytes()),
+            })
+            .collect();
+
+        NarInfo {
+            nar_size: value.nar_size,
+            nar_sha256: Bytes::copy_from_slice(&value.nar_hash),
+            signatures,
+            reference_names: value.references.iter().map(|r| r.to_string()).collect(),
+            deriver: value.deriver.as_ref().map(|sp| StorePath {
+                name: sp.name().to_owned(),
+                digest: Bytes::copy_from_slice(sp.digest()),
+            }),
+            ca: value.ca.as_ref().map(|ca| ca.into()),
+        }
+    }
+}
+
+impl From<&nix_compat::narinfo::NarInfo<'_>> for PathInfo {
+    /// Converts from a NarInfo (returned from the NARInfo parser) to a PathInfo
+    /// struct with the node set to None.
+    fn from(value: &nix_compat::narinfo::NarInfo<'_>) -> Self {
+        Self {
+            node: None,
+            references: value
+                .references
+                .iter()
+                .map(|x| Bytes::copy_from_slice(x.digest()))
+                .collect(),
+            narinfo: Some(value.into()),
+        }
+    }
+}
diff --git a/tvix/store/src/proto/tests/mod.rs b/tvix/store/src/proto/tests/mod.rs
new file mode 100644
index 0000000000..c9c6702027
--- /dev/null
+++ b/tvix/store/src/proto/tests/mod.rs
@@ -0,0 +1 @@
+mod pathinfo;
diff --git a/tvix/store/src/proto/tests/pathinfo.rs b/tvix/store/src/proto/tests/pathinfo.rs
new file mode 100644
index 0000000000..4d0834878d
--- /dev/null
+++ b/tvix/store/src/proto/tests/pathinfo.rs
@@ -0,0 +1,431 @@
+use crate::proto::{nar_info::Signature, NarInfo, PathInfo, ValidatePathInfoError};
+use crate::tests::fixtures::*;
+use bytes::Bytes;
+use data_encoding::BASE64;
+use nix_compat::nixbase32;
+use nix_compat::store_path::{self, StorePathRef};
+use rstest::rstest;
+use tvix_castore::proto as castorepb;
+
+#[rstest]
+#[case::no_node(None, Err(ValidatePathInfoError::NoNodePresent))]
+#[case::no_node_2(Some(castorepb::Node { node: None}), Err(ValidatePathInfoError::NoNodePresent))]
+
+fn validate_pathinfo(
+    #[case] node: Option<castorepb::Node>,
+    #[case] exp_result: Result<StorePathRef, ValidatePathInfoError>,
+) {
+    // construct the PathInfo object
+    let p = PathInfo {
+        node,
+        ..Default::default()
+    };
+
+    assert_eq!(exp_result, p.validate());
+
+    let err = p.validate().expect_err("validation should fail");
+    assert!(matches!(err, ValidatePathInfoError::NoNodePresent));
+}
+
+#[rstest]
+#[case::ok(castorepb::DirectoryNode {
+        name: DUMMY_PATH.into(),
+        digest: DUMMY_DIGEST.clone().into(),
+        size: 0,
+}, Ok(StorePathRef::from_bytes(DUMMY_PATH.as_bytes()).unwrap()))]
+#[case::invalid_digest_length(castorepb::DirectoryNode {
+        name: DUMMY_PATH.into(),
+        digest: Bytes::new(),
+        size: 0,
+}, Err(ValidatePathInfoError::InvalidRootNode(castorepb::ValidateNodeError::InvalidDigestLen(0))))]
+#[case::invalid_node_name_no_storepath(castorepb::DirectoryNode {
+        name: "invalid".into(),
+        digest: DUMMY_DIGEST.clone().into(),
+        size: 0,
+}, Err(ValidatePathInfoError::InvalidNodeName(
+        "invalid".into(),
+        store_path::Error::InvalidLength
+)))]
+fn validate_directory(
+    #[case] directory_node: castorepb::DirectoryNode,
+    #[case] exp_result: Result<StorePathRef, ValidatePathInfoError>,
+) {
+    // construct the PathInfo object
+    let p = PathInfo {
+        node: Some(castorepb::Node {
+            node: Some(castorepb::node::Node::Directory(directory_node)),
+        }),
+        ..Default::default()
+    };
+    assert_eq!(exp_result, p.validate());
+}
+
+#[rstest]
+#[case::ok(
+    castorepb::FileNode {
+        name: DUMMY_PATH.into(),
+        digest: DUMMY_DIGEST.clone().into(),
+        size: 0,
+        executable: false,
+    },
+    Ok(StorePathRef::from_bytes(DUMMY_PATH.as_bytes()).unwrap())
+)]
+#[case::invalid_digest_len(
+    castorepb::FileNode {
+        name: DUMMY_PATH.into(),
+        digest: Bytes::new(),
+        ..Default::default()
+    },
+    Err(ValidatePathInfoError::InvalidRootNode(castorepb::ValidateNodeError::InvalidDigestLen(0)))
+)]
+#[case::invalid_node_name(
+    castorepb::FileNode {
+        name: "invalid".into(),
+        digest: DUMMY_DIGEST.clone().into(),
+        ..Default::default()
+    },
+    Err(ValidatePathInfoError::InvalidNodeName(
+        "invalid".into(),
+        store_path::Error::InvalidLength
+    ))
+)]
+fn validate_file(
+    #[case] file_node: castorepb::FileNode,
+    #[case] exp_result: Result<StorePathRef, ValidatePathInfoError>,
+) {
+    // construct the PathInfo object
+    let p = PathInfo {
+        node: Some(castorepb::Node {
+            node: Some(castorepb::node::Node::File(file_node)),
+        }),
+        ..Default::default()
+    };
+    assert_eq!(exp_result, p.validate());
+}
+
+#[rstest]
+#[case::ok(
+    castorepb::SymlinkNode {
+        name: DUMMY_PATH.into(),
+        target: "foo".into(),
+    },
+    Ok(StorePathRef::from_bytes(DUMMY_PATH.as_bytes()).unwrap())
+)]
+#[case::invalid_node_name(
+    castorepb::SymlinkNode {
+        name: "invalid".into(),
+        target: "foo".into(),
+    },
+    Err(ValidatePathInfoError::InvalidNodeName(
+        "invalid".into(),
+        store_path::Error::InvalidLength
+    ))
+)]
+fn validate_symlink(
+    #[case] symlink_node: castorepb::SymlinkNode,
+    #[case] exp_result: Result<StorePathRef, ValidatePathInfoError>,
+) {
+    // construct the PathInfo object
+    let p = PathInfo {
+        node: Some(castorepb::Node {
+            node: Some(castorepb::node::Node::Symlink(symlink_node)),
+        }),
+        ..Default::default()
+    };
+    assert_eq!(exp_result, p.validate());
+}
+
+/// Ensure parsing a correct PathInfo without narinfo populated succeeds.
+#[test]
+fn validate_references_without_narinfo_ok() {
+    assert!(PATH_INFO_WITHOUT_NARINFO.validate().is_ok());
+}
+
+/// Ensure parsing a correct PathInfo with narinfo populated succeeds.
+#[test]
+fn validate_references_with_narinfo_ok() {
+    assert!(PATH_INFO_WITH_NARINFO.validate().is_ok());
+}
+
+/// Create a PathInfo with a wrong digest length in narinfo.nar_sha256, and
+/// ensure validation fails.
+#[test]
+fn validate_wrong_nar_sha256() {
+    let mut path_info = PATH_INFO_WITH_NARINFO.clone();
+    path_info.narinfo.as_mut().unwrap().nar_sha256 = vec![0xbe, 0xef].into();
+
+    match path_info.validate().expect_err("must_fail") {
+        ValidatePathInfoError::InvalidNarSha256DigestLen(2) => {}
+        e => panic!("unexpected error: {:?}", e),
+    };
+}
+
+/// Create a PathInfo with a wrong count of narinfo.reference_names,
+/// and ensure validation fails.
+#[test]
+fn validate_inconsistent_num_refs_fail() {
+    let mut path_info = PATH_INFO_WITH_NARINFO.clone();
+    path_info.narinfo.as_mut().unwrap().reference_names = vec![];
+
+    match path_info.validate().expect_err("must_fail") {
+        ValidatePathInfoError::InconsistentNumberOfReferences(1, 0) => {}
+        e => panic!("unexpected error: {:?}", e),
+    };
+}
+
+/// Create a PathInfo with a wrong digest length in references.
+#[test]
+fn validate_invalid_reference_digest_len() {
+    let mut path_info = PATH_INFO_WITHOUT_NARINFO.clone();
+    path_info.references.push(vec![0xff, 0xff].into());
+
+    match path_info.validate().expect_err("must fail") {
+        ValidatePathInfoError::InvalidReferenceDigestLen(
+            1, // position
+            2, // unexpected digest len
+        ) => {}
+        e => panic!("unexpected error: {:?}", e),
+    };
+}
+
+/// Create a PathInfo with a narinfo.reference_name[1] that is no valid store path.
+#[test]
+fn validate_invalid_narinfo_reference_name() {
+    let mut path_info = PATH_INFO_WITH_NARINFO.clone();
+
+    // This is invalid, as the store prefix is not part of reference_names.
+    path_info.narinfo.as_mut().unwrap().reference_names[0] =
+        "/nix/store/00000000000000000000000000000000-dummy".to_string();
+
+    match path_info.validate().expect_err("must fail") {
+        ValidatePathInfoError::InvalidNarinfoReferenceName(0, reference_name) => {
+            assert_eq!(
+                "/nix/store/00000000000000000000000000000000-dummy",
+                reference_name
+            );
+        }
+        e => panic!("unexpected error: {:?}", e),
+    }
+}
+
+/// Create a PathInfo with a narinfo.reference_name[0] that doesn't match references[0].
+#[test]
+fn validate_inconsistent_narinfo_reference_name_digest() {
+    let mut path_info = PATH_INFO_WITH_NARINFO.clone();
+
+    // mutate the first reference, they were all zeroes before
+    path_info.references[0] = vec![0xff; store_path::DIGEST_SIZE].into();
+
+    match path_info.validate().expect_err("must fail") {
+        ValidatePathInfoError::InconsistentNarinfoReferenceNameDigest(0, e_expected, e_actual) => {
+            assert_eq!(path_info.references[0][..], e_expected[..]);
+            assert_eq!(DUMMY_PATH_DIGEST, e_actual);
+        }
+        e => panic!("unexpected error: {:?}", e),
+    }
+}
+
+/// Create a node with an empty symlink target, and ensure it fails validation.
+#[test]
+fn validate_symlink_empty_target_invalid() {
+    let node = castorepb::node::Node::Symlink(castorepb::SymlinkNode {
+        name: "foo".into(),
+        target: "".into(),
+    });
+
+    node.validate().expect_err("must fail validation");
+}
+
+/// Create a node with a symlink target including null bytes, and ensure it
+/// fails validation.
+#[test]
+fn validate_symlink_target_null_byte_invalid() {
+    let node = castorepb::node::Node::Symlink(castorepb::SymlinkNode {
+        name: "foo".into(),
+        target: "foo\0".into(),
+    });
+
+    node.validate().expect_err("must fail validation");
+}
+
+/// Create a PathInfo with a correct deriver field and ensure it succeeds.
+#[test]
+fn validate_valid_deriver() {
+    let mut path_info = PATH_INFO_WITH_NARINFO.clone();
+
+    // add a valid deriver
+    let narinfo = path_info.narinfo.as_mut().unwrap();
+    narinfo.deriver = Some(crate::proto::StorePath {
+        name: "foo".to_string(),
+        digest: Bytes::from(DUMMY_PATH_DIGEST.as_slice()),
+    });
+
+    path_info.validate().expect("must validate");
+}
+
+/// Create a PathInfo with a broken deriver field and ensure it fails.
+#[test]
+fn validate_invalid_deriver() {
+    let mut path_info = PATH_INFO_WITH_NARINFO.clone();
+
+    // add a broken deriver (invalid digest)
+    let narinfo = path_info.narinfo.as_mut().unwrap();
+    narinfo.deriver = Some(crate::proto::StorePath {
+        name: "foo".to_string(),
+        digest: vec![].into(),
+    });
+
+    match path_info.validate().expect_err("must fail validation") {
+        ValidatePathInfoError::InvalidDeriverField(_) => {}
+        e => panic!("unexpected error: {:?}", e),
+    }
+}
+
+#[test]
+fn from_nixcompat_narinfo() {
+    let narinfo_parsed = nix_compat::narinfo::NarInfo::parse(
+        r#"StorePath: /nix/store/s66mzxpvicwk07gjbjfw9izjfa797vsw-hello-2.12.1
+URL: nar/1nhgq6wcggx0plpy4991h3ginj6hipsdslv4fd4zml1n707j26yq.nar.xz
+Compression: xz
+FileHash: sha256:1nhgq6wcggx0plpy4991h3ginj6hipsdslv4fd4zml1n707j26yq
+FileSize: 50088
+NarHash: sha256:0yzhigwjl6bws649vcs2asa4lbs8hg93hyix187gc7s7a74w5h80
+NarSize: 226488
+References: 3n58xw4373jp0ljirf06d8077j15pc4j-glibc-2.37-8 s66mzxpvicwk07gjbjfw9izjfa797vsw-hello-2.12.1
+Deriver: ib3sh3pcz10wsmavxvkdbayhqivbghlq-hello-2.12.1.drv
+Sig: cache.nixos.org-1:8ijECciSFzWHwwGVOIVYdp2fOIOJAfmzGHPQVwpktfTQJF6kMPPDre7UtFw3o+VqenC5P8RikKOAAfN7CvPEAg=="#).expect("must parse");
+
+    assert_eq!(
+        PathInfo {
+            node: None,
+            references: vec![
+                Bytes::copy_from_slice(&nixbase32::decode_fixed::<20>("3n58xw4373jp0ljirf06d8077j15pc4j").unwrap()),
+                Bytes::copy_from_slice(&nixbase32::decode_fixed::<20>("s66mzxpvicwk07gjbjfw9izjfa797vsw").unwrap()),
+            ],
+            narinfo: Some(
+                NarInfo {
+                    nar_size: 226488,
+                    nar_sha256: Bytes::copy_from_slice(
+                        &nixbase32::decode_fixed::<32>("0yzhigwjl6bws649vcs2asa4lbs8hg93hyix187gc7s7a74w5h80".as_bytes())
+                            .unwrap()
+                    ),
+                    signatures: vec![Signature {
+                        name: "cache.nixos.org-1".to_string(),
+                        data: BASE64.decode("8ijECciSFzWHwwGVOIVYdp2fOIOJAfmzGHPQVwpktfTQJF6kMPPDre7UtFw3o+VqenC5P8RikKOAAfN7CvPEAg==".as_bytes()).unwrap().into(),
+                    }],
+                    reference_names: vec![
+                        "3n58xw4373jp0ljirf06d8077j15pc4j-glibc-2.37-8".to_string(),
+                        "s66mzxpvicwk07gjbjfw9izjfa797vsw-hello-2.12.1".to_string()
+                    ],
+                    deriver: Some(crate::proto::StorePath {
+                        digest: Bytes::copy_from_slice(&nixbase32::decode_fixed::<20>("ib3sh3pcz10wsmavxvkdbayhqivbghlq").unwrap()),
+                        name: "hello-2.12.1".to_string(),
+                     }),
+                    ca: None,
+                }
+            )
+        },
+        (&narinfo_parsed).into(),
+    );
+}
+
+#[test]
+fn from_nixcompat_narinfo_fod() {
+    let narinfo_parsed = nix_compat::narinfo::NarInfo::parse(
+        r#"StorePath: /nix/store/pa10z4ngm0g83kx9mssrqzz30s84vq7k-hello-2.12.1.tar.gz
+URL: nar/1zjrhzhaizsrlsvdkqfl073vivmxcqnzkff4s50i0cdf541ary1r.nar.xz
+Compression: xz
+FileHash: sha256:1zjrhzhaizsrlsvdkqfl073vivmxcqnzkff4s50i0cdf541ary1r
+FileSize: 1033524
+NarHash: sha256:1lvqpbk2k1sb39z8jfxixf7p7v8sj4z6mmpa44nnmff3w1y6h8lh
+NarSize: 1033416
+References: 
+Deriver: dyivpmlaq2km6c11i0s6bi6mbsx0ylqf-hello-2.12.1.tar.gz.drv
+Sig: cache.nixos.org-1:ywnIG629nQZQhEr6/HLDrLT/mUEp5J1LC6NmWSlJRWL/nM7oGItJQUYWGLvYGhSQvHrhIuvMpjNmBNh/WWqCDg==
+CA: fixed:sha256:086vqwk2wl8zfs47sq2xpjc9k066ilmb8z6dn0q6ymwjzlm196cd"#
+    ).expect("must parse");
+
+    assert_eq!(
+        PathInfo {
+            node: None,
+            references: vec![],
+            narinfo: Some(
+                NarInfo {
+                    nar_size: 1033416,
+                    nar_sha256: Bytes::copy_from_slice(
+                        &nixbase32::decode_fixed::<32>(
+                            "1lvqpbk2k1sb39z8jfxixf7p7v8sj4z6mmpa44nnmff3w1y6h8lh"
+                        )
+                        .unwrap()
+                    ),
+                    signatures: vec![Signature {
+                        name: "cache.nixos.org-1".to_string(),
+                        data: BASE64
+                            .decode("ywnIG629nQZQhEr6/HLDrLT/mUEp5J1LC6NmWSlJRWL/nM7oGItJQUYWGLvYGhSQvHrhIuvMpjNmBNh/WWqCDg==".as_bytes())
+                            .unwrap()
+                            .into(),
+                    }],
+                    reference_names: vec![],
+                    deriver: Some(crate::proto::StorePath {
+                        digest: Bytes::copy_from_slice(
+                            &nixbase32::decode_fixed::<20>("dyivpmlaq2km6c11i0s6bi6mbsx0ylqf").unwrap()
+                        ),
+                        name: "hello-2.12.1.tar.gz".to_string(),
+                    }),
+                    ca: Some(crate::proto::nar_info::Ca {
+                        r#type: crate::proto::nar_info::ca::Hash::FlatSha256.into(),
+                        digest: Bytes::copy_from_slice(
+                            &nixbase32::decode_fixed::<32>(
+                                "086vqwk2wl8zfs47sq2xpjc9k066ilmb8z6dn0q6ymwjzlm196cd"
+                            )
+                            .unwrap()
+                        )
+                    }),
+                }
+            ),
+        },
+        (&narinfo_parsed).into()
+    );
+}
+
+/// Exercise .as_narinfo() on a PathInfo and ensure important fields are preserved..
+#[test]
+fn as_narinfo() {
+    let narinfo_parsed = nix_compat::narinfo::NarInfo::parse(
+        r#"StorePath: /nix/store/pa10z4ngm0g83kx9mssrqzz30s84vq7k-hello-2.12.1.tar.gz
+URL: nar/1zjrhzhaizsrlsvdkqfl073vivmxcqnzkff4s50i0cdf541ary1r.nar.xz
+Compression: xz
+FileHash: sha256:1zjrhzhaizsrlsvdkqfl073vivmxcqnzkff4s50i0cdf541ary1r
+FileSize: 1033524
+NarHash: sha256:1lvqpbk2k1sb39z8jfxixf7p7v8sj4z6mmpa44nnmff3w1y6h8lh
+NarSize: 1033416
+References: 
+Deriver: dyivpmlaq2km6c11i0s6bi6mbsx0ylqf-hello-2.12.1.tar.gz.drv
+Sig: cache.nixos.org-1:ywnIG629nQZQhEr6/HLDrLT/mUEp5J1LC6NmWSlJRWL/nM7oGItJQUYWGLvYGhSQvHrhIuvMpjNmBNh/WWqCDg==
+CA: fixed:sha256:086vqwk2wl8zfs47sq2xpjc9k066ilmb8z6dn0q6ymwjzlm196cd"#
+    ).expect("must parse");
+
+    let path_info: PathInfo = (&narinfo_parsed).into();
+
+    let mut narinfo_returned = path_info
+        .to_narinfo(
+            StorePathRef::from_bytes(b"pa10z4ngm0g83kx9mssrqzz30s84vq7k-hello-2.12.1.tar.gz")
+                .expect("invalid storepath"),
+        )
+        .expect("must be some");
+    narinfo_returned.url = "some.nar";
+
+    assert_eq!(
+        r#"StorePath: /nix/store/pa10z4ngm0g83kx9mssrqzz30s84vq7k-hello-2.12.1.tar.gz
+URL: some.nar
+Compression: none
+NarHash: sha256:1lvqpbk2k1sb39z8jfxixf7p7v8sj4z6mmpa44nnmff3w1y6h8lh
+NarSize: 1033416
+References: 
+Deriver: dyivpmlaq2km6c11i0s6bi6mbsx0ylqf-hello-2.12.1.tar.gz.drv
+Sig: cache.nixos.org-1:ywnIG629nQZQhEr6/HLDrLT/mUEp5J1LC6NmWSlJRWL/nM7oGItJQUYWGLvYGhSQvHrhIuvMpjNmBNh/WWqCDg==
+CA: fixed:sha256:086vqwk2wl8zfs47sq2xpjc9k066ilmb8z6dn0q6ymwjzlm196cd
+"#,
+        narinfo_returned.to_string(),
+    );
+}
diff --git a/tvix/store/src/tests/fixtures.rs b/tvix/store/src/tests/fixtures.rs
new file mode 100644
index 0000000000..1c8359a2c0
--- /dev/null
+++ b/tvix/store/src/tests/fixtures.rs
@@ -0,0 +1,142 @@
+use lazy_static::lazy_static;
+use rstest::*;
+use std::sync::Arc;
+pub use tvix_castore::fixtures::*;
+use tvix_castore::{
+    blobservice::{BlobService, MemoryBlobService},
+    directoryservice::{DirectoryService, MemoryDirectoryService},
+    proto as castorepb,
+};
+
+use crate::proto::{
+    nar_info::{ca, Ca},
+    NarInfo, PathInfo,
+};
+
+pub const DUMMY_PATH: &str = "00000000000000000000000000000000-dummy";
+pub const DUMMY_PATH_DIGEST: [u8; 20] = [0; 20];
+
+lazy_static! {
+    /// The NAR representation of a symlink pointing to `/nix/store/somewhereelse`
+    pub static ref NAR_CONTENTS_SYMLINK: Vec<u8> = vec![
+        13, 0, 0, 0, 0, 0, 0, 0, b'n', b'i', b'x', b'-', b'a', b'r', b'c', b'h', b'i', b'v', b'e', b'-', b'1', 0,
+        0, 0, // "nix-archive-1"
+        1, 0, 0, 0, 0, 0, 0, 0, b'(', 0, 0, 0, 0, 0, 0, 0, // "("
+        4, 0, 0, 0, 0, 0, 0, 0, b't', b'y', b'p', b'e', 0, 0, 0, 0, // "type"
+        7, 0, 0, 0, 0, 0, 0, 0, b's', b'y', b'm', b'l', b'i', b'n', b'k', 0, // "symlink"
+        6, 0, 0, 0, 0, 0, 0, 0, b't', b'a', b'r', b'g', b'e', b't', 0, 0, // target
+        24, 0, 0, 0, 0, 0, 0, 0, b'/', b'n', b'i', b'x', b'/', b's', b't', b'o', b'r', b'e', b'/', b's', b'o',
+        b'm', b'e', b'w', b'h', b'e', b'r', b'e', b'e', b'l', b's',
+        b'e', // "/nix/store/somewhereelse"
+        1, 0, 0, 0, 0, 0, 0, 0, b')', 0, 0, 0, 0, 0, 0, 0 // ")"
+    ];
+
+    /// The NAR representation of a regular file with the contents "Hello World!"
+    pub static ref NAR_CONTENTS_HELLOWORLD: Vec<u8> = vec![
+        13, 0, 0, 0, 0, 0, 0, 0, b'n', b'i', b'x', b'-', b'a', b'r', b'c', b'h', b'i', b'v', b'e', b'-', b'1', 0,
+        0, 0, // "nix-archive-1"
+        1, 0, 0, 0, 0, 0, 0, 0, b'(', 0, 0, 0, 0, 0, 0, 0, // "("
+        4, 0, 0, 0, 0, 0, 0, 0, b't', b'y', b'p', b'e', 0, 0, 0, 0, // "type"
+        7, 0, 0, 0, 0, 0, 0, 0, b'r', b'e', b'g', b'u', b'l', b'a', b'r', 0, // "regular"
+        8, 0, 0, 0, 0, 0, 0, 0, b'c', b'o', b'n', b't', b'e', b'n', b't', b's', // "contents"
+        12, 0, 0, 0, 0, 0, 0, 0, b'H', b'e', b'l', b'l', b'o', b' ', b'W', b'o', b'r', b'l', b'd', b'!', 0, 0,
+        0, 0, // "Hello World!"
+        1, 0, 0, 0, 0, 0, 0, 0, b')', 0, 0, 0, 0, 0, 0, 0 // ")"
+    ];
+
+    /// The NAR representation of a more complicated directory structure.
+    pub static ref NAR_CONTENTS_COMPLICATED: Vec<u8> = vec![
+        13, 0, 0, 0, 0, 0, 0, 0, b'n', b'i', b'x', b'-', b'a', b'r', b'c', b'h', b'i', b'v', b'e', b'-', b'1', 0,
+        0, 0, // "nix-archive-1"
+        1, 0, 0, 0, 0, 0, 0, 0, b'(', 0, 0, 0, 0, 0, 0, 0, // "("
+        4, 0, 0, 0, 0, 0, 0, 0, b't', b'y', b'p', b'e', 0, 0, 0, 0, // "type"
+        9, 0, 0, 0, 0, 0, 0, 0, b'd', b'i', b'r', b'e', b'c', b't', b'o', b'r', b'y', 0, 0, 0, 0, 0, 0, 0, // "directory"
+        5, 0, 0, 0, 0, 0, 0, 0, b'e', b'n', b't', b'r', b'y', 0, 0, 0, // "entry"
+        1, 0, 0, 0, 0, 0, 0, 0, b'(', 0, 0, 0, 0, 0, 0, 0, // "("
+        4, 0, 0, 0, 0, 0, 0, 0, b'n', b'a', b'm', b'e', 0, 0, 0, 0, // "name"
+        5, 0, 0, 0, 0, 0, 0, 0, b'.', b'k', b'e', b'e', b'p', 0, 0, 0, // ".keep"
+        4, 0, 0, 0, 0, 0, 0, 0, b'n', b'o', b'd', b'e', 0, 0, 0, 0, // "node"
+        1, 0, 0, 0, 0, 0, 0, 0, b'(', 0, 0, 0, 0, 0, 0, 0, // "("
+        4, 0, 0, 0, 0, 0, 0, 0, b't', b'y', b'p', b'e', 0, 0, 0, 0, // "type"
+        7, 0, 0, 0, 0, 0, 0, 0, b'r', b'e', b'g', b'u', b'l', b'a', b'r', 0, // "regular"
+        8, 0, 0, 0, 0, 0, 0, 0, b'c', b'o', b'n', b't', b'e', b'n', b't', b's', // "contents"
+        0, 0, 0, 0, 0, 0, 0, 0, // ""
+        1, 0, 0, 0, 0, 0, 0, 0, b')', 0, 0, 0, 0, 0, 0, 0, // ")"
+        1, 0, 0, 0, 0, 0, 0, 0, b')', 0, 0, 0, 0, 0, 0, 0, // ")"
+        5, 0, 0, 0, 0, 0, 0, 0, b'e', b'n', b't', b'r', b'y', 0, 0, 0, // "entry"
+        1, 0, 0, 0, 0, 0, 0, 0, b'(', 0, 0, 0, 0, 0, 0, 0, // "("
+        4, 0, 0, 0, 0, 0, 0, 0, b'n', b'a', b'm', b'e', 0, 0, 0, 0, // "name"
+        2, 0, 0, 0, 0, 0, 0, 0, b'a', b'a', 0, 0, 0, 0, 0, 0, // "aa"
+        4, 0, 0, 0, 0, 0, 0, 0, b'n', b'o', b'd', b'e', 0, 0, 0, 0, // "node"
+        1, 0, 0, 0, 0, 0, 0, 0, b'(', 0, 0, 0, 0, 0, 0, 0, // "("
+        4, 0, 0, 0, 0, 0, 0, 0, b't', b'y', b'p', b'e', 0, 0, 0, 0, // "type"
+        7, 0, 0, 0, 0, 0, 0, 0, b's', b'y', b'm', b'l', b'i', b'n', b'k', 0, // "symlink"
+        6, 0, 0, 0, 0, 0, 0, 0, b't', b'a', b'r', b'g', b'e', b't', 0, 0, // target
+        24, 0, 0, 0, 0, 0, 0, 0, b'/', b'n', b'i', b'x', b'/', b's', b't', b'o', b'r', b'e', b'/', b's', b'o',
+        b'm', b'e', b'w', b'h', b'e', b'r', b'e', b'e', b'l', b's',
+        b'e', // "/nix/store/somewhereelse"
+        1, 0, 0, 0, 0, 0, 0, 0, b')', 0, 0, 0, 0, 0, 0, 0, // ")"
+        1, 0, 0, 0, 0, 0, 0, 0, b')', 0, 0, 0, 0, 0, 0, 0, // ")"
+        5, 0, 0, 0, 0, 0, 0, 0, b'e', b'n', b't', b'r', b'y', 0, 0, 0, // "entry"
+        1, 0, 0, 0, 0, 0, 0, 0, b'(', 0, 0, 0, 0, 0, 0, 0, // "("
+        4, 0, 0, 0, 0, 0, 0, 0, b'n', b'a', b'm', b'e', 0, 0, 0, 0, // "name"
+        4, 0, 0, 0, 0, 0, 0, 0, b'k', b'e', b'e', b'p', 0, 0, 0, 0, // "keep"
+        4, 0, 0, 0, 0, 0, 0, 0, b'n', b'o', b'd', b'e', 0, 0, 0, 0, // "node"
+        1, 0, 0, 0, 0, 0, 0, 0, b'(', 0, 0, 0, 0, 0, 0, 0, // "("
+        4, 0, 0, 0, 0, 0, 0, 0, b't', b'y', b'p', b'e', 0, 0, 0, 0, // "type"
+        9, 0, 0, 0, 0, 0, 0, 0, b'd', b'i', b'r', b'e', b'c', b't', b'o', b'r', b'y', 0, 0, 0, 0, 0, 0, 0, // "directory"
+        5, 0, 0, 0, 0, 0, 0, 0, b'e', b'n', b't', b'r', b'y', 0, 0, 0, // "entry"
+        1, 0, 0, 0, 0, 0, 0, 0, b'(', 0, 0, 0, 0, 0, 0, 0, // "("
+        4, 0, 0, 0, 0, 0, 0, 0, b'n', b'a', b'm', b'e', 0, 0, 0, 0, // "name"
+        5, 0, 0, 0, 0, 0, 0, 0, 46, 107, 101, 101, 112, 0, 0, 0, // ".keep"
+        4, 0, 0, 0, 0, 0, 0, 0, 110, 111, 100, 101, 0, 0, 0, 0, // "node"
+        1, 0, 0, 0, 0, 0, 0, 0, b'(', 0, 0, 0, 0, 0, 0, 0, // "("
+        4, 0, 0, 0, 0, 0, 0, 0, b't', b'y', b'p', b'e', 0, 0, 0, 0, // "type"
+        7, 0, 0, 0, 0, 0, 0, 0, b'r', b'e', b'g', b'u', b'l', b'a', b'r', 0, // "regular"
+        8, 0, 0, 0, 0, 0, 0, 0, b'c', b'o', b'n', b't', b'e', b'n', b't', b's', // "contents"
+        0, 0, 0, 0, 0, 0, 0, 0, // ""
+        1, 0, 0, 0, 0, 0, 0, 0, b')', 0, 0, 0, 0, 0, 0, 0, // ")"
+        1, 0, 0, 0, 0, 0, 0, 0, b')', 0, 0, 0, 0, 0, 0, 0, // ")"
+        1, 0, 0, 0, 0, 0, 0, 0, b')', 0, 0, 0, 0, 0, 0, 0, // ")"
+        1, 0, 0, 0, 0, 0, 0, 0, b')', 0, 0, 0, 0, 0, 0, 0, // ")"
+        1, 0, 0, 0, 0, 0, 0, 0, b')', 0, 0, 0, 0, 0, 0, 0, // ")"
+    ];
+
+    /// A PathInfo message without .narinfo populated.
+    pub static ref PATH_INFO_WITHOUT_NARINFO : PathInfo = PathInfo {
+        node: Some(castorepb::Node {
+            node: Some(castorepb::node::Node::Directory(castorepb::DirectoryNode {
+                name: DUMMY_PATH.into(),
+                digest: DUMMY_DIGEST.clone().into(),
+                size: 0,
+            })),
+        }),
+        references: vec![DUMMY_PATH_DIGEST.as_slice().into()],
+        narinfo: None,
+    };
+
+    /// A PathInfo message with .narinfo populated.
+    /// The references in `narinfo.reference_names` aligns with what's in
+    /// `references`.
+    pub static ref PATH_INFO_WITH_NARINFO : PathInfo = PathInfo {
+        narinfo: Some(NarInfo {
+            nar_size: 0,
+            nar_sha256: DUMMY_DIGEST.clone().into(),
+            signatures: vec![],
+            reference_names: vec![DUMMY_PATH.to_string()],
+            deriver: None,
+            ca: Some(Ca { r#type: ca::Hash::NarSha256.into(), digest:  DUMMY_DIGEST.clone().into() })
+        }),
+      ..PATH_INFO_WITHOUT_NARINFO.clone()
+    };
+}
+
+#[fixture]
+pub(crate) fn blob_service() -> Arc<dyn BlobService> {
+    Arc::from(MemoryBlobService::default())
+}
+
+#[fixture]
+pub(crate) fn directory_service() -> Arc<dyn DirectoryService> {
+    Arc::from(MemoryDirectoryService::default())
+}
diff --git a/tvix/store/src/tests/mod.rs b/tvix/store/src/tests/mod.rs
new file mode 100644
index 0000000000..1e7fc3f6b4
--- /dev/null
+++ b/tvix/store/src/tests/mod.rs
@@ -0,0 +1,2 @@
+pub mod fixtures;
+mod nar_renderer;
diff --git a/tvix/store/src/tests/nar_renderer.rs b/tvix/store/src/tests/nar_renderer.rs
new file mode 100644
index 0000000000..8bfb5a72bb
--- /dev/null
+++ b/tvix/store/src/tests/nar_renderer.rs
@@ -0,0 +1,228 @@
+use crate::nar::calculate_size_and_sha256;
+use crate::nar::write_nar;
+use crate::tests::fixtures::blob_service;
+use crate::tests::fixtures::directory_service;
+use crate::tests::fixtures::*;
+use rstest::*;
+use sha2::{Digest, Sha256};
+use std::io;
+use std::sync::Arc;
+use tokio::io::sink;
+use tvix_castore::blobservice::BlobService;
+use tvix_castore::directoryservice::DirectoryService;
+use tvix_castore::proto as castorepb;
+
+#[rstest]
+#[tokio::test]
+async fn single_symlink(
+    blob_service: Arc<dyn BlobService>,
+    directory_service: Arc<dyn DirectoryService>,
+) {
+    let mut buf: Vec<u8> = vec![];
+
+    write_nar(
+        &mut buf,
+        &castorepb::node::Node::Symlink(castorepb::SymlinkNode {
+            name: "doesntmatter".into(),
+            target: "/nix/store/somewhereelse".into(),
+        }),
+        // don't put anything in the stores, as we don't actually do any requests.
+        blob_service,
+        directory_service,
+    )
+    .await
+    .expect("must succeed");
+
+    assert_eq!(buf, NAR_CONTENTS_SYMLINK.to_vec());
+}
+
+/// Make sure the NARRenderer fails if a referred blob doesn't exist.
+#[rstest]
+#[tokio::test]
+async fn single_file_missing_blob(
+    blob_service: Arc<dyn BlobService>,
+    directory_service: Arc<dyn DirectoryService>,
+) {
+    let e = write_nar(
+        sink(),
+        &castorepb::node::Node::File(castorepb::FileNode {
+            name: "doesntmatter".into(),
+            digest: HELLOWORLD_BLOB_DIGEST.clone().into(),
+            size: HELLOWORLD_BLOB_CONTENTS.len() as u64,
+            executable: false,
+        }),
+        // the blobservice is empty intentionally, to provoke the error.
+        blob_service,
+        directory_service,
+    )
+    .await
+    .expect_err("must fail");
+
+    match e {
+        crate::nar::RenderError::NARWriterError(e) => {
+            assert_eq!(io::ErrorKind::NotFound, e.kind());
+        }
+        _ => panic!("unexpected error: {:?}", e),
+    }
+}
+
+/// Make sure the NAR Renderer fails if the returned blob meta has another size
+/// than specified in the proto node.
+#[rstest]
+#[tokio::test]
+async fn single_file_wrong_blob_size(
+    blob_service: Arc<dyn BlobService>,
+    directory_service: Arc<dyn DirectoryService>,
+) {
+    // insert blob into the store
+    let mut writer = blob_service.open_write().await;
+    tokio::io::copy(
+        &mut io::Cursor::new(HELLOWORLD_BLOB_CONTENTS.to_vec()),
+        &mut writer,
+    )
+    .await
+    .unwrap();
+    assert_eq!(
+        HELLOWORLD_BLOB_DIGEST.clone(),
+        writer.close().await.unwrap()
+    );
+
+    // Test with a root FileNode of a too big size
+    let e = write_nar(
+        sink(),
+        &castorepb::node::Node::File(castorepb::FileNode {
+            name: "doesntmatter".into(),
+            digest: HELLOWORLD_BLOB_DIGEST.clone().into(),
+            size: 42, // <- note the wrong size here!
+            executable: false,
+        }),
+        blob_service.clone(),
+        directory_service.clone(),
+    )
+    .await
+    .expect_err("must fail");
+
+    match e {
+        crate::nar::RenderError::NARWriterError(e) => {
+            assert_eq!(io::ErrorKind::UnexpectedEof, e.kind());
+        }
+        _ => panic!("unexpected error: {:?}", e),
+    }
+
+    // Test with a root FileNode of a too small size
+    let e = write_nar(
+        sink(),
+        &castorepb::node::Node::File(castorepb::FileNode {
+            name: "doesntmatter".into(),
+            digest: HELLOWORLD_BLOB_DIGEST.clone().into(),
+            size: 2, // <- note the wrong size here!
+            executable: false,
+        }),
+        blob_service,
+        directory_service,
+    )
+    .await
+    .expect_err("must fail");
+
+    match e {
+        crate::nar::RenderError::NARWriterError(e) => {
+            assert_eq!(io::ErrorKind::InvalidInput, e.kind());
+        }
+        _ => panic!("unexpected error: {:?}", e),
+    }
+}
+
+#[rstest]
+#[tokio::test]
+async fn single_file(
+    blob_service: Arc<dyn BlobService>,
+    directory_service: Arc<dyn DirectoryService>,
+) {
+    // insert blob into the store
+    let mut writer = blob_service.open_write().await;
+    tokio::io::copy(&mut io::Cursor::new(HELLOWORLD_BLOB_CONTENTS), &mut writer)
+        .await
+        .unwrap();
+
+    assert_eq!(
+        HELLOWORLD_BLOB_DIGEST.clone(),
+        writer.close().await.unwrap()
+    );
+
+    let mut buf: Vec<u8> = vec![];
+
+    write_nar(
+        &mut buf,
+        &castorepb::node::Node::File(castorepb::FileNode {
+            name: "doesntmatter".into(),
+            digest: HELLOWORLD_BLOB_DIGEST.clone().into(),
+            size: HELLOWORLD_BLOB_CONTENTS.len() as u64,
+            executable: false,
+        }),
+        blob_service,
+        directory_service,
+    )
+    .await
+    .expect("must succeed");
+
+    assert_eq!(buf, NAR_CONTENTS_HELLOWORLD.to_vec());
+}
+
+#[rstest]
+#[tokio::test]
+async fn test_complicated(
+    blob_service: Arc<dyn BlobService>,
+    directory_service: Arc<dyn DirectoryService>,
+) {
+    // put all data into the stores.
+    // insert blob into the store
+    let mut writer = blob_service.open_write().await;
+    tokio::io::copy(&mut io::Cursor::new(EMPTY_BLOB_CONTENTS), &mut writer)
+        .await
+        .unwrap();
+    assert_eq!(EMPTY_BLOB_DIGEST.clone(), writer.close().await.unwrap());
+
+    // insert directories
+    directory_service
+        .put(DIRECTORY_WITH_KEEP.clone())
+        .await
+        .unwrap();
+    directory_service
+        .put(DIRECTORY_COMPLICATED.clone())
+        .await
+        .unwrap();
+
+    let mut buf: Vec<u8> = vec![];
+
+    write_nar(
+        &mut buf,
+        &castorepb::node::Node::Directory(castorepb::DirectoryNode {
+            name: "doesntmatter".into(),
+            digest: DIRECTORY_COMPLICATED.digest().into(),
+            size: DIRECTORY_COMPLICATED.size(),
+        }),
+        blob_service.clone(),
+        directory_service.clone(),
+    )
+    .await
+    .expect("must succeed");
+
+    assert_eq!(buf, NAR_CONTENTS_COMPLICATED.to_vec());
+
+    // ensure calculate_nar does return the correct sha256 digest and sum.
+    let (nar_size, nar_digest) = calculate_size_and_sha256(
+        &castorepb::node::Node::Directory(castorepb::DirectoryNode {
+            name: "doesntmatter".into(),
+            digest: DIRECTORY_COMPLICATED.digest().into(),
+            size: DIRECTORY_COMPLICATED.size(),
+        }),
+        blob_service,
+        directory_service,
+    )
+    .await
+    .expect("must succeed");
+
+    assert_eq!(NAR_CONTENTS_COMPLICATED.len() as u64, nar_size);
+    let d = Sha256::digest(NAR_CONTENTS_COMPLICATED.clone());
+    assert_eq!(d.as_slice(), nar_digest);
+}
diff --git a/tvix/store/src/utils.rs b/tvix/store/src/utils.rs
new file mode 100644
index 0000000000..0b171377bd
--- /dev/null
+++ b/tvix/store/src/utils.rs
@@ -0,0 +1,65 @@
+use std::sync::Arc;
+use std::{
+    pin::Pin,
+    task::{self, Poll},
+};
+use tokio::io::{self, AsyncWrite};
+
+use tvix_castore::{
+    blobservice::{self, BlobService},
+    directoryservice::{self, DirectoryService},
+};
+
+use crate::pathinfoservice::{self, PathInfoService};
+
+/// Construct the three store handles from their addrs.
+pub async fn construct_services(
+    blob_service_addr: impl AsRef<str>,
+    directory_service_addr: impl AsRef<str>,
+    path_info_service_addr: impl AsRef<str>,
+) -> std::io::Result<(
+    Arc<dyn BlobService>,
+    Arc<dyn DirectoryService>,
+    Box<dyn PathInfoService>,
+)> {
+    let blob_service: Arc<dyn BlobService> = blobservice::from_addr(blob_service_addr.as_ref())
+        .await?
+        .into();
+    let directory_service: Arc<dyn DirectoryService> =
+        directoryservice::from_addr(directory_service_addr.as_ref())
+            .await?
+            .into();
+    let path_info_service = pathinfoservice::from_addr(
+        path_info_service_addr.as_ref(),
+        blob_service.clone(),
+        directory_service.clone(),
+    )
+    .await?;
+
+    Ok((blob_service, directory_service, path_info_service))
+}
+
+/// The inverse of [tokio_util::io::SyncIoBridge].
+/// Don't use this with anything that actually does blocking I/O.
+pub struct AsyncIoBridge<T>(pub T);
+
+impl<W: std::io::Write + Unpin> AsyncWrite for AsyncIoBridge<W> {
+    fn poll_write(
+        self: Pin<&mut Self>,
+        _cx: &mut task::Context<'_>,
+        buf: &[u8],
+    ) -> Poll<io::Result<usize>> {
+        Poll::Ready(self.get_mut().0.write(buf))
+    }
+
+    fn poll_flush(self: Pin<&mut Self>, _cx: &mut task::Context<'_>) -> Poll<io::Result<()>> {
+        Poll::Ready(self.get_mut().0.flush())
+    }
+
+    fn poll_shutdown(
+        self: Pin<&mut Self>,
+        _cx: &mut task::Context<'_>,
+    ) -> Poll<Result<(), io::Error>> {
+        Poll::Ready(Ok(()))
+    }
+}
diff --git a/tvix/tools/crunch-v2/.gitignore b/tvix/tools/crunch-v2/.gitignore
new file mode 100644
index 0000000000..4bed5da93f
--- /dev/null
+++ b/tvix/tools/crunch-v2/.gitignore
@@ -0,0 +1 @@
+*.parquet
diff --git a/tvix/tools/crunch-v2/Cargo.lock b/tvix/tools/crunch-v2/Cargo.lock
new file mode 100644
index 0000000000..cff5509d0b
--- /dev/null
+++ b/tvix/tools/crunch-v2/Cargo.lock
@@ -0,0 +1,3214 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "addr2line"
+version = "0.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb"
+dependencies = [
+ "gimli",
+]
+
+[[package]]
+name = "adler"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+
+[[package]]
+name = "ahash"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a"
+dependencies = [
+ "cfg-if",
+ "getrandom",
+ "once_cell",
+ "version_check",
+ "zerocopy",
+]
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "alloc-no-stdlib"
+version = "2.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3"
+
+[[package]]
+name = "alloc-stdlib"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece"
+dependencies = [
+ "alloc-no-stdlib",
+]
+
+[[package]]
+name = "allocator-api2"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5"
+
+[[package]]
+name = "android-tzdata"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
+
+[[package]]
+name = "android_system_properties"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "anstream"
+version = "0.6.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5"
+dependencies = [
+ "anstyle",
+ "anstyle-parse",
+ "anstyle-query",
+ "anstyle-wincon",
+ "colorchoice",
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87"
+
+[[package]]
+name = "anstyle-parse"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
+dependencies = [
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle-query"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648"
+dependencies = [
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "anstyle-wincon"
+version = "3.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
+dependencies = [
+ "anstyle",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.75"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
+dependencies = [
+ "backtrace",
+]
+
+[[package]]
+name = "argminmax"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "202108b46429b765ef483f8a24d5c46f48c14acfdacc086dd4ab6dddf6bcdbd2"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "array-init-cursor"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf7d0a018de4f6aa429b9d33d69edf69072b1c5b1cb8d3e4a5f7ef898fc3eb76"
+
+[[package]]
+name = "arrayref"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545"
+
+[[package]]
+name = "arrayvec"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
+
+[[package]]
+name = "arrow-format"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07884ea216994cdc32a2d5f8274a8bee979cfe90274b83f86f440866ee3132c7"
+dependencies = [
+ "planus",
+ "serde",
+]
+
+[[package]]
+name = "async-stream"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51"
+dependencies = [
+ "async-stream-impl",
+ "futures-core",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "async-stream-impl"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.39",
+]
+
+[[package]]
+name = "async-trait"
+version = "0.1.74"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.39",
+]
+
+[[package]]
+name = "atoi"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "atoi_simd"
+version = "0.15.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc41b65e01b6851bdcd2d741824e6b310d571396bf3915e31e4792034ee65126"
+
+[[package]]
+name = "autocfg"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+
+[[package]]
+name = "backtrace"
+version = "0.3.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837"
+dependencies = [
+ "addr2line",
+ "cc",
+ "cfg-if",
+ "libc",
+ "miniz_oxide",
+ "object",
+ "rustc-demangle",
+]
+
+[[package]]
+name = "base64"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
+
+[[package]]
+name = "base64"
+version = "0.21.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9"
+
+[[package]]
+name = "base64ct"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "bitflags"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
+
+[[package]]
+name = "blake3"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0231f06152bf547e9c2b5194f247cd97aacf6dcd8b15d8e5ec0663f64580da87"
+dependencies = [
+ "arrayref",
+ "arrayvec",
+ "cc",
+ "cfg-if",
+ "constant_time_eq",
+]
+
+[[package]]
+name = "block-buffer"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "block-buffer"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "brotli"
+version = "3.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "516074a47ef4bce09577a3b379392300159ce5b1ba2e501ff1c819950066100f"
+dependencies = [
+ "alloc-no-stdlib",
+ "alloc-stdlib",
+ "brotli-decompressor",
+]
+
+[[package]]
+name = "brotli-decompressor"
+version = "2.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4e2e4afe60d7dd600fdd3de8d0f08c2b7ec039712e3b6137ff98b7004e82de4f"
+dependencies = [
+ "alloc-no-stdlib",
+ "alloc-stdlib",
+]
+
+[[package]]
+name = "bstr"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "542f33a8835a0884b006a0c3df3dadd99c0c3f296ed26c2fdc8028e01ad6230c"
+dependencies = [
+ "memchr",
+ "regex-automata",
+ "serde",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"
+
+[[package]]
+name = "bytemuck"
+version = "1.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6"
+dependencies = [
+ "bytemuck_derive",
+]
+
+[[package]]
+name = "bytemuck_derive"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "965ab7eb5f8f97d2a083c799f3a1b994fc397b2fe2da5d1da1626ce15a39f2b1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.39",
+]
+
+[[package]]
+name = "byteorder"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
+[[package]]
+name = "bytes"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
+
+[[package]]
+name = "bzip2"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8"
+dependencies = [
+ "bzip2-sys",
+ "libc",
+]
+
+[[package]]
+name = "bzip2-sys"
+version = "0.1.11+1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+]
+
+[[package]]
+name = "cc"
+version = "1.0.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
+dependencies = [
+ "jobserver",
+ "libc",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "chrono"
+version = "0.4.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38"
+dependencies = [
+ "android-tzdata",
+ "iana-time-zone",
+ "num-traits",
+ "serde",
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "clap"
+version = "4.4.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e578d6ec4194633722ccf9544794b71b1385c3c027efe0c55db226fc880865c"
+dependencies = [
+ "clap_builder",
+ "clap_derive",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.4.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4df4df40ec50c46000231c914968278b1eb05098cf8f1b3a518a95030e71d1c7"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "clap_lex",
+ "strsim",
+]
+
+[[package]]
+name = "clap_derive"
+version = "4.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.39",
+]
+
+[[package]]
+name = "clap_lex"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1"
+
+[[package]]
+name = "colorchoice"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
+
+[[package]]
+name = "console"
+version = "0.15.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8"
+dependencies = [
+ "encode_unicode",
+ "lazy_static",
+ "libc",
+ "unicode-width",
+ "windows-sys 0.45.0",
+]
+
+[[package]]
+name = "const-oid"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f"
+
+[[package]]
+name = "constant_time_eq"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2"
+
+[[package]]
+name = "core-foundation"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
+
+[[package]]
+name = "cpufeatures"
+version = "0.2.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crc32fast"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "crossbeam-channel"
+version = "0.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200"
+dependencies = [
+ "cfg-if",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-deque"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef"
+dependencies = [
+ "cfg-if",
+ "crossbeam-epoch",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.9.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7"
+dependencies = [
+ "autocfg",
+ "cfg-if",
+ "crossbeam-utils",
+ "memoffset",
+ "scopeguard",
+]
+
+[[package]]
+name = "crossbeam-queue"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add"
+dependencies = [
+ "cfg-if",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "crunch-v2"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "blake3",
+ "bstr",
+ "bytes",
+ "bzip2",
+ "clap",
+ "digest 0.10.7",
+ "fastcdc",
+ "futures",
+ "indicatif",
+ "lazy_static",
+ "nix-compat",
+ "polars",
+ "prost",
+ "prost-build",
+ "rusoto_core",
+ "rusoto_s3",
+ "sha2 0.10.8",
+ "sled",
+ "tokio",
+ "xz2",
+ "zstd",
+]
+
+[[package]]
+name = "crypto-common"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
+dependencies = [
+ "generic-array",
+ "typenum",
+]
+
+[[package]]
+name = "crypto-mac"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714"
+dependencies = [
+ "generic-array",
+ "subtle",
+]
+
+[[package]]
+name = "curve25519-dalek"
+version = "4.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e89b8c6a2e4b1f45971ad09761aafb85514a84744b67a95e32c3cc1352d1f65c"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "curve25519-dalek-derive",
+ "digest 0.10.7",
+ "fiat-crypto",
+ "platforms",
+ "rustc_version",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "curve25519-dalek-derive"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.39",
+]
+
+[[package]]
+name = "data-encoding"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"
+
+[[package]]
+name = "der"
+version = "0.7.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c"
+dependencies = [
+ "const-oid",
+ "zeroize",
+]
+
+[[package]]
+name = "digest"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "digest"
+version = "0.10.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
+dependencies = [
+ "block-buffer 0.10.4",
+ "crypto-common",
+]
+
+[[package]]
+name = "dirs-next"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
+dependencies = [
+ "cfg-if",
+ "dirs-sys-next",
+]
+
+[[package]]
+name = "dirs-sys-next"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
+dependencies = [
+ "libc",
+ "redox_users",
+ "winapi",
+]
+
+[[package]]
+name = "dyn-clone"
+version = "1.0.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "545b22097d44f8a9581187cdf93de7a71e4722bf51200cfaba810865b49a495d"
+
+[[package]]
+name = "ed25519"
+version = "2.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53"
+dependencies = [
+ "pkcs8",
+ "signature",
+]
+
+[[package]]
+name = "ed25519-dalek"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f628eaec48bfd21b865dc2950cfa014450c01d2fa2b69a86c2fd5844ec523c0"
+dependencies = [
+ "curve25519-dalek",
+ "ed25519",
+ "serde",
+ "sha2 0.10.8",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "either"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
+
+[[package]]
+name = "encode_unicode"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
+
+[[package]]
+name = "enum_dispatch"
+version = "0.3.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f33313078bb8d4d05a2733a94ac4c2d8a0df9a2b84424ebf4f33bfc224a890e"
+dependencies = [
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.39",
+]
+
+[[package]]
+name = "equivalent"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
+
+[[package]]
+name = "errno"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f258a7194e7f7c2a7837a8913aeab7fd8c383457034fa20ce4dd3dcb813e8eb8"
+dependencies = [
+ "libc",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "ethnum"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b90ca2580b73ab6a1f724b76ca11ab632df820fd6040c336200d2c1df7b3c82c"
+
+[[package]]
+name = "fallible-streaming-iterator"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
+
+[[package]]
+name = "fast-float"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95765f67b4b18863968b4a1bd5bb576f732b29a4a28c7cd84c09fa3e2875f33c"
+
+[[package]]
+name = "fastcdc"
+version = "3.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a71061d097bfa9a5a4d2efdec57990d9a88745020b365191d37e48541a1628f2"
+
+[[package]]
+name = "fastrand"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
+
+[[package]]
+name = "fiat-crypto"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "27573eac26f4dd11e2b1916c3fe1baa56407c83c71a773a8ba17ec0bca03b6b7"
+
+[[package]]
+name = "fixedbitset"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
+
+[[package]]
+name = "flate2"
+version = "1.0.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e"
+dependencies = [
+ "crc32fast",
+ "miniz_oxide",
+]
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "foreign_vec"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee1b05cbd864bcaecbd3455d6d967862d446e4ebfc3c2e5e5b9841e53cba6673"
+
+[[package]]
+name = "fs2"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "futures"
+version = "0.3.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-io",
+ "futures-sink",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c"
+
+[[package]]
+name = "futures-executor"
+version = "0.3.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-io"
+version = "0.3.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa"
+
+[[package]]
+name = "futures-macro"
+version = "0.3.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.39",
+]
+
+[[package]]
+name = "futures-sink"
+version = "0.3.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817"
+
+[[package]]
+name = "futures-task"
+version = "0.3.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2"
+
+[[package]]
+name = "futures-util"
+version = "0.3.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-macro",
+ "futures-sink",
+ "futures-task",
+ "memchr",
+ "pin-project-lite",
+ "pin-utils",
+ "slab",
+]
+
+[[package]]
+name = "fxhash"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
+dependencies = [
+ "byteorder",
+]
+
+[[package]]
+name = "generic-array"
+version = "0.14.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "libc",
+ "wasi",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "gimli"
+version = "0.28.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0"
+
+[[package]]
+name = "glob"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
+
+[[package]]
+name = "h2"
+version = "0.3.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178"
+dependencies = [
+ "bytes",
+ "fnv",
+ "futures-core",
+ "futures-sink",
+ "futures-util",
+ "http",
+ "indexmap",
+ "slab",
+ "tokio",
+ "tokio-util",
+ "tracing",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.14.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156"
+dependencies = [
+ "ahash",
+ "allocator-api2",
+ "rayon",
+]
+
+[[package]]
+name = "heck"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
+
+[[package]]
+name = "hermit-abi"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
+
+[[package]]
+name = "hex"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+
+[[package]]
+name = "hmac"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b"
+dependencies = [
+ "crypto-mac",
+ "digest 0.9.0",
+]
+
+[[package]]
+name = "home"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb"
+dependencies = [
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "http"
+version = "0.2.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb"
+dependencies = [
+ "bytes",
+ "fnv",
+ "itoa",
+]
+
+[[package]]
+name = "http-body"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1"
+dependencies = [
+ "bytes",
+ "http",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "httparse"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
+
+[[package]]
+name = "httpdate"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
+
+[[package]]
+name = "hyper"
+version = "0.14.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468"
+dependencies = [
+ "bytes",
+ "futures-channel",
+ "futures-core",
+ "futures-util",
+ "h2",
+ "http",
+ "http-body",
+ "httparse",
+ "httpdate",
+ "itoa",
+ "pin-project-lite",
+ "socket2 0.4.10",
+ "tokio",
+ "tower-service",
+ "tracing",
+ "want",
+]
+
+[[package]]
+name = "hyper-rustls"
+version = "0.23.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1788965e61b367cd03a62950836d5cd41560c3577d90e40e0819373194d1661c"
+dependencies = [
+ "http",
+ "hyper",
+ "log",
+ "rustls",
+ "rustls-native-certs",
+ "tokio",
+ "tokio-rustls",
+]
+
+[[package]]
+name = "iana-time-zone"
+version = "0.1.58"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "iana-time-zone-haiku",
+ "js-sys",
+ "wasm-bindgen",
+ "windows-core",
+]
+
+[[package]]
+name = "iana-time-zone-haiku"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "indexmap"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f"
+dependencies = [
+ "equivalent",
+ "hashbrown",
+]
+
+[[package]]
+name = "indicatif"
+version = "0.17.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb28741c9db9a713d93deb3bb9515c20788cef5815265bee4980e87bde7e0f25"
+dependencies = [
+ "console",
+ "instant",
+ "number_prefix",
+ "portable-atomic",
+ "unicode-width",
+]
+
+[[package]]
+name = "instant"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "itertools"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
+
+[[package]]
+name = "jobserver"
+version = "0.1.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "js-sys"
+version = "0.3.65"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "54c0c35952f67de54bb584e9fd912b3023117cbafc0a77d8f3dee1fb5f572fe8"
+dependencies = [
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "libc"
+version = "0.2.150"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c"
+
+[[package]]
+name = "libm"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058"
+
+[[package]]
+name = "libredox"
+version = "0.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8"
+dependencies = [
+ "bitflags 2.4.1",
+ "libc",
+ "redox_syscall 0.4.1",
+]
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.4.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829"
+
+[[package]]
+name = "lock_api"
+version = "0.4.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45"
+dependencies = [
+ "autocfg",
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
+
+[[package]]
+name = "lz4"
+version = "1.24.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e9e2dd86df36ce760a60f6ff6ad526f7ba1f14ba0356f8254fb6905e6494df1"
+dependencies = [
+ "libc",
+ "lz4-sys",
+]
+
+[[package]]
+name = "lz4-sys"
+version = "1.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57d27b317e207b10f69f5e75494119e391a96f48861ae870d1da6edac98ca900"
+dependencies = [
+ "cc",
+ "libc",
+]
+
+[[package]]
+name = "lzma-sys"
+version = "0.1.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+]
+
+[[package]]
+name = "md-5"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b5a279bb9607f9f53c22d496eade00d138d1bdcccd07d74650387cf94942a15"
+dependencies = [
+ "block-buffer 0.9.0",
+ "digest 0.9.0",
+ "opaque-debug",
+]
+
+[[package]]
+name = "memchr"
+version = "2.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
+
+[[package]]
+name = "memmap2"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f49388d20533534cd19360ad3d6a7dadc885944aa802ba3995040c5ec11288c6"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "memoffset"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "minimal-lexical"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
+
+[[package]]
+name = "miniz_oxide"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
+dependencies = [
+ "adler",
+]
+
+[[package]]
+name = "mio"
+version = "0.8.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0"
+dependencies = [
+ "libc",
+ "wasi",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "multimap"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a"
+
+[[package]]
+name = "multiversion"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2c7b9d7fe61760ce5ea19532ead98541f6b4c495d87247aff9826445cf6872a"
+dependencies = [
+ "multiversion-macros",
+ "target-features",
+]
+
+[[package]]
+name = "multiversion-macros"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26a83d8500ed06d68877e9de1dde76c1dbb83885dcdbda4ef44ccbc3fbda2ac8"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+ "target-features",
+]
+
+[[package]]
+name = "nix-compat"
+version = "0.1.0"
+dependencies = [
+ "bitflags 2.4.1",
+ "bstr",
+ "data-encoding",
+ "ed25519",
+ "ed25519-dalek",
+ "glob",
+ "nom",
+ "serde",
+ "serde_json",
+ "sha2 0.10.8",
+ "thiserror",
+]
+
+[[package]]
+name = "nom"
+version = "7.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
+dependencies = [
+ "memchr",
+ "minimal-lexical",
+]
+
+[[package]]
+name = "now"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d89e9874397a1f0a52fc1f197a8effd9735223cb2390e9dcc83ac6cd02923d0"
+dependencies = [
+ "chrono",
+]
+
+[[package]]
+name = "ntapi"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c"
+dependencies = [
+ "autocfg",
+ "libm",
+]
+
+[[package]]
+name = "num_cpus"
+version = "1.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
+dependencies = [
+ "hermit-abi",
+ "libc",
+]
+
+[[package]]
+name = "number_prefix"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
+
+[[package]]
+name = "object"
+version = "0.32.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
+
+[[package]]
+name = "opaque-debug"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
+
+[[package]]
+name = "openssl-probe"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
+
+[[package]]
+name = "parking_lot"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
+dependencies = [
+ "instant",
+ "lock_api",
+ "parking_lot_core 0.8.6",
+]
+
+[[package]]
+name = "parking_lot"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
+dependencies = [
+ "lock_api",
+ "parking_lot_core 0.9.9",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc"
+dependencies = [
+ "cfg-if",
+ "instant",
+ "libc",
+ "redox_syscall 0.2.16",
+ "smallvec",
+ "winapi",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall 0.4.1",
+ "smallvec",
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "parquet-format-safe"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1131c54b167dd4e4799ce762e1ab01549ebb94d5bdd13e6ec1b467491c378e1f"
+dependencies = [
+ "async-trait",
+ "futures",
+]
+
+[[package]]
+name = "percent-encoding"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
+
+[[package]]
+name = "petgraph"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9"
+dependencies = [
+ "fixedbitset",
+ "indexmap",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
+[[package]]
+name = "pkcs8"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
+dependencies = [
+ "der",
+ "spki",
+]
+
+[[package]]
+name = "pkg-config"
+version = "0.3.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
+
+[[package]]
+name = "planus"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc1691dd09e82f428ce8d6310bd6d5da2557c82ff17694d2a32cad7242aea89f"
+dependencies = [
+ "array-init-cursor",
+]
+
+[[package]]
+name = "platforms"
+version = "3.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "14e6ab3f592e6fb464fc9712d8d6e6912de6473954635fd76a589d832cffcbb0"
+
+[[package]]
+name = "polars"
+version = "0.35.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df8e52f9236eb722da0990a70bbb1216dcc7a77bcb00c63439d2d982823e90d5"
+dependencies = [
+ "getrandom",
+ "polars-core",
+ "polars-io",
+ "polars-lazy",
+ "polars-ops",
+ "polars-sql",
+ "version_check",
+]
+
+[[package]]
+name = "polars-arrow"
+version = "0.35.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd503430a6d9779b07915d858865fe998317ef3cfef8973881f578ac5d4baae7"
+dependencies = [
+ "ahash",
+ "arrow-format",
+ "atoi_simd",
+ "bytemuck",
+ "chrono",
+ "dyn-clone",
+ "either",
+ "ethnum",
+ "fast-float",
+ "foreign_vec",
+ "futures",
+ "getrandom",
+ "hashbrown",
+ "itoa",
+ "lz4",
+ "multiversion",
+ "num-traits",
+ "polars-error",
+ "polars-utils",
+ "rustc_version",
+ "ryu",
+ "simdutf8",
+ "streaming-iterator",
+ "strength_reduce",
+ "zstd",
+]
+
+[[package]]
+name = "polars-core"
+version = "0.35.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae73d5b8e55decde670caba1cc82b61f14bfb9a72503198f0997d657a98dcfd6"
+dependencies = [
+ "ahash",
+ "bitflags 2.4.1",
+ "bytemuck",
+ "chrono",
+ "either",
+ "hashbrown",
+ "indexmap",
+ "num-traits",
+ "once_cell",
+ "polars-arrow",
+ "polars-error",
+ "polars-row",
+ "polars-utils",
+ "rand",
+ "rand_distr",
+ "rayon",
+ "regex",
+ "smartstring",
+ "thiserror",
+ "version_check",
+ "xxhash-rust",
+]
+
+[[package]]
+name = "polars-error"
+version = "0.35.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eb0520d68eaa9993ae0c741409d1526beff5b8f48e1d73e4381616f8152cf488"
+dependencies = [
+ "arrow-format",
+ "regex",
+ "simdutf8",
+ "thiserror",
+]
+
+[[package]]
+name = "polars-io"
+version = "0.35.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96e10a0745acd6009db64bef0ceb9e23a70b1c27b26a0a6517c91f3e6363bc06"
+dependencies = [
+ "ahash",
+ "async-trait",
+ "bytes",
+ "futures",
+ "home",
+ "memchr",
+ "memmap2",
+ "num-traits",
+ "once_cell",
+ "percent-encoding",
+ "polars-arrow",
+ "polars-core",
+ "polars-error",
+ "polars-parquet",
+ "polars-utils",
+ "rayon",
+ "regex",
+ "smartstring",
+ "tokio",
+ "tokio-util",
+]
+
+[[package]]
+name = "polars-lazy"
+version = "0.35.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3555f759705be6dd0d3762d16a0b8787b2dc4da73b57465f3b2bf1a070ba8f20"
+dependencies = [
+ "ahash",
+ "bitflags 2.4.1",
+ "glob",
+ "once_cell",
+ "polars-arrow",
+ "polars-core",
+ "polars-io",
+ "polars-ops",
+ "polars-pipe",
+ "polars-plan",
+ "polars-time",
+ "polars-utils",
+ "rayon",
+ "smartstring",
+ "version_check",
+]
+
+[[package]]
+name = "polars-ops"
+version = "0.35.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a7eb218296aaa7f79945f08288ca32ca3cf25fa505649eeee689ec21eebf636"
+dependencies = [
+ "ahash",
+ "argminmax",
+ "bytemuck",
+ "either",
+ "hashbrown",
+ "indexmap",
+ "memchr",
+ "num-traits",
+ "polars-arrow",
+ "polars-core",
+ "polars-error",
+ "polars-utils",
+ "rayon",
+ "regex",
+ "smartstring",
+ "version_check",
+]
+
+[[package]]
+name = "polars-parquet"
+version = "0.35.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "146010e4b7dd4d2d0e58ddc762f6361f77d7a0385c54471199370c17164f67dd"
+dependencies = [
+ "ahash",
+ "async-stream",
+ "base64 0.21.5",
+ "brotli",
+ "ethnum",
+ "flate2",
+ "futures",
+ "lz4",
+ "num-traits",
+ "parquet-format-safe",
+ "polars-arrow",
+ "polars-error",
+ "polars-utils",
+ "seq-macro",
+ "simdutf8",
+ "snap",
+ "streaming-decompression",
+ "zstd",
+]
+
+[[package]]
+name = "polars-pipe"
+version = "0.35.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "66094e7df64c932a9a7bdfe7df0c65efdcb192096e11a6a765a9778f78b4bdec"
+dependencies = [
+ "crossbeam-channel",
+ "crossbeam-queue",
+ "enum_dispatch",
+ "hashbrown",
+ "num-traits",
+ "polars-arrow",
+ "polars-core",
+ "polars-io",
+ "polars-ops",
+ "polars-plan",
+ "polars-row",
+ "polars-utils",
+ "rayon",
+ "smartstring",
+ "version_check",
+]
+
+[[package]]
+name = "polars-plan"
+version = "0.35.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "10e32a0958ef854b132bad7f8369cb3237254635d5e864c99505bc0bc1035fbc"
+dependencies = [
+ "ahash",
+ "bytemuck",
+ "once_cell",
+ "percent-encoding",
+ "polars-arrow",
+ "polars-core",
+ "polars-io",
+ "polars-ops",
+ "polars-parquet",
+ "polars-time",
+ "polars-utils",
+ "rayon",
+ "regex",
+ "smartstring",
+ "strum_macros",
+ "version_check",
+]
+
+[[package]]
+name = "polars-row"
+version = "0.35.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d135ab81cac2906ba74ea8984c7e6025d081ae5867615bcefb4d84dfdb456dac"
+dependencies = [
+ "polars-arrow",
+ "polars-error",
+ "polars-utils",
+]
+
+[[package]]
+name = "polars-sql"
+version = "0.35.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8dbd7786849a5e3ad1fde188bf38141632f626e3a57319b0bbf7a5f1d75519e"
+dependencies = [
+ "polars-arrow",
+ "polars-core",
+ "polars-error",
+ "polars-lazy",
+ "polars-plan",
+ "rand",
+ "serde",
+ "serde_json",
+ "sqlparser",
+]
+
+[[package]]
+name = "polars-time"
+version = "0.35.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aae56f79e9cedd617773c1c8f5ca84a31a8b1d593714959d5f799e7bdd98fe51"
+dependencies = [
+ "atoi",
+ "chrono",
+ "now",
+ "once_cell",
+ "polars-arrow",
+ "polars-core",
+ "polars-error",
+ "polars-ops",
+ "polars-utils",
+ "regex",
+ "smartstring",
+]
+
+[[package]]
+name = "polars-utils"
+version = "0.35.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da6ce68169fe61d46958c8eab7447360f30f2f23f6e24a0ce703a14b0a3cfbfc"
+dependencies = [
+ "ahash",
+ "bytemuck",
+ "hashbrown",
+ "indexmap",
+ "num-traits",
+ "once_cell",
+ "polars-error",
+ "rayon",
+ "smartstring",
+ "sysinfo",
+ "version_check",
+]
+
+[[package]]
+name = "portable-atomic"
+version = "1.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3bccab0e7fd7cc19f820a1c8c91720af652d0c88dc9664dd72aef2614f04af3b"
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
+
+[[package]]
+name = "prettyplease"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d"
+dependencies = [
+ "proc-macro2",
+ "syn 2.0.39",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "prost"
+version = "0.12.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a5a410fc7882af66deb8d01d01737353cf3ad6204c408177ba494291a626312"
+dependencies = [
+ "bytes",
+ "prost-derive",
+]
+
+[[package]]
+name = "prost-build"
+version = "0.12.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fa3d084c8704911bfefb2771be2f9b6c5c0da7343a71e0021ee3c665cada738"
+dependencies = [
+ "bytes",
+ "heck",
+ "itertools",
+ "log",
+ "multimap",
+ "once_cell",
+ "petgraph",
+ "prettyplease",
+ "prost",
+ "prost-types",
+ "regex",
+ "syn 2.0.39",
+ "tempfile",
+ "which",
+]
+
+[[package]]
+name = "prost-derive"
+version = "0.12.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "065717a5dfaca4a83d2fe57db3487b311365200000551d7a364e715dbf4346bc"
+dependencies = [
+ "anyhow",
+ "itertools",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.39",
+]
+
+[[package]]
+name = "prost-types"
+version = "0.12.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8339f32236f590281e2f6368276441394fcd1b2133b549cc895d0ae80f2f9a52"
+dependencies = [
+ "prost",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.33"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "libc",
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "rand_distr"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31"
+dependencies = [
+ "num-traits",
+ "rand",
+]
+
+[[package]]
+name = "rayon"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1"
+dependencies = [
+ "either",
+ "rayon-core",
+]
+
+[[package]]
+name = "rayon-core"
+version = "1.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed"
+dependencies = [
+ "crossbeam-deque",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
+dependencies = [
+ "bitflags 1.3.2",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
+dependencies = [
+ "bitflags 1.3.2",
+]
+
+[[package]]
+name = "redox_users"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4"
+dependencies = [
+ "getrandom",
+ "libredox",
+ "thiserror",
+]
+
+[[package]]
+name = "regex"
+version = "1.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
+
+[[package]]
+name = "ring"
+version = "0.16.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
+dependencies = [
+ "cc",
+ "libc",
+ "once_cell",
+ "spin 0.5.2",
+ "untrusted 0.7.1",
+ "web-sys",
+ "winapi",
+]
+
+[[package]]
+name = "ring"
+version = "0.17.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb0205304757e5d899b9c2e448b867ffd03ae7f988002e47cd24954391394d0b"
+dependencies = [
+ "cc",
+ "getrandom",
+ "libc",
+ "spin 0.9.8",
+ "untrusted 0.9.0",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "rusoto_core"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1db30db44ea73551326269adcf7a2169428a054f14faf9e1768f2163494f2fa2"
+dependencies = [
+ "async-trait",
+ "base64 0.13.1",
+ "bytes",
+ "crc32fast",
+ "futures",
+ "http",
+ "hyper",
+ "hyper-rustls",
+ "lazy_static",
+ "log",
+ "rusoto_credential",
+ "rusoto_signature",
+ "rustc_version",
+ "serde",
+ "serde_json",
+ "tokio",
+ "xml-rs",
+]
+
+[[package]]
+name = "rusoto_credential"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee0a6c13db5aad6047b6a44ef023dbbc21a056b6dab5be3b79ce4283d5c02d05"
+dependencies = [
+ "async-trait",
+ "chrono",
+ "dirs-next",
+ "futures",
+ "hyper",
+ "serde",
+ "serde_json",
+ "shlex",
+ "tokio",
+ "zeroize",
+]
+
+[[package]]
+name = "rusoto_s3"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7aae4677183411f6b0b412d66194ef5403293917d66e70ab118f07cc24c5b14d"
+dependencies = [
+ "async-trait",
+ "bytes",
+ "futures",
+ "rusoto_core",
+ "xml-rs",
+]
+
+[[package]]
+name = "rusoto_signature"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a5ae95491c8b4847931e291b151127eccd6ff8ca13f33603eb3d0035ecb05272"
+dependencies = [
+ "base64 0.13.1",
+ "bytes",
+ "chrono",
+ "digest 0.9.0",
+ "futures",
+ "hex",
+ "hmac",
+ "http",
+ "hyper",
+ "log",
+ "md-5",
+ "percent-encoding",
+ "pin-project-lite",
+ "rusoto_credential",
+ "rustc_version",
+ "serde",
+ "sha2 0.9.9",
+ "tokio",
+]
+
+[[package]]
+name = "rustc-demangle"
+version = "0.1.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
+
+[[package]]
+name = "rustc_version"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
+dependencies = [
+ "semver",
+]
+
+[[package]]
+name = "rustix"
+version = "0.38.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ad981d6c340a49cdc40a1028d9c6084ec7e9fa33fcb839cab656a267071e234"
+dependencies = [
+ "bitflags 2.4.1",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "rustls"
+version = "0.20.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b80e3dec595989ea8510028f30c408a4630db12c9cbb8de34203b89d6577e99"
+dependencies = [
+ "log",
+ "ring 0.16.20",
+ "sct",
+ "webpki",
+]
+
+[[package]]
+name = "rustls-native-certs"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00"
+dependencies = [
+ "openssl-probe",
+ "rustls-pemfile",
+ "schannel",
+ "security-framework",
+]
+
+[[package]]
+name = "rustls-pemfile"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c"
+dependencies = [
+ "base64 0.21.5",
+]
+
+[[package]]
+name = "rustversion"
+version = "1.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4"
+
+[[package]]
+name = "ryu"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
+
+[[package]]
+name = "schannel"
+version = "0.1.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88"
+dependencies = [
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "scopeguard"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+
+[[package]]
+name = "sct"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414"
+dependencies = [
+ "ring 0.17.5",
+ "untrusted 0.9.0",
+]
+
+[[package]]
+name = "security-framework"
+version = "2.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de"
+dependencies = [
+ "bitflags 1.3.2",
+ "core-foundation",
+ "core-foundation-sys",
+ "libc",
+ "security-framework-sys",
+]
+
+[[package]]
+name = "security-framework-sys"
+version = "2.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "semver"
+version = "1.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090"
+
+[[package]]
+name = "seq-macro"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a3f0bf26fd526d2a95683cd0f87bf103b8539e2ca1ef48ce002d67aad59aa0b4"
+
+[[package]]
+name = "serde"
+version = "1.0.192"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.192"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.39",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.108"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "sha2"
+version = "0.9.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800"
+dependencies = [
+ "block-buffer 0.9.0",
+ "cfg-if",
+ "cpufeatures",
+ "digest 0.9.0",
+ "opaque-debug",
+]
+
+[[package]]
+name = "sha2"
+version = "0.10.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest 0.10.7",
+ "sha2-asm",
+]
+
+[[package]]
+name = "sha2-asm"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f27ba7066011e3fb30d808b51affff34f0a66d3a03a58edd787c6e420e40e44e"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "shlex"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380"
+
+[[package]]
+name = "signal-hook-registry"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "signature"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
+dependencies = [
+ "rand_core",
+]
+
+[[package]]
+name = "simdutf8"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a"
+
+[[package]]
+name = "slab"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "sled"
+version = "0.34.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f96b4737c2ce5987354855aed3797279def4ebf734436c6aa4552cf8e169935"
+dependencies = [
+ "crc32fast",
+ "crossbeam-epoch",
+ "crossbeam-utils",
+ "fs2",
+ "fxhash",
+ "libc",
+ "log",
+ "parking_lot 0.11.2",
+]
+
+[[package]]
+name = "smallvec"
+version = "1.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970"
+
+[[package]]
+name = "smartstring"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29"
+dependencies = [
+ "autocfg",
+ "static_assertions",
+ "version_check",
+]
+
+[[package]]
+name = "snap"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e9f0ab6ef7eb7353d9119c170a436d1bf248eea575ac42d19d12f4e34130831"
+
+[[package]]
+name = "socket2"
+version = "0.4.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "socket2"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9"
+dependencies = [
+ "libc",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "spin"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
+
+[[package]]
+name = "spin"
+version = "0.9.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
+
+[[package]]
+name = "spki"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a"
+dependencies = [
+ "base64ct",
+ "der",
+]
+
+[[package]]
+name = "sqlparser"
+version = "0.39.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "743b4dc2cbde11890ccb254a8fc9d537fa41b36da00de2a1c5e9848c9bc42bd7"
+dependencies = [
+ "log",
+]
+
+[[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
+[[package]]
+name = "streaming-decompression"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf6cc3b19bfb128a8ad11026086e31d3ce9ad23f8ea37354b31383a187c44cf3"
+dependencies = [
+ "fallible-streaming-iterator",
+]
+
+[[package]]
+name = "streaming-iterator"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b2231b7c3057d5e4ad0156fb3dc807d900806020c5ffa3ee6ff2c8c76fb8520"
+
+[[package]]
+name = "strength_reduce"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82"
+
+[[package]]
+name = "strsim"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
+
+[[package]]
+name = "strum_macros"
+version = "0.25.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "rustversion",
+ "syn 2.0.39",
+]
+
+[[package]]
+name = "subtle"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
+
+[[package]]
+name = "syn"
+version = "1.0.109"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.39"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "sysinfo"
+version = "0.29.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0a18d114d420ada3a891e6bc8e96a2023402203296a47cdd65083377dad18ba5"
+dependencies = [
+ "cfg-if",
+ "core-foundation-sys",
+ "libc",
+ "ntapi",
+ "once_cell",
+ "winapi",
+]
+
+[[package]]
+name = "target-features"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfb5fa503293557c5158bd215fdc225695e567a77e453f5d4452a50a193969bd"
+
+[[package]]
+name = "tempfile"
+version = "3.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5"
+dependencies = [
+ "cfg-if",
+ "fastrand",
+ "redox_syscall 0.4.1",
+ "rustix",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.50"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.50"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.39",
+]
+
+[[package]]
+name = "tokio"
+version = "1.34.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9"
+dependencies = [
+ "backtrace",
+ "bytes",
+ "libc",
+ "mio",
+ "num_cpus",
+ "parking_lot 0.12.1",
+ "pin-project-lite",
+ "signal-hook-registry",
+ "socket2 0.5.5",
+ "tokio-macros",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "tokio-macros"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.39",
+]
+
+[[package]]
+name = "tokio-rustls"
+version = "0.23.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59"
+dependencies = [
+ "rustls",
+ "tokio",
+ "webpki",
+]
+
+[[package]]
+name = "tokio-util"
+version = "0.7.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "futures-sink",
+ "pin-project-lite",
+ "tokio",
+ "tracing",
+]
+
+[[package]]
+name = "tower-service"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52"
+
+[[package]]
+name = "tracing"
+version = "0.1.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
+dependencies = [
+ "pin-project-lite",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
+dependencies = [
+ "once_cell",
+]
+
+[[package]]
+name = "try-lock"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
+
+[[package]]
+name = "typenum"
+version = "1.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+
+[[package]]
+name = "unicode-width"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
+
+[[package]]
+name = "untrusted"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
+
+[[package]]
+name = "untrusted"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
+
+[[package]]
+name = "utf8parse"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
+
+[[package]]
+name = "version_check"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
+
+[[package]]
+name = "want"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
+dependencies = [
+ "try-lock",
+]
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.88"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce"
+dependencies = [
+ "cfg-if",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.88"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e397f4664c0e4e428e8313a469aaa58310d302159845980fd23b0f22a847f217"
+dependencies = [
+ "bumpalo",
+ "log",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.39",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.88"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5961017b3b08ad5f3fe39f1e79877f8ee7c23c5e5fd5eb80de95abc41f1f16b2"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.88"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.39",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.88"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b"
+
+[[package]]
+name = "web-sys"
+version = "0.3.65"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5db499c5f66323272151db0e666cd34f78617522fb0c1604d31a27c50c206a85"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "webpki"
+version = "0.22.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53"
+dependencies = [
+ "ring 0.17.5",
+ "untrusted 0.9.0",
+]
+
+[[package]]
+name = "which"
+version = "4.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7"
+dependencies = [
+ "either",
+ "home",
+ "once_cell",
+ "rustix",
+]
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "windows-core"
+version = "0.51.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64"
+dependencies = [
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.45.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
+dependencies = [
+ "windows-targets 0.42.2",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+dependencies = [
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets 0.52.0",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
+dependencies = [
+ "windows_aarch64_gnullvm 0.42.2",
+ "windows_aarch64_msvc 0.42.2",
+ "windows_i686_gnu 0.42.2",
+ "windows_i686_msvc 0.42.2",
+ "windows_x86_64_gnu 0.42.2",
+ "windows_x86_64_gnullvm 0.42.2",
+ "windows_x86_64_msvc 0.42.2",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
+dependencies = [
+ "windows_aarch64_gnullvm 0.48.5",
+ "windows_aarch64_msvc 0.48.5",
+ "windows_i686_gnu 0.48.5",
+ "windows_i686_msvc 0.48.5",
+ "windows_x86_64_gnu 0.48.5",
+ "windows_x86_64_gnullvm 0.48.5",
+ "windows_x86_64_msvc 0.48.5",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
+dependencies = [
+ "windows_aarch64_gnullvm 0.52.0",
+ "windows_aarch64_msvc 0.52.0",
+ "windows_i686_gnu 0.52.0",
+ "windows_i686_msvc 0.52.0",
+ "windows_x86_64_gnu 0.52.0",
+ "windows_x86_64_gnullvm 0.52.0",
+ "windows_x86_64_msvc 0.52.0",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
+
+[[package]]
+name = "xml-rs"
+version = "0.8.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fcb9cbac069e033553e8bb871be2fbdffcab578eb25bd0f7c508cedc6dcd75a"
+
+[[package]]
+name = "xxhash-rust"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9828b178da53440fa9c766a3d2f73f7cf5d0ac1fe3980c1e5018d899fd19e07b"
+
+[[package]]
+name = "xz2"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2"
+dependencies = [
+ "lzma-sys",
+]
+
+[[package]]
+name = "zerocopy"
+version = "0.7.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e97e415490559a91254a2979b4829267a57d2fcd741a98eee8b722fb57289aa0"
+dependencies = [
+ "zerocopy-derive",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.7.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd7e48ccf166952882ca8bd778a43502c64f33bf94c12ebe2a7f08e5a0f6689f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.39",
+]
+
+[[package]]
+name = "zeroize"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d"
+
+[[package]]
+name = "zstd"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bffb3309596d527cfcba7dfc6ed6052f1d39dfbd7c867aa2e865e4a449c10110"
+dependencies = [
+ "zstd-safe",
+]
+
+[[package]]
+name = "zstd-safe"
+version = "7.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43747c7422e2924c11144d5229878b98180ef8b06cca4ab5af37afc8a8d8ea3e"
+dependencies = [
+ "zstd-sys",
+]
+
+[[package]]
+name = "zstd-sys"
+version = "2.0.9+zstd.1.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656"
+dependencies = [
+ "cc",
+ "pkg-config",
+]
diff --git a/tvix/tools/crunch-v2/Cargo.nix b/tvix/tools/crunch-v2/Cargo.nix
new file mode 100644
index 0000000000..35c09ecee7
--- /dev/null
+++ b/tvix/tools/crunch-v2/Cargo.nix
@@ -0,0 +1,11881 @@
+# This file was @generated by crate2nix 0.13.0 with the command:
+#   "generate" "--all-features"
+# See https://github.com/kolloch/crate2nix for more info.
+
+{ nixpkgs ? <nixpkgs>
+, pkgs ? import nixpkgs { config = { }; }
+, lib ? pkgs.lib
+, stdenv ? pkgs.stdenv
+, buildRustCrateForPkgs ? pkgs: pkgs.buildRustCrate
+  # This is used as the `crateOverrides` argument for `buildRustCrate`.
+, defaultCrateOverrides ? pkgs.defaultCrateOverrides
+  # The features to enable for the root_crate or the workspace_members.
+, rootFeatures ? [ "default" ]
+  # If true, throw errors instead of issueing deprecation warnings.
+, strictDeprecation ? false
+  # Used for conditional compilation based on CPU feature detection.
+, targetFeatures ? [ ]
+  # Whether to perform release builds: longer compile times, faster binaries.
+, release ? true
+  # Additional crate2nix configuration if it exists.
+, crateConfig ? if builtins.pathExists ./crate-config.nix
+  then pkgs.callPackage ./crate-config.nix { }
+  else { }
+}:
+
+rec {
+  #
+  # "public" attributes that we attempt to keep stable with new versions of crate2nix.
+  #
+
+  rootCrate = rec {
+    packageId = "crunch-v2";
+
+    # Use this attribute to refer to the derivation building your root crate package.
+    # You can override the features with rootCrate.build.override { features = [ "default" "feature1" ... ]; }.
+    build = internal.buildRustCrateWithFeatures {
+      inherit packageId;
+    };
+
+    # Debug support which might change between releases.
+    # File a bug if you depend on any for non-debug work!
+    debug = internal.debugCrate { inherit packageId; };
+  };
+  # Refer your crate build derivation by name here.
+  # You can override the features with
+  # workspaceMembers."${crateName}".build.override { features = [ "default" "feature1" ... ]; }.
+  workspaceMembers = {
+    "crunch-v2" = rec {
+      packageId = "crunch-v2";
+      build = internal.buildRustCrateWithFeatures {
+        packageId = "crunch-v2";
+      };
+
+      # Debug support which might change between releases.
+      # File a bug if you depend on any for non-debug work!
+      debug = internal.debugCrate { inherit packageId; };
+    };
+  };
+
+  # A derivation that joins the outputs of all workspace members together.
+  allWorkspaceMembers = pkgs.symlinkJoin {
+    name = "all-workspace-members";
+    paths =
+      let members = builtins.attrValues workspaceMembers;
+      in builtins.map (m: m.build) members;
+  };
+
+  #
+  # "internal" ("private") attributes that may change in every new version of crate2nix.
+  #
+
+  internal = rec {
+    # Build and dependency information for crates.
+    # Many of the fields are passed one-to-one to buildRustCrate.
+    #
+    # Noteworthy:
+    # * `dependencies`/`buildDependencies`: similar to the corresponding fields for buildRustCrate.
+    #   but with additional information which is used during dependency/feature resolution.
+    # * `resolvedDependencies`: the selected default features reported by cargo - only included for debugging.
+    # * `devDependencies` as of now not used by `buildRustCrate` but used to
+    #   inject test dependencies into the build
+
+    crates = {
+      "addr2line" = rec {
+        crateName = "addr2line";
+        version = "0.21.0";
+        edition = "2018";
+        sha256 = "1jx0k3iwyqr8klqbzk6kjvr496yd94aspis10vwsj5wy7gib4c4a";
+        dependencies = [
+          {
+            name = "gimli";
+            packageId = "gimli";
+            usesDefaultFeatures = false;
+            features = [ "read" ];
+          }
+        ];
+        features = {
+          "alloc" = [ "dep:alloc" ];
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "core" = [ "dep:core" ];
+          "cpp_demangle" = [ "dep:cpp_demangle" ];
+          "default" = [ "rustc-demangle" "cpp_demangle" "std-object" "fallible-iterator" "smallvec" "memmap2" ];
+          "fallible-iterator" = [ "dep:fallible-iterator" ];
+          "memmap2" = [ "dep:memmap2" ];
+          "object" = [ "dep:object" ];
+          "rustc-demangle" = [ "dep:rustc-demangle" ];
+          "rustc-dep-of-std" = [ "core" "alloc" "compiler_builtins" "gimli/rustc-dep-of-std" ];
+          "smallvec" = [ "dep:smallvec" ];
+          "std" = [ "gimli/std" ];
+          "std-object" = [ "std" "object" "object/std" "object/compression" "gimli/endian-reader" ];
+        };
+      };
+      "adler" = rec {
+        crateName = "adler";
+        version = "1.0.2";
+        edition = "2015";
+        sha256 = "1zim79cvzd5yrkzl3nyfx0avijwgk9fqv3yrscdy1cc79ih02qpj";
+        authors = [
+          "Jonas Schievink <jonasschievink@gmail.com>"
+        ];
+        features = {
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "core" = [ "dep:core" ];
+          "default" = [ "std" ];
+          "rustc-dep-of-std" = [ "core" "compiler_builtins" ];
+        };
+      };
+      "ahash" = rec {
+        crateName = "ahash";
+        version = "0.8.6";
+        edition = "2018";
+        sha256 = "0yn9i8nc6mmv28ig9w3dga571q09vg9f1f650mi5z8phx42r6hli";
+        authors = [
+          "Tom Kaitchuck <Tom.Kaitchuck@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "getrandom";
+            packageId = "getrandom";
+            optional = true;
+          }
+          {
+            name = "once_cell";
+            packageId = "once_cell";
+            usesDefaultFeatures = false;
+            target = { target, features }: (!(("arm" == target."arch" or null) && ("none" == target."os" or null)));
+            features = [ "unstable" "alloc" ];
+          }
+          {
+            name = "zerocopy";
+            packageId = "zerocopy";
+            usesDefaultFeatures = false;
+            features = [ "simd" ];
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "version_check";
+            packageId = "version_check";
+          }
+        ];
+        features = {
+          "atomic-polyfill" = [ "dep:atomic-polyfill" "once_cell/atomic-polyfill" ];
+          "compile-time-rng" = [ "const-random" ];
+          "const-random" = [ "dep:const-random" ];
+          "default" = [ "std" "runtime-rng" ];
+          "getrandom" = [ "dep:getrandom" ];
+          "runtime-rng" = [ "getrandom" ];
+          "serde" = [ "dep:serde" ];
+        };
+        resolvedDefaultFeatures = [ "default" "getrandom" "runtime-rng" "std" ];
+      };
+      "aho-corasick" = rec {
+        crateName = "aho-corasick";
+        version = "1.1.2";
+        edition = "2021";
+        sha256 = "1w510wnixvlgimkx1zjbvlxh6xps2vjgfqgwf5a6adlbjp5rv5mj";
+        libName = "aho_corasick";
+        authors = [
+          "Andrew Gallant <jamslam@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "memchr";
+            packageId = "memchr";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "default" = [ "std" "perf-literal" ];
+          "logging" = [ "dep:log" ];
+          "perf-literal" = [ "dep:memchr" ];
+          "std" = [ "memchr?/std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "perf-literal" "std" ];
+      };
+      "alloc-no-stdlib" = rec {
+        crateName = "alloc-no-stdlib";
+        version = "2.0.4";
+        edition = "2015";
+        crateBin = [ ];
+        sha256 = "1cy6r2sfv5y5cigv86vms7n5nlwhx1rbyxwcraqnmm1rxiib2yyc";
+        authors = [
+          "Daniel Reiter Horn <danielrh@dropbox.com>"
+        ];
+        features = { };
+      };
+      "alloc-stdlib" = rec {
+        crateName = "alloc-stdlib";
+        version = "0.2.2";
+        edition = "2015";
+        crateBin = [ ];
+        sha256 = "1kkfbld20ab4165p29v172h8g0wvq8i06z8vnng14whw0isq5ywl";
+        authors = [
+          "Daniel Reiter Horn <danielrh@dropbox.com>"
+        ];
+        dependencies = [
+          {
+            name = "alloc-no-stdlib";
+            packageId = "alloc-no-stdlib";
+          }
+        ];
+        features = { };
+      };
+      "allocator-api2" = rec {
+        crateName = "allocator-api2";
+        version = "0.2.16";
+        edition = "2018";
+        sha256 = "1iayppgq4wqbfbfcqmsbwgamj0s65012sskfvyx07pxavk3gyhh9";
+        authors = [
+          "Zakarum <zaq.dev@icloud.com>"
+        ];
+        features = {
+          "default" = [ "std" ];
+          "serde" = [ "dep:serde" ];
+          "std" = [ "alloc" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" ];
+      };
+      "android-tzdata" = rec {
+        crateName = "android-tzdata";
+        version = "0.1.1";
+        edition = "2018";
+        sha256 = "1w7ynjxrfs97xg3qlcdns4kgfpwcdv824g611fq32cag4cdr96g9";
+        authors = [
+          "RumovZ"
+        ];
+
+      };
+      "android_system_properties" = rec {
+        crateName = "android_system_properties";
+        version = "0.1.5";
+        edition = "2018";
+        sha256 = "04b3wrz12837j7mdczqd95b732gw5q7q66cv4yn4646lvccp57l1";
+        authors = [
+          "Nicolas Silva <nical@fastmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+          }
+        ];
+
+      };
+      "anstream" = rec {
+        crateName = "anstream";
+        version = "0.6.11";
+        edition = "2021";
+        sha256 = "19dndamalavhjwp4i74k8hdijcixb7gsfa6ycwyc1r8xn6y1wbkf";
+        dependencies = [
+          {
+            name = "anstyle";
+            packageId = "anstyle";
+          }
+          {
+            name = "anstyle-parse";
+            packageId = "anstyle-parse";
+          }
+          {
+            name = "anstyle-query";
+            packageId = "anstyle-query";
+            optional = true;
+          }
+          {
+            name = "anstyle-wincon";
+            packageId = "anstyle-wincon";
+            optional = true;
+            target = { target, features }: (target."windows" or false);
+          }
+          {
+            name = "colorchoice";
+            packageId = "colorchoice";
+          }
+          {
+            name = "utf8parse";
+            packageId = "utf8parse";
+          }
+        ];
+        features = {
+          "auto" = [ "dep:anstyle-query" ];
+          "default" = [ "auto" "wincon" ];
+          "wincon" = [ "dep:anstyle-wincon" ];
+        };
+        resolvedDefaultFeatures = [ "auto" "default" "wincon" ];
+      };
+      "anstyle" = rec {
+        crateName = "anstyle";
+        version = "1.0.4";
+        edition = "2021";
+        sha256 = "11yxw02b6parn29s757z96rgiqbn8qy0fk9a3p3bhczm85dhfybh";
+        features = {
+          "default" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "anstyle-parse" = rec {
+        crateName = "anstyle-parse";
+        version = "0.2.3";
+        edition = "2021";
+        sha256 = "134jhzrz89labrdwxxnjxqjdg06qvaflj1wkfnmyapwyldfwcnn7";
+        dependencies = [
+          {
+            name = "utf8parse";
+            packageId = "utf8parse";
+            optional = true;
+          }
+        ];
+        features = {
+          "core" = [ "dep:arrayvec" ];
+          "default" = [ "utf8" ];
+          "utf8" = [ "dep:utf8parse" ];
+        };
+        resolvedDefaultFeatures = [ "default" "utf8" ];
+      };
+      "anstyle-query" = rec {
+        crateName = "anstyle-query";
+        version = "1.0.2";
+        edition = "2021";
+        sha256 = "0j3na4b1nma39g4x7cwvj009awxckjf3z2vkwhldgka44hqj72g2";
+        dependencies = [
+          {
+            name = "windows-sys";
+            packageId = "windows-sys 0.52.0";
+            target = { target, features }: (target."windows" or false);
+            features = [ "Win32_System_Console" "Win32_Foundation" ];
+          }
+        ];
+
+      };
+      "anstyle-wincon" = rec {
+        crateName = "anstyle-wincon";
+        version = "3.0.2";
+        edition = "2021";
+        sha256 = "19v0fv400bmp4niqpzxnhg83vz12mmqv7l2l8vi80qcdxj0lpm8w";
+        dependencies = [
+          {
+            name = "anstyle";
+            packageId = "anstyle";
+          }
+          {
+            name = "windows-sys";
+            packageId = "windows-sys 0.52.0";
+            target = { target, features }: (target."windows" or false);
+            features = [ "Win32_System_Console" "Win32_Foundation" ];
+          }
+        ];
+
+      };
+      "anyhow" = rec {
+        crateName = "anyhow";
+        version = "1.0.75";
+        edition = "2018";
+        sha256 = "1rmcjkim91c5mw7h9wn8nv0k6x118yz0xg0z1q18svgn42mqqrm4";
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "backtrace";
+            packageId = "backtrace";
+            optional = true;
+          }
+        ];
+        features = {
+          "backtrace" = [ "dep:backtrace" ];
+          "default" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "backtrace" "default" "std" ];
+      };
+      "argminmax" = rec {
+        crateName = "argminmax";
+        version = "0.6.1";
+        edition = "2021";
+        sha256 = "1lnvpkvdsvdbsinhik6srx5c2j3gqkaj92iz93pnbdr9cjs0h890";
+        authors = [
+          "Jeroen Van Der Donckt"
+        ];
+        dependencies = [
+          {
+            name = "num-traits";
+            packageId = "num-traits";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "arrow" = [ "dep:arrow" ];
+          "arrow2" = [ "dep:arrow2" ];
+          "default" = [ "nightly_simd" "float" ];
+          "half" = [ "dep:half" ];
+          "ndarray" = [ "dep:ndarray" ];
+        };
+        resolvedDefaultFeatures = [ "float" ];
+      };
+      "array-init-cursor" = rec {
+        crateName = "array-init-cursor";
+        version = "0.2.0";
+        edition = "2021";
+        sha256 = "0xpbqf7qkvzplpjd7f0wbcf2n1v9vygdccwxkd1amxp4il0hlzdz";
+
+      };
+      "arrayref" = rec {
+        crateName = "arrayref";
+        version = "0.3.7";
+        edition = "2015";
+        sha256 = "0ia5ndyxqkzdymqr4ls53jdmajf09adjimg5kvw65kkprg930jbb";
+        authors = [
+          "David Roundy <roundyd@physics.oregonstate.edu>"
+        ];
+
+      };
+      "arrayvec" = rec {
+        crateName = "arrayvec";
+        version = "0.7.4";
+        edition = "2018";
+        sha256 = "04b7n722jij0v3fnm3qk072d5ysc2q30rl9fz33zpfhzah30mlwn";
+        authors = [
+          "bluss"
+        ];
+        features = {
+          "default" = [ "std" ];
+          "serde" = [ "dep:serde" ];
+          "zeroize" = [ "dep:zeroize" ];
+        };
+      };
+      "arrow-format" = rec {
+        crateName = "arrow-format";
+        version = "0.8.1";
+        edition = "2018";
+        sha256 = "1irj67p6c224dzw86jr7j3z9r5zfid52gy6ml8rdqk4r2si4x207";
+        authors = [
+          "Jorge C. Leitao <jorgecarleitao@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "planus";
+            packageId = "planus";
+            optional = true;
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+            optional = true;
+            usesDefaultFeatures = false;
+            features = [ "derive" "std" ];
+          }
+        ];
+        features = {
+          "flight-data" = [ "prost" "prost-derive" ];
+          "flight-service" = [ "flight-data" "tonic" ];
+          "full" = [ "ipc" "flight-data" "flight-service" ];
+          "ipc" = [ "planus" "serde" ];
+          "planus" = [ "dep:planus" ];
+          "prost" = [ "dep:prost" ];
+          "prost-derive" = [ "dep:prost-derive" ];
+          "serde" = [ "dep:serde" ];
+          "tonic" = [ "dep:tonic" ];
+        };
+        resolvedDefaultFeatures = [ "default" "ipc" "planus" "serde" ];
+      };
+      "async-stream" = rec {
+        crateName = "async-stream";
+        version = "0.3.5";
+        edition = "2018";
+        sha256 = "0l8sjq1rylkb1ak0pdyjn83b3k6x36j22myngl4sqqgg7whdsmnd";
+        authors = [
+          "Carl Lerche <me@carllerche.com>"
+        ];
+        dependencies = [
+          {
+            name = "async-stream-impl";
+            packageId = "async-stream-impl";
+          }
+          {
+            name = "futures-core";
+            packageId = "futures-core";
+          }
+          {
+            name = "pin-project-lite";
+            packageId = "pin-project-lite";
+          }
+        ];
+
+      };
+      "async-stream-impl" = rec {
+        crateName = "async-stream-impl";
+        version = "0.3.5";
+        edition = "2018";
+        sha256 = "14q179j4y8p2z1d0ic6aqgy9fhwz8p9cai1ia8kpw4bw7q12mrhn";
+        procMacro = true;
+        authors = [
+          "Carl Lerche <me@carllerche.com>"
+        ];
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn 2.0.39";
+            features = [ "full" "visit-mut" ];
+          }
+        ];
+
+      };
+      "async-trait" = rec {
+        crateName = "async-trait";
+        version = "0.1.74";
+        edition = "2021";
+        sha256 = "1ydhbsqjqqa6bxbv0kgys2wq2vi3jpwjy57dk162ajwppgqkfrd6";
+        procMacro = true;
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn 2.0.39";
+            features = [ "full" "visit-mut" ];
+          }
+        ];
+
+      };
+      "atoi" = rec {
+        crateName = "atoi";
+        version = "2.0.0";
+        edition = "2021";
+        sha256 = "0a05h42fggmy7h0ajjv6m7z72l924i7igbx13hk9d8pyign9k3gj";
+        authors = [
+          "Markus Klein"
+        ];
+        dependencies = [
+          {
+            name = "num-traits";
+            packageId = "num-traits";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "std" = [ "num-traits/std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "atoi_simd" = rec {
+        crateName = "atoi_simd";
+        version = "0.15.3";
+        edition = "2018";
+        sha256 = "09jiwr7074j73viiafdzjq9mf39idd784hfpsbf1p1dn05gbchgw";
+        authors = [
+          "Dmitry Rodionov <gh@rdmtr.com>"
+        ];
+        features = {
+          "default" = [ "std" ];
+          "std" = [ "arrayvec/std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "autocfg" = rec {
+        crateName = "autocfg";
+        version = "1.1.0";
+        edition = "2015";
+        sha256 = "1ylp3cb47ylzabimazvbz9ms6ap784zhb6syaz6c1jqpmcmq0s6l";
+        authors = [
+          "Josh Stone <cuviper@gmail.com>"
+        ];
+
+      };
+      "backtrace" = rec {
+        crateName = "backtrace";
+        version = "0.3.69";
+        edition = "2018";
+        sha256 = "0dsq23dhw4pfndkx2nsa1ml2g31idm7ss7ljxp8d57avygivg290";
+        authors = [
+          "The Rust Project Developers"
+        ];
+        dependencies = [
+          {
+            name = "addr2line";
+            packageId = "addr2line";
+            usesDefaultFeatures = false;
+            target = { target, features }: (!((target."windows" or false) && ("msvc" == target."env" or null) && (!("uwp" == target."vendor" or null))));
+          }
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            usesDefaultFeatures = false;
+            target = { target, features }: (!((target."windows" or false) && ("msvc" == target."env" or null) && (!("uwp" == target."vendor" or null))));
+          }
+          {
+            name = "miniz_oxide";
+            packageId = "miniz_oxide";
+            usesDefaultFeatures = false;
+            target = { target, features }: (!((target."windows" or false) && ("msvc" == target."env" or null) && (!("uwp" == target."vendor" or null))));
+          }
+          {
+            name = "object";
+            packageId = "object";
+            usesDefaultFeatures = false;
+            target = { target, features }: (!((target."windows" or false) && ("msvc" == target."env" or null) && (!("uwp" == target."vendor" or null))));
+            features = [ "read_core" "elf" "macho" "pe" "unaligned" "archive" ];
+          }
+          {
+            name = "rustc-demangle";
+            packageId = "rustc-demangle";
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "cc";
+            packageId = "cc";
+          }
+        ];
+        features = {
+          "cpp_demangle" = [ "dep:cpp_demangle" ];
+          "default" = [ "std" ];
+          "rustc-serialize" = [ "dep:rustc-serialize" ];
+          "serde" = [ "dep:serde" ];
+          "serialize-rustc" = [ "rustc-serialize" ];
+          "serialize-serde" = [ "serde" ];
+          "verify-winapi" = [ "winapi/dbghelp" "winapi/handleapi" "winapi/libloaderapi" "winapi/memoryapi" "winapi/minwindef" "winapi/processthreadsapi" "winapi/synchapi" "winapi/tlhelp32" "winapi/winbase" "winapi/winnt" ];
+          "winapi" = [ "dep:winapi" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "base64 0.13.1" = rec {
+        crateName = "base64";
+        version = "0.13.1";
+        edition = "2018";
+        sha256 = "1s494mqmzjb766fy1kqlccgfg2sdcjb6hzbvzqv2jw65fdi5h6wy";
+        authors = [
+          "Alice Maz <alice@alicemaz.com>"
+          "Marshall Pierce <marshall@mpierce.org>"
+        ];
+        features = {
+          "default" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "base64 0.21.5" = rec {
+        crateName = "base64";
+        version = "0.21.5";
+        edition = "2018";
+        sha256 = "1y8x2xs9nszj5ix7gg4ycn5a6wy7ca74zxwqri3bdqzdjha6lqrm";
+        authors = [
+          "Alice Maz <alice@alicemaz.com>"
+          "Marshall Pierce <marshall@mpierce.org>"
+        ];
+        features = {
+          "default" = [ "std" ];
+          "std" = [ "alloc" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "std" ];
+      };
+      "base64ct" = rec {
+        crateName = "base64ct";
+        version = "1.6.0";
+        edition = "2021";
+        sha256 = "0nvdba4jb8aikv60az40x2w1y96sjdq8z3yp09rwzmkhiwv1lg4c";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        features = {
+          "std" = [ "alloc" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" ];
+      };
+      "bitflags 1.3.2" = rec {
+        crateName = "bitflags";
+        version = "1.3.2";
+        edition = "2018";
+        sha256 = "12ki6w8gn1ldq7yz9y680llwk5gmrhrzszaa17g1sbrw2r2qvwxy";
+        authors = [
+          "The Rust Project Developers"
+        ];
+        features = {
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "core" = [ "dep:core" ];
+          "rustc-dep-of-std" = [ "core" "compiler_builtins" ];
+        };
+        resolvedDefaultFeatures = [ "default" ];
+      };
+      "bitflags 2.4.1" = rec {
+        crateName = "bitflags";
+        version = "2.4.1";
+        edition = "2021";
+        sha256 = "01ryy3kd671b0ll4bhdvhsz67vwz1lz53fz504injrd7wpv64xrj";
+        authors = [
+          "The Rust Project Developers"
+        ];
+        features = {
+          "arbitrary" = [ "dep:arbitrary" ];
+          "bytemuck" = [ "dep:bytemuck" ];
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "core" = [ "dep:core" ];
+          "rustc-dep-of-std" = [ "core" "compiler_builtins" ];
+          "serde" = [ "dep:serde" ];
+        };
+        resolvedDefaultFeatures = [ "std" ];
+      };
+      "blake3" = rec {
+        crateName = "blake3";
+        version = "1.5.0";
+        edition = "2021";
+        sha256 = "11ysh12zcqq6xkjxh5cbrmnwzalprm3z552i5ff7wm5za9hz0c82";
+        authors = [
+          "Jack O'Connor <oconnor663@gmail.com>"
+          "Samuel Neves"
+        ];
+        dependencies = [
+          {
+            name = "arrayref";
+            packageId = "arrayref";
+          }
+          {
+            name = "arrayvec";
+            packageId = "arrayvec";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "constant_time_eq";
+            packageId = "constant_time_eq";
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "cc";
+            packageId = "cc";
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "digest" = [ "dep:digest" ];
+          "mmap" = [ "std" "dep:memmap2" ];
+          "rayon" = [ "dep:rayon" "std" ];
+          "serde" = [ "dep:serde" ];
+          "traits-preview" = [ "digest" ];
+          "zeroize" = [ "dep:zeroize" "arrayvec/zeroize" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "block-buffer 0.10.4" = rec {
+        crateName = "block-buffer";
+        version = "0.10.4";
+        edition = "2018";
+        sha256 = "0w9sa2ypmrsqqvc20nhwr75wbb5cjr4kkyhpjm1z1lv2kdicfy1h";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        dependencies = [
+          {
+            name = "generic-array";
+            packageId = "generic-array";
+          }
+        ];
+
+      };
+      "block-buffer 0.9.0" = rec {
+        crateName = "block-buffer";
+        version = "0.9.0";
+        edition = "2018";
+        sha256 = "1r4pf90s7d7lj1wdjhlnqa26vvbm6pnc33z138lxpnp9srpi2lj1";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        dependencies = [
+          {
+            name = "generic-array";
+            packageId = "generic-array";
+          }
+        ];
+        features = {
+          "block-padding" = [ "dep:block-padding" ];
+        };
+      };
+      "brotli" = rec {
+        crateName = "brotli";
+        version = "3.4.0";
+        edition = "2015";
+        crateBin = [ ];
+        sha256 = "03qhcq09a6f8y4gm0bmsn7jrq5804cwpkcx3fyay1g7lgsj78q2i";
+        authors = [
+          "Daniel Reiter Horn <danielrh@dropbox.com>"
+          "The Brotli Authors"
+        ];
+        dependencies = [
+          {
+            name = "alloc-no-stdlib";
+            packageId = "alloc-no-stdlib";
+          }
+          {
+            name = "alloc-stdlib";
+            packageId = "alloc-stdlib";
+            optional = true;
+          }
+          {
+            name = "brotli-decompressor";
+            packageId = "brotli-decompressor";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "alloc-stdlib" = [ "dep:alloc-stdlib" ];
+          "benchmark" = [ "brotli-decompressor/benchmark" ];
+          "default" = [ "std" "ffi-api" ];
+          "disable-timer" = [ "brotli-decompressor/disable-timer" ];
+          "seccomp" = [ "brotli-decompressor/seccomp" ];
+          "sha2" = [ "dep:sha2" ];
+          "std" = [ "alloc-stdlib" "brotli-decompressor/std" ];
+          "validation" = [ "sha2" ];
+        };
+        resolvedDefaultFeatures = [ "alloc-stdlib" "default" "ffi-api" "std" ];
+      };
+      "brotli-decompressor" = rec {
+        crateName = "brotli-decompressor";
+        version = "2.5.1";
+        edition = "2015";
+        crateBin = [ ];
+        sha256 = "0kyyh9701dwqzwvn2frff4ww0zibikqd1s1xvl7n1pfpc3z4lbjf";
+        authors = [
+          "Daniel Reiter Horn <danielrh@dropbox.com>"
+          "The Brotli Authors"
+        ];
+        dependencies = [
+          {
+            name = "alloc-no-stdlib";
+            packageId = "alloc-no-stdlib";
+          }
+          {
+            name = "alloc-stdlib";
+            packageId = "alloc-stdlib";
+            optional = true;
+          }
+        ];
+        features = {
+          "alloc-stdlib" = [ "dep:alloc-stdlib" ];
+          "default" = [ "std" ];
+          "std" = [ "alloc-stdlib" ];
+          "unsafe" = [ "alloc-no-stdlib/unsafe" "alloc-stdlib/unsafe" ];
+        };
+        resolvedDefaultFeatures = [ "alloc-stdlib" "std" ];
+      };
+      "bstr" = rec {
+        crateName = "bstr";
+        version = "1.8.0";
+        edition = "2021";
+        sha256 = "0313sqdf0a40vhpnrlkf54zhr76rmlyxzhx00sq8822shfl36bsl";
+        authors = [
+          "Andrew Gallant <jamslam@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "memchr";
+            packageId = "memchr";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "regex-automata";
+            packageId = "regex-automata";
+            optional = true;
+            usesDefaultFeatures = false;
+            features = [ "dfa-search" ];
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "alloc" = [ "memchr/alloc" "serde?/alloc" ];
+          "default" = [ "std" "unicode" ];
+          "serde" = [ "dep:serde" ];
+          "std" = [ "alloc" "memchr/std" "serde?/std" ];
+          "unicode" = [ "dep:regex-automata" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "serde" "std" "unicode" ];
+      };
+      "bumpalo" = rec {
+        crateName = "bumpalo";
+        version = "3.14.0";
+        edition = "2021";
+        sha256 = "1v4arnv9kwk54v5d0qqpv4vyw2sgr660nk0w3apzixi1cm3yfc3z";
+        authors = [
+          "Nick Fitzgerald <fitzgen@gmail.com>"
+        ];
+        features = {
+          "allocator-api2" = [ "dep:allocator-api2" ];
+        };
+        resolvedDefaultFeatures = [ "default" ];
+      };
+      "bytemuck" = rec {
+        crateName = "bytemuck";
+        version = "1.14.0";
+        edition = "2018";
+        sha256 = "1ik1ma5n3bg700skkzhx50zjk7kj7mbsphi773if17l04pn2hk9p";
+        authors = [
+          "Lokathor <zefria@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "bytemuck_derive";
+            packageId = "bytemuck_derive";
+            optional = true;
+          }
+        ];
+        features = {
+          "bytemuck_derive" = [ "dep:bytemuck_derive" ];
+          "derive" = [ "bytemuck_derive" ];
+          "extern_crate_std" = [ "extern_crate_alloc" ];
+        };
+        resolvedDefaultFeatures = [ "bytemuck_derive" "derive" "extern_crate_alloc" ];
+      };
+      "bytemuck_derive" = rec {
+        crateName = "bytemuck_derive";
+        version = "1.5.0";
+        edition = "2018";
+        sha256 = "1cgj75df2v32l4fmvnp25xxkkz4lp6hz76f7hfhd55wgbzmvfnln";
+        procMacro = true;
+        authors = [
+          "Lokathor <zefria@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn 2.0.39";
+          }
+        ];
+
+      };
+      "byteorder" = rec {
+        crateName = "byteorder";
+        version = "1.5.0";
+        edition = "2021";
+        sha256 = "0jzncxyf404mwqdbspihyzpkndfgda450l0893pz5xj685cg5l0z";
+        authors = [
+          "Andrew Gallant <jamslam@gmail.com>"
+        ];
+        features = {
+          "default" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "bytes" = rec {
+        crateName = "bytes";
+        version = "1.5.0";
+        edition = "2018";
+        sha256 = "08w2i8ac912l8vlvkv3q51cd4gr09pwlg3sjsjffcizlrb0i5gd2";
+        authors = [
+          "Carl Lerche <me@carllerche.com>"
+          "Sean McArthur <sean@seanmonstar.com>"
+        ];
+        features = {
+          "default" = [ "std" ];
+          "serde" = [ "dep:serde" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "bzip2" = rec {
+        crateName = "bzip2";
+        version = "0.4.4";
+        edition = "2015";
+        sha256 = "1y27wgqkx3k2jmh4k26vra2kqjq1qc1asww8hac3cv1zxyk1dcdx";
+        authors = [
+          "Alex Crichton <alex@alexcrichton.com>"
+        ];
+        dependencies = [
+          {
+            name = "bzip2-sys";
+            packageId = "bzip2-sys";
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+          }
+        ];
+        features = {
+          "futures" = [ "dep:futures" ];
+          "static" = [ "bzip2-sys/static" ];
+          "tokio" = [ "tokio-io" "futures" ];
+          "tokio-io" = [ "dep:tokio-io" ];
+        };
+      };
+      "bzip2-sys" = rec {
+        crateName = "bzip2-sys";
+        version = "0.1.11+1.0.8";
+        edition = "2015";
+        links = "bzip2";
+        sha256 = "1p2crnv8d8gpz5c2vlvzl0j55i3yqg5bi0kwsl1531x77xgraskk";
+        libName = "bzip2_sys";
+        libPath = "lib.rs";
+        authors = [
+          "Alex Crichton <alex@alexcrichton.com>"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "cc";
+            packageId = "cc";
+          }
+          {
+            name = "pkg-config";
+            packageId = "pkg-config";
+          }
+        ];
+        features = { };
+      };
+      "cc" = rec {
+        crateName = "cc";
+        version = "1.0.83";
+        edition = "2018";
+        crateBin = [ ];
+        sha256 = "1l643zidlb5iy1dskc5ggqs4wqa29a02f44piczqc8zcnsq4y5zi";
+        authors = [
+          "Alex Crichton <alex@alexcrichton.com>"
+        ];
+        dependencies = [
+          {
+            name = "jobserver";
+            packageId = "jobserver";
+            optional = true;
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            usesDefaultFeatures = false;
+            target = { target, features }: (target."unix" or false);
+          }
+        ];
+        features = {
+          "jobserver" = [ "dep:jobserver" ];
+          "parallel" = [ "jobserver" ];
+        };
+        resolvedDefaultFeatures = [ "jobserver" "parallel" ];
+      };
+      "cfg-if" = rec {
+        crateName = "cfg-if";
+        version = "1.0.0";
+        edition = "2018";
+        sha256 = "1za0vb97n4brpzpv8lsbnzmq5r8f2b0cpqqr0sy8h5bn751xxwds";
+        authors = [
+          "Alex Crichton <alex@alexcrichton.com>"
+        ];
+        features = {
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "core" = [ "dep:core" ];
+          "rustc-dep-of-std" = [ "core" "compiler_builtins" ];
+        };
+      };
+      "chrono" = rec {
+        crateName = "chrono";
+        version = "0.4.31";
+        edition = "2021";
+        sha256 = "0f6vg67pipm8cziad2yms6a639pssnvysk1m05dd9crymmdnhb3z";
+        dependencies = [
+          {
+            name = "android-tzdata";
+            packageId = "android-tzdata";
+            optional = true;
+            target = { target, features }: ("android" == target."os" or null);
+          }
+          {
+            name = "iana-time-zone";
+            packageId = "iana-time-zone";
+            optional = true;
+            target = { target, features }: (target."unix" or false);
+            features = [ "fallback" ];
+          }
+          {
+            name = "num-traits";
+            packageId = "num-traits";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "windows-targets";
+            packageId = "windows-targets 0.48.5";
+            optional = true;
+            target = { target, features }: (target."windows" or false);
+          }
+        ];
+        features = {
+          "android-tzdata" = [ "dep:android-tzdata" ];
+          "arbitrary" = [ "dep:arbitrary" ];
+          "clock" = [ "std" "winapi" "iana-time-zone" "android-tzdata" ];
+          "default" = [ "clock" "std" "oldtime" "wasmbind" ];
+          "iana-time-zone" = [ "dep:iana-time-zone" ];
+          "js-sys" = [ "dep:js-sys" ];
+          "pure-rust-locales" = [ "dep:pure-rust-locales" ];
+          "rkyv" = [ "dep:rkyv" ];
+          "rustc-serialize" = [ "dep:rustc-serialize" ];
+          "serde" = [ "dep:serde" ];
+          "unstable-locales" = [ "pure-rust-locales" "alloc" ];
+          "wasm-bindgen" = [ "dep:wasm-bindgen" ];
+          "wasmbind" = [ "wasm-bindgen" "js-sys" ];
+          "winapi" = [ "windows-targets" ];
+          "windows-targets" = [ "dep:windows-targets" ];
+        };
+        resolvedDefaultFeatures = [ "android-tzdata" "clock" "iana-time-zone" "serde" "std" "winapi" "windows-targets" ];
+      };
+      "clap" = rec {
+        crateName = "clap";
+        version = "4.4.18";
+        edition = "2021";
+        crateBin = [ ];
+        sha256 = "0p46h346y8nval6gwzh27if3icbi9dwl95fg5ir36ihrqip8smqy";
+        dependencies = [
+          {
+            name = "clap_builder";
+            packageId = "clap_builder";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "clap_derive";
+            packageId = "clap_derive";
+            optional = true;
+          }
+        ];
+        features = {
+          "cargo" = [ "clap_builder/cargo" ];
+          "color" = [ "clap_builder/color" ];
+          "debug" = [ "clap_builder/debug" "clap_derive?/debug" ];
+          "default" = [ "std" "color" "help" "usage" "error-context" "suggestions" ];
+          "deprecated" = [ "clap_builder/deprecated" "clap_derive?/deprecated" ];
+          "derive" = [ "dep:clap_derive" ];
+          "env" = [ "clap_builder/env" ];
+          "error-context" = [ "clap_builder/error-context" ];
+          "help" = [ "clap_builder/help" ];
+          "std" = [ "clap_builder/std" ];
+          "string" = [ "clap_builder/string" ];
+          "suggestions" = [ "clap_builder/suggestions" ];
+          "unicode" = [ "clap_builder/unicode" ];
+          "unstable-doc" = [ "clap_builder/unstable-doc" "derive" ];
+          "unstable-styles" = [ "clap_builder/unstable-styles" ];
+          "unstable-v5" = [ "clap_builder/unstable-v5" "clap_derive?/unstable-v5" "deprecated" ];
+          "usage" = [ "clap_builder/usage" ];
+          "wrap_help" = [ "clap_builder/wrap_help" ];
+        };
+        resolvedDefaultFeatures = [ "color" "default" "derive" "error-context" "help" "std" "suggestions" "usage" ];
+      };
+      "clap_builder" = rec {
+        crateName = "clap_builder";
+        version = "4.4.18";
+        edition = "2021";
+        sha256 = "1iyif47075caa4x1p3ygk18b07lb4xl4k48w4c061i2hxi0dzx2d";
+        dependencies = [
+          {
+            name = "anstream";
+            packageId = "anstream";
+            optional = true;
+          }
+          {
+            name = "anstyle";
+            packageId = "anstyle";
+          }
+          {
+            name = "clap_lex";
+            packageId = "clap_lex";
+          }
+          {
+            name = "strsim";
+            packageId = "strsim";
+            optional = true;
+          }
+        ];
+        features = {
+          "color" = [ "dep:anstream" ];
+          "debug" = [ "dep:backtrace" ];
+          "default" = [ "std" "color" "help" "usage" "error-context" "suggestions" ];
+          "std" = [ "anstyle/std" ];
+          "suggestions" = [ "dep:strsim" "error-context" ];
+          "unicode" = [ "dep:unicode-width" "dep:unicase" ];
+          "unstable-doc" = [ "cargo" "wrap_help" "env" "unicode" "string" ];
+          "unstable-styles" = [ "color" ];
+          "unstable-v5" = [ "deprecated" ];
+          "wrap_help" = [ "help" "dep:terminal_size" ];
+        };
+        resolvedDefaultFeatures = [ "color" "error-context" "help" "std" "suggestions" "usage" ];
+      };
+      "clap_derive" = rec {
+        crateName = "clap_derive";
+        version = "4.4.7";
+        edition = "2021";
+        sha256 = "0hk4hcxl56qwqsf4hmf7c0gr19r9fbxk0ah2bgkr36pmmaph966g";
+        procMacro = true;
+        dependencies = [
+          {
+            name = "heck";
+            packageId = "heck";
+          }
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn 2.0.39";
+            features = [ "full" ];
+          }
+        ];
+        features = {
+          "raw-deprecated" = [ "deprecated" ];
+          "unstable-v5" = [ "deprecated" ];
+        };
+        resolvedDefaultFeatures = [ "default" ];
+      };
+      "clap_lex" = rec {
+        crateName = "clap_lex";
+        version = "0.6.0";
+        edition = "2021";
+        sha256 = "1l8bragdvim7mva9flvd159dskn2bdkpl0jqrr41wnjfn8pcfbvh";
+
+      };
+      "colorchoice" = rec {
+        crateName = "colorchoice";
+        version = "1.0.0";
+        edition = "2021";
+        sha256 = "1ix7w85kwvyybwi2jdkl3yva2r2bvdcc3ka2grjfzfgrapqimgxc";
+
+      };
+      "console" = rec {
+        crateName = "console";
+        version = "0.15.7";
+        edition = "2018";
+        sha256 = "1y6cbwadid5g4fyn4xq9c0s7mfavqqfg6prs9p3gvphfqw6f09n9";
+        authors = [
+          "Armin Ronacher <armin.ronacher@active-4.com>"
+        ];
+        dependencies = [
+          {
+            name = "encode_unicode";
+            packageId = "encode_unicode";
+            target = { target, features }: (target."windows" or false);
+          }
+          {
+            name = "lazy_static";
+            packageId = "lazy_static";
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+          }
+          {
+            name = "unicode-width";
+            packageId = "unicode-width";
+            optional = true;
+          }
+          {
+            name = "windows-sys";
+            packageId = "windows-sys 0.45.0";
+            target = { target, features }: (target."windows" or false);
+            features = [ "Win32_Foundation" "Win32_System_Console" "Win32_Storage_FileSystem" "Win32_UI_Input_KeyboardAndMouse" ];
+          }
+        ];
+        features = {
+          "default" = [ "unicode-width" "ansi-parsing" ];
+          "unicode-width" = [ "dep:unicode-width" ];
+          "windows-console-colors" = [ "ansi-parsing" ];
+        };
+        resolvedDefaultFeatures = [ "ansi-parsing" "unicode-width" ];
+      };
+      "const-oid" = rec {
+        crateName = "const-oid";
+        version = "0.9.5";
+        edition = "2021";
+        sha256 = "0vxb4d25mgk8y0phay7j078limx2553716ixsr1x5605k31j5h98";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        features = {
+          "arbitrary" = [ "dep:arbitrary" ];
+        };
+      };
+      "constant_time_eq" = rec {
+        crateName = "constant_time_eq";
+        version = "0.3.0";
+        edition = "2021";
+        sha256 = "1hl0y8frzlhpr58rh8rlg4bm53ax09ikj2i5fk7gpyphvhq4s57p";
+        authors = [
+          "Cesar Eduardo Barros <cesarb@cesarb.eti.br>"
+        ];
+        features = { };
+      };
+      "core-foundation" = rec {
+        crateName = "core-foundation";
+        version = "0.9.3";
+        edition = "2015";
+        sha256 = "0ii1ihpjb30fk38gdikm5wqlkmyr8k46fh4k2r8sagz5dng7ljhr";
+        authors = [
+          "The Servo Project Developers"
+        ];
+        dependencies = [
+          {
+            name = "core-foundation-sys";
+            packageId = "core-foundation-sys";
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+          }
+        ];
+        features = {
+          "chrono" = [ "dep:chrono" ];
+          "mac_os_10_7_support" = [ "core-foundation-sys/mac_os_10_7_support" ];
+          "mac_os_10_8_features" = [ "core-foundation-sys/mac_os_10_8_features" ];
+          "uuid" = [ "dep:uuid" ];
+          "with-chrono" = [ "chrono" ];
+          "with-uuid" = [ "uuid" ];
+        };
+      };
+      "core-foundation-sys" = rec {
+        crateName = "core-foundation-sys";
+        version = "0.8.4";
+        edition = "2015";
+        sha256 = "1yhf471qj6snnm2mcswai47vsbc9w30y4abmdp4crb4av87sb5p4";
+        authors = [
+          "The Servo Project Developers"
+        ];
+        features = { };
+      };
+      "cpufeatures" = rec {
+        crateName = "cpufeatures";
+        version = "0.2.11";
+        edition = "2018";
+        sha256 = "1l0gzsyy576n017g9bf0vkv5hhg9cpz1h1libxyfdlzcgbh0yhnf";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (pkgs.rust.lib.toRustTarget stdenv.hostPlatform == "aarch64-linux-android");
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (("aarch64" == target."arch" or null) && ("linux" == target."os" or null));
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (("aarch64" == target."arch" or null) && ("apple" == target."vendor" or null));
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (("loongarch64" == target."arch" or null) && ("linux" == target."os" or null));
+          }
+        ];
+
+      };
+      "crc32fast" = rec {
+        crateName = "crc32fast";
+        version = "1.3.2";
+        edition = "2015";
+        sha256 = "03c8f29yx293yf43xar946xbls1g60c207m9drf8ilqhr25vsh5m";
+        authors = [
+          "Sam Rijs <srijs@airpost.net>"
+          "Alex Crichton <alex@alexcrichton.com>"
+        ];
+        dependencies = [
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "crossbeam-channel" = rec {
+        crateName = "crossbeam-channel";
+        version = "0.5.8";
+        edition = "2018";
+        sha256 = "004jz4wxp9k26z657i7rsh9s7586dklx2c5aqf1n3w1dgzvjng53";
+        dependencies = [
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "crossbeam-utils";
+            packageId = "crossbeam-utils";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "crossbeam-utils" = [ "dep:crossbeam-utils" ];
+          "default" = [ "std" ];
+          "std" = [ "crossbeam-utils/std" ];
+        };
+        resolvedDefaultFeatures = [ "crossbeam-utils" "default" "std" ];
+      };
+      "crossbeam-deque" = rec {
+        crateName = "crossbeam-deque";
+        version = "0.8.3";
+        edition = "2018";
+        sha256 = "1vqczbcild7nczh5z116w8w46z991kpjyw7qxkf24c14apwdcvyf";
+        dependencies = [
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "crossbeam-epoch";
+            packageId = "crossbeam-epoch";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "crossbeam-utils";
+            packageId = "crossbeam-utils";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "crossbeam-epoch" = [ "dep:crossbeam-epoch" ];
+          "crossbeam-utils" = [ "dep:crossbeam-utils" ];
+          "default" = [ "std" ];
+          "std" = [ "crossbeam-epoch/std" "crossbeam-utils/std" ];
+        };
+        resolvedDefaultFeatures = [ "crossbeam-epoch" "crossbeam-utils" "default" "std" ];
+      };
+      "crossbeam-epoch" = rec {
+        crateName = "crossbeam-epoch";
+        version = "0.9.15";
+        edition = "2018";
+        sha256 = "1ixwc3cq816wb8rlh3ix4jnybqbyyq4l61nwlx0mfm3ck0s148df";
+        dependencies = [
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "crossbeam-utils";
+            packageId = "crossbeam-utils";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "memoffset";
+            packageId = "memoffset";
+          }
+          {
+            name = "scopeguard";
+            packageId = "scopeguard";
+            usesDefaultFeatures = false;
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "autocfg";
+            packageId = "autocfg";
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "loom" = [ "loom-crate" "crossbeam-utils/loom" ];
+          "loom-crate" = [ "dep:loom-crate" ];
+          "nightly" = [ "crossbeam-utils/nightly" ];
+          "std" = [ "alloc" "crossbeam-utils/std" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "std" ];
+      };
+      "crossbeam-queue" = rec {
+        crateName = "crossbeam-queue";
+        version = "0.3.8";
+        edition = "2018";
+        sha256 = "1p9s6n4ckwdgxkb7a8ay9zjzmgc8ppfbxix2vr07rwskibmb7kyi";
+        dependencies = [
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "crossbeam-utils";
+            packageId = "crossbeam-utils";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "nightly" = [ "crossbeam-utils/nightly" ];
+          "std" = [ "alloc" "crossbeam-utils/std" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "std" ];
+      };
+      "crossbeam-utils" = rec {
+        crateName = "crossbeam-utils";
+        version = "0.8.16";
+        edition = "2018";
+        sha256 = "153j0gikblz7n7qdvdi8pslhi008s1yp9cmny6vw07ad7pbb48js";
+        dependencies = [
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "loom" = [ "dep:loom" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "crunch-v2" = rec {
+        crateName = "crunch-v2";
+        version = "0.1.0";
+        edition = "2021";
+        crateBin = [
+          {
+            name = "crunch-v2";
+            path = "src/main.rs";
+            requiredFeatures = [ ];
+          }
+          {
+            name = "extract";
+            path = "src/bin/extract.rs";
+            requiredFeatures = [ ];
+          }
+        ];
+        # We can't filter paths with references in Nix 2.4
+        # See https://github.com/NixOS/nix/issues/5410
+        src =
+          if ((lib.versionOlder builtins.nixVersion "2.4pre20211007") || (lib.versionOlder "2.5" builtins.nixVersion))
+          then lib.cleanSourceWith { filter = sourceFilter; src = ./.; }
+          else ./.;
+        dependencies = [
+          {
+            name = "anyhow";
+            packageId = "anyhow";
+            features = [ "backtrace" ];
+          }
+          {
+            name = "blake3";
+            packageId = "blake3";
+          }
+          {
+            name = "bstr";
+            packageId = "bstr";
+          }
+          {
+            name = "bytes";
+            packageId = "bytes";
+          }
+          {
+            name = "bzip2";
+            packageId = "bzip2";
+          }
+          {
+            name = "clap";
+            packageId = "clap";
+            features = [ "derive" ];
+          }
+          {
+            name = "digest";
+            packageId = "digest 0.10.7";
+          }
+          {
+            name = "fastcdc";
+            packageId = "fastcdc";
+          }
+          {
+            name = "futures";
+            packageId = "futures";
+          }
+          {
+            name = "indicatif";
+            packageId = "indicatif";
+          }
+          {
+            name = "lazy_static";
+            packageId = "lazy_static";
+          }
+          {
+            name = "nix-compat";
+            packageId = "nix-compat";
+          }
+          {
+            name = "polars";
+            packageId = "polars";
+            usesDefaultFeatures = false;
+            features = [ "parquet" "lazy" "sql" "dtype-struct" ];
+          }
+          {
+            name = "prost";
+            packageId = "prost";
+          }
+          {
+            name = "rusoto_core";
+            packageId = "rusoto_core";
+            usesDefaultFeatures = false;
+            features = [ "hyper-rustls" ];
+          }
+          {
+            name = "rusoto_s3";
+            packageId = "rusoto_s3";
+            usesDefaultFeatures = false;
+            features = [ "rustls" ];
+          }
+          {
+            name = "sha2";
+            packageId = "sha2 0.10.8";
+            features = [ "asm" ];
+          }
+          {
+            name = "sled";
+            packageId = "sled";
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "full" ];
+          }
+          {
+            name = "xz2";
+            packageId = "xz2";
+          }
+          {
+            name = "zstd";
+            packageId = "zstd";
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "prost-build";
+            packageId = "prost-build";
+          }
+        ];
+
+      };
+      "crypto-common" = rec {
+        crateName = "crypto-common";
+        version = "0.1.6";
+        edition = "2018";
+        sha256 = "1cvby95a6xg7kxdz5ln3rl9xh66nz66w46mm3g56ri1z5x815yqv";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        dependencies = [
+          {
+            name = "generic-array";
+            packageId = "generic-array";
+            features = [ "more_lengths" ];
+          }
+          {
+            name = "typenum";
+            packageId = "typenum";
+          }
+        ];
+        features = {
+          "getrandom" = [ "rand_core/getrandom" ];
+          "rand_core" = [ "dep:rand_core" ];
+        };
+        resolvedDefaultFeatures = [ "std" ];
+      };
+      "crypto-mac" = rec {
+        crateName = "crypto-mac";
+        version = "0.11.1";
+        edition = "2018";
+        sha256 = "05672ncc54h66vph42s0a42ljl69bwnqjh0x4xgj2v1395psildi";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        dependencies = [
+          {
+            name = "generic-array";
+            packageId = "generic-array";
+          }
+          {
+            name = "subtle";
+            packageId = "subtle";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "blobby" = [ "dep:blobby" ];
+          "cipher" = [ "dep:cipher" ];
+          "dev" = [ "blobby" ];
+        };
+      };
+      "curve25519-dalek" = rec {
+        crateName = "curve25519-dalek";
+        version = "4.1.1";
+        edition = "2021";
+        sha256 = "0p7ns5917k6369gajrsbfj24llc5zfm635yh3abla7sb5rm8r6z8";
+        authors = [
+          "Isis Lovecruft <isis@patternsinthevoid.net>"
+          "Henry de Valence <hdevalence@hdevalence.ca>"
+        ];
+        dependencies = [
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "cpufeatures";
+            packageId = "cpufeatures";
+            target = { target, features }: ("x86_64" == target."arch" or null);
+          }
+          {
+            name = "curve25519-dalek-derive";
+            packageId = "curve25519-dalek-derive";
+            target = { target, features }: ((!("fiat" == target."curve25519_dalek_backend" or null)) && (!("serial" == target."curve25519_dalek_backend" or null)) && ("x86_64" == target."arch" or null));
+          }
+          {
+            name = "digest";
+            packageId = "digest 0.10.7";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "fiat-crypto";
+            packageId = "fiat-crypto";
+            usesDefaultFeatures = false;
+            target = { target, features }: ("fiat" == target."curve25519_dalek_backend" or null);
+          }
+          {
+            name = "subtle";
+            packageId = "subtle";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "zeroize";
+            packageId = "zeroize";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "platforms";
+            packageId = "platforms";
+          }
+          {
+            name = "rustc_version";
+            packageId = "rustc_version";
+          }
+        ];
+        features = {
+          "alloc" = [ "zeroize?/alloc" ];
+          "default" = [ "alloc" "precomputed-tables" "zeroize" ];
+          "digest" = [ "dep:digest" ];
+          "ff" = [ "dep:ff" ];
+          "group" = [ "dep:group" "rand_core" ];
+          "group-bits" = [ "group" "ff/bits" ];
+          "rand_core" = [ "dep:rand_core" ];
+          "serde" = [ "dep:serde" ];
+          "zeroize" = [ "dep:zeroize" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "digest" "precomputed-tables" "zeroize" ];
+      };
+      "curve25519-dalek-derive" = rec {
+        crateName = "curve25519-dalek-derive";
+        version = "0.1.1";
+        edition = "2021";
+        sha256 = "1cry71xxrr0mcy5my3fb502cwfxy6822k4pm19cwrilrg7hq4s7l";
+        procMacro = true;
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn 2.0.39";
+            features = [ "full" ];
+          }
+        ];
+
+      };
+      "data-encoding" = rec {
+        crateName = "data-encoding";
+        version = "2.4.0";
+        edition = "2018";
+        sha256 = "023k3dk8422jgbj7k72g63x51h1mhv91dhw1j4h205vzh6fnrrn2";
+        authors = [
+          "Julien Cretin <git@ia0.eu>"
+        ];
+        features = {
+          "default" = [ "std" ];
+          "std" = [ "alloc" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "std" ];
+      };
+      "der" = rec {
+        crateName = "der";
+        version = "0.7.8";
+        edition = "2021";
+        sha256 = "070bwiyr80800h31c5zd96ckkgagfjgnrrdmz3dzg2lccsd3dypz";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        dependencies = [
+          {
+            name = "const-oid";
+            packageId = "const-oid";
+            optional = true;
+          }
+          {
+            name = "zeroize";
+            packageId = "zeroize";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "alloc" = [ "zeroize?/alloc" ];
+          "arbitrary" = [ "dep:arbitrary" "const-oid?/arbitrary" "std" ];
+          "bytes" = [ "dep:bytes" "alloc" ];
+          "derive" = [ "dep:der_derive" ];
+          "flagset" = [ "dep:flagset" ];
+          "oid" = [ "dep:const-oid" ];
+          "pem" = [ "dep:pem-rfc7468" "alloc" "zeroize" ];
+          "std" = [ "alloc" ];
+          "time" = [ "dep:time" ];
+          "zeroize" = [ "dep:zeroize" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "oid" "std" "zeroize" ];
+      };
+      "digest 0.10.7" = rec {
+        crateName = "digest";
+        version = "0.10.7";
+        edition = "2018";
+        sha256 = "14p2n6ih29x81akj097lvz7wi9b6b9hvls0lwrv7b6xwyy0s5ncy";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        dependencies = [
+          {
+            name = "block-buffer";
+            packageId = "block-buffer 0.10.4";
+            optional = true;
+          }
+          {
+            name = "crypto-common";
+            packageId = "crypto-common";
+          }
+        ];
+        features = {
+          "blobby" = [ "dep:blobby" ];
+          "block-buffer" = [ "dep:block-buffer" ];
+          "const-oid" = [ "dep:const-oid" ];
+          "core-api" = [ "block-buffer" ];
+          "default" = [ "core-api" ];
+          "dev" = [ "blobby" ];
+          "mac" = [ "subtle" ];
+          "oid" = [ "const-oid" ];
+          "rand_core" = [ "crypto-common/rand_core" ];
+          "std" = [ "alloc" "crypto-common/std" ];
+          "subtle" = [ "dep:subtle" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "block-buffer" "core-api" "default" "std" ];
+      };
+      "digest 0.9.0" = rec {
+        crateName = "digest";
+        version = "0.9.0";
+        edition = "2018";
+        sha256 = "0rmhvk33rgvd6ll71z8sng91a52rw14p0drjn1da0mqa138n1pfk";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        dependencies = [
+          {
+            name = "generic-array";
+            packageId = "generic-array";
+          }
+        ];
+        features = {
+          "blobby" = [ "dep:blobby" ];
+          "dev" = [ "blobby" ];
+          "std" = [ "alloc" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "std" ];
+      };
+      "dirs-next" = rec {
+        crateName = "dirs-next";
+        version = "2.0.0";
+        edition = "2018";
+        sha256 = "1q9kr151h9681wwp6is18750ssghz6j9j7qm7qi1ngcwy7mzi35r";
+        authors = [
+          "The @xdg-rs members"
+        ];
+        dependencies = [
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "dirs-sys-next";
+            packageId = "dirs-sys-next";
+          }
+        ];
+
+      };
+      "dirs-sys-next" = rec {
+        crateName = "dirs-sys-next";
+        version = "0.1.2";
+        edition = "2018";
+        sha256 = "0kavhavdxv4phzj4l0psvh55hszwnr0rcz8sxbvx20pyqi2a3gaf";
+        authors = [
+          "The @xdg-rs members"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (target."unix" or false);
+          }
+          {
+            name = "redox_users";
+            packageId = "redox_users";
+            usesDefaultFeatures = false;
+            target = { target, features }: ("redox" == target."os" or null);
+          }
+          {
+            name = "winapi";
+            packageId = "winapi";
+            target = { target, features }: (target."windows" or false);
+            features = [ "knownfolders" "objbase" "shlobj" "winbase" "winerror" ];
+          }
+        ];
+
+      };
+      "dyn-clone" = rec {
+        crateName = "dyn-clone";
+        version = "1.0.16";
+        edition = "2018";
+        sha256 = "0pa9kas6a241pbx0q82ipwi4f7m7wwyzkkc725caky24gl4j4nsl";
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+
+      };
+      "ed25519" = rec {
+        crateName = "ed25519";
+        version = "2.2.3";
+        edition = "2021";
+        sha256 = "0lydzdf26zbn82g7xfczcac9d7mzm3qgx934ijjrd5hjpjx32m8i";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        dependencies = [
+          {
+            name = "pkcs8";
+            packageId = "pkcs8";
+            optional = true;
+          }
+          {
+            name = "signature";
+            packageId = "signature";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "alloc" = [ "pkcs8?/alloc" ];
+          "default" = [ "std" ];
+          "pem" = [ "alloc" "pkcs8/pem" ];
+          "pkcs8" = [ "dep:pkcs8" ];
+          "serde" = [ "dep:serde" ];
+          "serde_bytes" = [ "serde" "dep:serde_bytes" ];
+          "std" = [ "pkcs8?/std" "signature/std" ];
+          "zeroize" = [ "dep:zeroize" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "std" ];
+      };
+      "ed25519-dalek" = rec {
+        crateName = "ed25519-dalek";
+        version = "2.1.0";
+        edition = "2021";
+        sha256 = "1h13qm789m9gdjl6jazss80hqi8ll37m0afwcnw23zcbqjp8wqhz";
+        authors = [
+          "isis lovecruft <isis@patternsinthevoid.net>"
+          "Tony Arcieri <bascule@gmail.com>"
+          "Michael Rosenberg <michael@mrosenberg.pub>"
+        ];
+        dependencies = [
+          {
+            name = "curve25519-dalek";
+            packageId = "curve25519-dalek";
+            usesDefaultFeatures = false;
+            features = [ "digest" ];
+          }
+          {
+            name = "ed25519";
+            packageId = "ed25519";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "sha2";
+            packageId = "sha2 0.10.8";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "subtle";
+            packageId = "subtle";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "zeroize";
+            packageId = "zeroize";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+        ];
+        devDependencies = [
+          {
+            name = "curve25519-dalek";
+            packageId = "curve25519-dalek";
+            usesDefaultFeatures = false;
+            features = [ "digest" "rand_core" ];
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+            features = [ "derive" ];
+          }
+        ];
+        features = {
+          "alloc" = [ "curve25519-dalek/alloc" "ed25519/alloc" "serde?/alloc" "zeroize/alloc" ];
+          "asm" = [ "sha2/asm" ];
+          "batch" = [ "alloc" "merlin" "rand_core" ];
+          "default" = [ "fast" "std" "zeroize" ];
+          "digest" = [ "signature/digest" ];
+          "fast" = [ "curve25519-dalek/precomputed-tables" ];
+          "legacy_compatibility" = [ "curve25519-dalek/legacy_compatibility" ];
+          "merlin" = [ "dep:merlin" ];
+          "pem" = [ "alloc" "ed25519/pem" "pkcs8" ];
+          "pkcs8" = [ "ed25519/pkcs8" ];
+          "rand_core" = [ "dep:rand_core" ];
+          "serde" = [ "dep:serde" "ed25519/serde" ];
+          "signature" = [ "dep:signature" ];
+          "std" = [ "alloc" "ed25519/std" "serde?/std" "sha2/std" ];
+          "zeroize" = [ "dep:zeroize" "curve25519-dalek/zeroize" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "fast" "std" "zeroize" ];
+      };
+      "either" = rec {
+        crateName = "either";
+        version = "1.9.0";
+        edition = "2018";
+        sha256 = "01qy3anr7jal5lpc20791vxrw0nl6vksb5j7x56q2fycgcyy8sm2";
+        authors = [
+          "bluss"
+        ];
+        features = {
+          "default" = [ "use_std" ];
+          "serde" = [ "dep:serde" ];
+        };
+        resolvedDefaultFeatures = [ "default" "use_std" ];
+      };
+      "encode_unicode" = rec {
+        crateName = "encode_unicode";
+        version = "0.3.6";
+        edition = "2015";
+        sha256 = "07w3vzrhxh9lpjgsg2y5bwzfar2aq35mdznvcp3zjl0ssj7d4mx3";
+        authors = [
+          "Torbjørn Birch Moltu <t.b.moltu@lyse.net>"
+        ];
+        features = {
+          "ascii" = [ "dep:ascii" ];
+          "clippy" = [ "dep:clippy" ];
+          "default" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "enum_dispatch" = rec {
+        crateName = "enum_dispatch";
+        version = "0.3.12";
+        edition = "2018";
+        sha256 = "03l998igqfzkykmj8i5qlbwhv2id9jn98fkkl82lv3dvg0q32cwg";
+        procMacro = true;
+        authors = [
+          "Anton Lazarev <https://antonok.com>"
+        ];
+        dependencies = [
+          {
+            name = "once_cell";
+            packageId = "once_cell";
+          }
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn 2.0.39";
+            features = [ "full" ];
+          }
+        ];
+
+      };
+      "equivalent" = rec {
+        crateName = "equivalent";
+        version = "1.0.1";
+        edition = "2015";
+        sha256 = "1malmx5f4lkfvqasz319lq6gb3ddg19yzf9s8cykfsgzdmyq0hsl";
+
+      };
+      "errno" = rec {
+        crateName = "errno";
+        version = "0.3.7";
+        edition = "2018";
+        sha256 = "1f4f7s0wngfxwh6a4kq3aws3i37xnzm3m4d86xw2lz3z9qcsfn7j";
+        authors = [
+          "Chris Wong <lambda.fairy@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+            usesDefaultFeatures = false;
+            target = { target, features }: ("hermit" == target."os" or null);
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            usesDefaultFeatures = false;
+            target = { target, features }: ("wasi" == target."os" or null);
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            usesDefaultFeatures = false;
+            target = { target, features }: (target."unix" or false);
+          }
+          {
+            name = "windows-sys";
+            packageId = "windows-sys 0.48.0";
+            target = { target, features }: (target."windows" or false);
+            features = [ "Win32_Foundation" "Win32_System_Diagnostics_Debug" ];
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "std" = [ "libc/std" ];
+        };
+        resolvedDefaultFeatures = [ "std" ];
+      };
+      "ethnum" = rec {
+        crateName = "ethnum";
+        version = "1.5.0";
+        edition = "2021";
+        sha256 = "0b68ngvisb0d40vc6h30zlhghbb3mc8wlxjbf8gnmavk1dca435r";
+        authors = [
+          "Nicholas Rodrigues Lordello <nlordell@gmail.com>"
+        ];
+        features = {
+          "ethnum-intrinsics" = [ "dep:ethnum-intrinsics" ];
+          "llvm-intrinsics" = [ "ethnum-intrinsics" ];
+          "serde" = [ "dep:serde" ];
+        };
+      };
+      "fallible-streaming-iterator" = rec {
+        crateName = "fallible-streaming-iterator";
+        version = "0.1.9";
+        edition = "2015";
+        sha256 = "0nj6j26p71bjy8h42x6jahx1hn0ng6mc2miwpgwnp8vnwqf4jq3k";
+        authors = [
+          "Steven Fackler <sfackler@gmail.com>"
+        ];
+        features = { };
+      };
+      "fast-float" = rec {
+        crateName = "fast-float";
+        version = "0.2.0";
+        edition = "2018";
+        sha256 = "0g7kfll3xyh99kc7r352lhljnwvgayxxa6saifb6725inikmyxlm";
+        authors = [
+          "Ivan Smirnov <i.s.smirnov@gmail.com>"
+        ];
+        features = {
+          "default" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "fastcdc" = rec {
+        crateName = "fastcdc";
+        version = "3.1.0";
+        edition = "2018";
+        sha256 = "1wi82qd58j3ysf8m2dhb092qga6rj1wwbppgsajabadzjz862457";
+        authors = [
+          "Nathan Fiedler <nathanfiedler@fastmail.fm>"
+        ];
+        features = {
+          "async-stream" = [ "dep:async-stream" ];
+          "futures" = [ "dep:futures" ];
+          "tokio" = [ "dep:tokio" "tokio-stream" "async-stream" ];
+          "tokio-stream" = [ "dep:tokio-stream" ];
+        };
+        resolvedDefaultFeatures = [ "default" ];
+      };
+      "fastrand" = rec {
+        crateName = "fastrand";
+        version = "2.0.1";
+        edition = "2018";
+        sha256 = "19flpv5zbzpf0rk4x77z4zf25in0brg8l7m304d3yrf47qvwxjr5";
+        authors = [
+          "Stjepan Glavina <stjepang@gmail.com>"
+        ];
+        features = {
+          "default" = [ "std" ];
+          "getrandom" = [ "dep:getrandom" ];
+          "js" = [ "std" "getrandom" ];
+          "std" = [ "alloc" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "std" ];
+      };
+      "fiat-crypto" = rec {
+        crateName = "fiat-crypto";
+        version = "0.2.5";
+        edition = "2018";
+        sha256 = "1dxn0g50pv0ppal779vi7k40fr55pbhkyv4in7i13pgl4sn3wmr7";
+        authors = [
+          "Fiat Crypto library authors <jgross@mit.edu>"
+        ];
+        features = {
+          "default" = [ "std" ];
+        };
+      };
+      "fixedbitset" = rec {
+        crateName = "fixedbitset";
+        version = "0.4.2";
+        edition = "2015";
+        sha256 = "101v41amgv5n9h4hcghvrbfk5vrncx1jwm35rn5szv4rk55i7rqc";
+        authors = [
+          "bluss"
+        ];
+        features = {
+          "default" = [ "std" ];
+          "serde" = [ "dep:serde" ];
+        };
+      };
+      "flate2" = rec {
+        crateName = "flate2";
+        version = "1.0.28";
+        edition = "2018";
+        sha256 = "03llhsh4gqdirnfxxb9g2w9n0721dyn4yjir3pz7z4vjaxb3yc26";
+        authors = [
+          "Alex Crichton <alex@alexcrichton.com>"
+          "Josh Triplett <josh@joshtriplett.org>"
+        ];
+        dependencies = [
+          {
+            name = "crc32fast";
+            packageId = "crc32fast";
+          }
+          {
+            name = "miniz_oxide";
+            packageId = "miniz_oxide";
+            optional = true;
+            usesDefaultFeatures = false;
+            features = [ "with-alloc" ];
+          }
+          {
+            name = "miniz_oxide";
+            packageId = "miniz_oxide";
+            usesDefaultFeatures = false;
+            target = { target, features }: (("wasm32" == target."arch" or null) && (!("emscripten" == target."os" or null)));
+            features = [ "with-alloc" ];
+          }
+        ];
+        features = {
+          "any_zlib" = [ "any_impl" ];
+          "cloudflare-zlib-sys" = [ "dep:cloudflare-zlib-sys" ];
+          "cloudflare_zlib" = [ "any_zlib" "cloudflare-zlib-sys" ];
+          "default" = [ "rust_backend" ];
+          "libz-ng-sys" = [ "dep:libz-ng-sys" ];
+          "libz-sys" = [ "dep:libz-sys" ];
+          "miniz-sys" = [ "rust_backend" ];
+          "miniz_oxide" = [ "dep:miniz_oxide" ];
+          "rust_backend" = [ "miniz_oxide" "any_impl" ];
+          "zlib" = [ "any_zlib" "libz-sys" ];
+          "zlib-default" = [ "any_zlib" "libz-sys/default" ];
+          "zlib-ng" = [ "any_zlib" "libz-ng-sys" ];
+          "zlib-ng-compat" = [ "zlib" "libz-sys/zlib-ng" ];
+        };
+        resolvedDefaultFeatures = [ "any_impl" "miniz_oxide" "rust_backend" ];
+      };
+      "fnv" = rec {
+        crateName = "fnv";
+        version = "1.0.7";
+        edition = "2015";
+        sha256 = "1hc2mcqha06aibcaza94vbi81j6pr9a1bbxrxjfhc91zin8yr7iz";
+        libPath = "lib.rs";
+        authors = [
+          "Alex Crichton <alex@alexcrichton.com>"
+        ];
+        features = {
+          "default" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "foreign_vec" = rec {
+        crateName = "foreign_vec";
+        version = "0.1.0";
+        edition = "2021";
+        sha256 = "0wv6p8yfahcqbdg2wg7wxgj4dm32g2b6spa5sg5sxg34v35ha6zf";
+        authors = [
+          "Jorge C. Leitao <jorgecarleitao@gmail.com>"
+        ];
+
+      };
+      "fs2" = rec {
+        crateName = "fs2";
+        version = "0.4.3";
+        edition = "2015";
+        sha256 = "04v2hwk7035c088f19mfl5b1lz84gnvv2hv6m935n0hmirszqr4m";
+        authors = [
+          "Dan Burkert <dan@danburkert.com>"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (target."unix" or false);
+          }
+          {
+            name = "winapi";
+            packageId = "winapi";
+            target = { target, features }: (target."windows" or false);
+            features = [ "handleapi" "processthreadsapi" "winerror" "fileapi" "winbase" "std" ];
+          }
+        ];
+
+      };
+      "futures" = rec {
+        crateName = "futures";
+        version = "0.3.29";
+        edition = "2018";
+        sha256 = "0dak2ilpcmyjrb1j54fzy9hlw6vd10vqljq9gd59pbrq9dqr00ns";
+        dependencies = [
+          {
+            name = "futures-channel";
+            packageId = "futures-channel";
+            usesDefaultFeatures = false;
+            features = [ "sink" ];
+          }
+          {
+            name = "futures-core";
+            packageId = "futures-core";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-executor";
+            packageId = "futures-executor";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-io";
+            packageId = "futures-io";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-sink";
+            packageId = "futures-sink";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-task";
+            packageId = "futures-task";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-util";
+            packageId = "futures-util";
+            usesDefaultFeatures = false;
+            features = [ "sink" ];
+          }
+        ];
+        features = {
+          "alloc" = [ "futures-core/alloc" "futures-task/alloc" "futures-sink/alloc" "futures-channel/alloc" "futures-util/alloc" ];
+          "async-await" = [ "futures-util/async-await" "futures-util/async-await-macro" ];
+          "bilock" = [ "futures-util/bilock" ];
+          "compat" = [ "std" "futures-util/compat" ];
+          "default" = [ "std" "async-await" "executor" ];
+          "executor" = [ "std" "futures-executor/std" ];
+          "futures-executor" = [ "dep:futures-executor" ];
+          "io-compat" = [ "compat" "futures-util/io-compat" ];
+          "std" = [ "alloc" "futures-core/std" "futures-task/std" "futures-io/std" "futures-sink/std" "futures-util/std" "futures-util/io" "futures-util/channel" ];
+          "thread-pool" = [ "executor" "futures-executor/thread-pool" ];
+          "unstable" = [ "futures-core/unstable" "futures-task/unstable" "futures-channel/unstable" "futures-io/unstable" "futures-util/unstable" ];
+          "write-all-vectored" = [ "futures-util/write-all-vectored" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "async-await" "default" "executor" "futures-executor" "std" ];
+      };
+      "futures-channel" = rec {
+        crateName = "futures-channel";
+        version = "0.3.29";
+        edition = "2018";
+        sha256 = "1jxsifvrbqzdadk0svbax71cba5d3qg3wgjq8i160mxmd1kdckgz";
+        dependencies = [
+          {
+            name = "futures-core";
+            packageId = "futures-core";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-sink";
+            packageId = "futures-sink";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "alloc" = [ "futures-core/alloc" ];
+          "default" = [ "std" ];
+          "futures-sink" = [ "dep:futures-sink" ];
+          "sink" = [ "futures-sink" ];
+          "std" = [ "alloc" "futures-core/std" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "futures-sink" "sink" "std" ];
+      };
+      "futures-core" = rec {
+        crateName = "futures-core";
+        version = "0.3.29";
+        edition = "2018";
+        sha256 = "1308bpj0g36nhx2y6bl4mm6f1gnh9xyvvw2q2wpdgnb6dv3247gb";
+        features = {
+          "default" = [ "std" ];
+          "portable-atomic" = [ "dep:portable-atomic" ];
+          "std" = [ "alloc" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "std" ];
+      };
+      "futures-executor" = rec {
+        crateName = "futures-executor";
+        version = "0.3.29";
+        edition = "2018";
+        sha256 = "1g4pjni0sw28djx6mlcfz584abm2lpifz86cmng0kkxh7mlvhkqg";
+        dependencies = [
+          {
+            name = "futures-core";
+            packageId = "futures-core";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-task";
+            packageId = "futures-task";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-util";
+            packageId = "futures-util";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "num_cpus" = [ "dep:num_cpus" ];
+          "std" = [ "futures-core/std" "futures-task/std" "futures-util/std" ];
+          "thread-pool" = [ "std" "num_cpus" ];
+        };
+        resolvedDefaultFeatures = [ "std" ];
+      };
+      "futures-io" = rec {
+        crateName = "futures-io";
+        version = "0.3.29";
+        edition = "2018";
+        sha256 = "1ajsljgny3zfxwahba9byjzclrgvm1ypakca8z854k2w7cb4mwwb";
+        features = {
+          "default" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "std" ];
+      };
+      "futures-macro" = rec {
+        crateName = "futures-macro";
+        version = "0.3.29";
+        edition = "2018";
+        sha256 = "1nwd18i8kvpkdfwm045hddjli0n96zi7pn6f99zi9c74j7ym7cak";
+        procMacro = true;
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn 2.0.39";
+            features = [ "full" ];
+          }
+        ];
+
+      };
+      "futures-sink" = rec {
+        crateName = "futures-sink";
+        version = "0.3.29";
+        edition = "2018";
+        sha256 = "05q8jykqddxzp8nwf00wjk5m5mqi546d7i8hsxma7hiqxrw36vg3";
+        features = {
+          "default" = [ "std" ];
+          "std" = [ "alloc" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "std" ];
+      };
+      "futures-task" = rec {
+        crateName = "futures-task";
+        version = "0.3.29";
+        edition = "2018";
+        sha256 = "1qmsss8rb5ppql4qvd4r70h9gpfcpd0bg2b3qilxrnhdkc397lgg";
+        features = {
+          "default" = [ "std" ];
+          "std" = [ "alloc" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "std" ];
+      };
+      "futures-util" = rec {
+        crateName = "futures-util";
+        version = "0.3.29";
+        edition = "2018";
+        sha256 = "0141rkqh0psj4h8x8lgsl1p29dhqr7z2wcixkcbs60z74kb2d5d1";
+        dependencies = [
+          {
+            name = "futures-channel";
+            packageId = "futures-channel";
+            optional = true;
+            usesDefaultFeatures = false;
+            features = [ "std" ];
+          }
+          {
+            name = "futures-core";
+            packageId = "futures-core";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-io";
+            packageId = "futures-io";
+            optional = true;
+            usesDefaultFeatures = false;
+            features = [ "std" ];
+          }
+          {
+            name = "futures-macro";
+            packageId = "futures-macro";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-sink";
+            packageId = "futures-sink";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-task";
+            packageId = "futures-task";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "memchr";
+            packageId = "memchr";
+            optional = true;
+          }
+          {
+            name = "pin-project-lite";
+            packageId = "pin-project-lite";
+          }
+          {
+            name = "pin-utils";
+            packageId = "pin-utils";
+          }
+          {
+            name = "slab";
+            packageId = "slab";
+            optional = true;
+          }
+        ];
+        features = {
+          "alloc" = [ "futures-core/alloc" "futures-task/alloc" ];
+          "async-await-macro" = [ "async-await" "futures-macro" ];
+          "channel" = [ "std" "futures-channel" ];
+          "compat" = [ "std" "futures_01" ];
+          "default" = [ "std" "async-await" "async-await-macro" ];
+          "futures-channel" = [ "dep:futures-channel" ];
+          "futures-io" = [ "dep:futures-io" ];
+          "futures-macro" = [ "dep:futures-macro" ];
+          "futures-sink" = [ "dep:futures-sink" ];
+          "futures_01" = [ "dep:futures_01" ];
+          "io" = [ "std" "futures-io" "memchr" ];
+          "io-compat" = [ "io" "compat" "tokio-io" ];
+          "memchr" = [ "dep:memchr" ];
+          "portable-atomic" = [ "futures-core/portable-atomic" ];
+          "sink" = [ "futures-sink" ];
+          "slab" = [ "dep:slab" ];
+          "std" = [ "alloc" "futures-core/std" "futures-task/std" "slab" ];
+          "tokio-io" = [ "dep:tokio-io" ];
+          "unstable" = [ "futures-core/unstable" "futures-task/unstable" ];
+          "write-all-vectored" = [ "io" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "async-await" "async-await-macro" "channel" "futures-channel" "futures-io" "futures-macro" "futures-sink" "io" "memchr" "sink" "slab" "std" ];
+      };
+      "fxhash" = rec {
+        crateName = "fxhash";
+        version = "0.2.1";
+        edition = "2015";
+        sha256 = "037mb9ichariqi45xm6mz0b11pa92gj38ba0409z3iz239sns6y3";
+        libPath = "lib.rs";
+        authors = [
+          "cbreeden <github@u.breeden.cc>"
+        ];
+        dependencies = [
+          {
+            name = "byteorder";
+            packageId = "byteorder";
+          }
+        ];
+
+      };
+      "generic-array" = rec {
+        crateName = "generic-array";
+        version = "0.14.7";
+        edition = "2015";
+        sha256 = "16lyyrzrljfq424c3n8kfwkqihlimmsg5nhshbbp48np3yjrqr45";
+        libName = "generic_array";
+        authors = [
+          "Bartłomiej Kamiński <fizyk20@gmail.com>"
+          "Aaron Trent <novacrazy@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "typenum";
+            packageId = "typenum";
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "version_check";
+            packageId = "version_check";
+          }
+        ];
+        features = {
+          "serde" = [ "dep:serde" ];
+          "zeroize" = [ "dep:zeroize" ];
+        };
+        resolvedDefaultFeatures = [ "more_lengths" ];
+      };
+      "getrandom" = rec {
+        crateName = "getrandom";
+        version = "0.2.11";
+        edition = "2018";
+        sha256 = "03q7120cc2kn7ry013i67zmjl2g9q73h1ks5z08hq5v9syz0d47y";
+        authors = [
+          "The Rand Project Developers"
+        ];
+        dependencies = [
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "js-sys";
+            packageId = "js-sys";
+            optional = true;
+            target = { target, features }: ((("wasm32" == target."arch" or null) || ("wasm64" == target."arch" or null)) && ("unknown" == target."os" or null));
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            usesDefaultFeatures = false;
+            target = { target, features }: (target."unix" or false);
+          }
+          {
+            name = "wasi";
+            packageId = "wasi";
+            usesDefaultFeatures = false;
+            target = { target, features }: ("wasi" == target."os" or null);
+          }
+          {
+            name = "wasm-bindgen";
+            packageId = "wasm-bindgen";
+            optional = true;
+            usesDefaultFeatures = false;
+            target = { target, features }: ((("wasm32" == target."arch" or null) || ("wasm64" == target."arch" or null)) && ("unknown" == target."os" or null));
+          }
+        ];
+        features = {
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "core" = [ "dep:core" ];
+          "js" = [ "wasm-bindgen" "js-sys" ];
+          "js-sys" = [ "dep:js-sys" ];
+          "rustc-dep-of-std" = [ "compiler_builtins" "core" "libc/rustc-dep-of-std" "wasi/rustc-dep-of-std" ];
+          "wasm-bindgen" = [ "dep:wasm-bindgen" ];
+        };
+        resolvedDefaultFeatures = [ "js" "js-sys" "std" "wasm-bindgen" ];
+      };
+      "gimli" = rec {
+        crateName = "gimli";
+        version = "0.28.0";
+        edition = "2018";
+        sha256 = "1h7hcl3chfvd2gfrrxjymnwj7anqxjslvz20kcargkvsya2dgf3g";
+        features = {
+          "default" = [ "read-all" "write" ];
+          "endian-reader" = [ "read" "dep:stable_deref_trait" ];
+          "fallible-iterator" = [ "dep:fallible-iterator" ];
+          "read" = [ "read-core" ];
+          "read-all" = [ "read" "std" "fallible-iterator" "endian-reader" ];
+          "rustc-dep-of-std" = [ "dep:core" "dep:alloc" "dep:compiler_builtins" ];
+          "std" = [ "fallible-iterator?/std" "stable_deref_trait?/std" ];
+          "write" = [ "dep:indexmap" ];
+        };
+        resolvedDefaultFeatures = [ "read" "read-core" ];
+      };
+      "glob" = rec {
+        crateName = "glob";
+        version = "0.3.1";
+        edition = "2015";
+        sha256 = "16zca52nglanv23q5qrwd5jinw3d3as5ylya6y1pbx47vkxvrynj";
+        authors = [
+          "The Rust Project Developers"
+        ];
+
+      };
+      "h2" = rec {
+        crateName = "h2";
+        version = "0.3.22";
+        edition = "2018";
+        sha256 = "0y41jlflvw8niifdirgng67zdmic62cjf5m2z69hzrpn5qr50qjd";
+        authors = [
+          "Carl Lerche <me@carllerche.com>"
+          "Sean McArthur <sean@seanmonstar.com>"
+        ];
+        dependencies = [
+          {
+            name = "bytes";
+            packageId = "bytes";
+          }
+          {
+            name = "fnv";
+            packageId = "fnv";
+          }
+          {
+            name = "futures-core";
+            packageId = "futures-core";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-sink";
+            packageId = "futures-sink";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-util";
+            packageId = "futures-util";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "http";
+            packageId = "http";
+          }
+          {
+            name = "indexmap";
+            packageId = "indexmap";
+            features = [ "std" ];
+          }
+          {
+            name = "slab";
+            packageId = "slab";
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "io-util" ];
+          }
+          {
+            name = "tokio-util";
+            packageId = "tokio-util";
+            features = [ "codec" "io" ];
+          }
+          {
+            name = "tracing";
+            packageId = "tracing";
+            usesDefaultFeatures = false;
+            features = [ "std" ];
+          }
+        ];
+        devDependencies = [
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "rt-multi-thread" "macros" "sync" "net" ];
+          }
+        ];
+        features = { };
+      };
+      "hashbrown" = rec {
+        crateName = "hashbrown";
+        version = "0.14.2";
+        edition = "2021";
+        sha256 = "0mj1x1d16acxf4zg7wr7q2x8pgzfi1bzpifygcsxmg4d2n972gpr";
+        authors = [
+          "Amanieu d'Antras <amanieu@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "ahash";
+            packageId = "ahash";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "allocator-api2";
+            packageId = "allocator-api2";
+            optional = true;
+            usesDefaultFeatures = false;
+            features = [ "alloc" ];
+          }
+          {
+            name = "rayon";
+            packageId = "rayon";
+            optional = true;
+          }
+        ];
+        devDependencies = [
+          {
+            name = "rayon";
+            packageId = "rayon";
+          }
+        ];
+        features = {
+          "ahash" = [ "dep:ahash" ];
+          "alloc" = [ "dep:alloc" ];
+          "allocator-api2" = [ "dep:allocator-api2" ];
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "core" = [ "dep:core" ];
+          "default" = [ "ahash" "inline-more" "allocator-api2" ];
+          "equivalent" = [ "dep:equivalent" ];
+          "nightly" = [ "allocator-api2?/nightly" "bumpalo/allocator_api" ];
+          "rayon" = [ "dep:rayon" ];
+          "rkyv" = [ "dep:rkyv" ];
+          "rustc-dep-of-std" = [ "nightly" "core" "compiler_builtins" "alloc" "rustc-internal-api" ];
+          "serde" = [ "dep:serde" ];
+        };
+        resolvedDefaultFeatures = [ "ahash" "allocator-api2" "default" "inline-more" "raw" "rayon" ];
+      };
+      "heck" = rec {
+        crateName = "heck";
+        version = "0.4.1";
+        edition = "2018";
+        sha256 = "1a7mqsnycv5z4z5vnv1k34548jzmc0ajic7c1j8jsaspnhw5ql4m";
+        authors = [
+          "Without Boats <woboats@gmail.com>"
+        ];
+        features = {
+          "unicode" = [ "unicode-segmentation" ];
+          "unicode-segmentation" = [ "dep:unicode-segmentation" ];
+        };
+        resolvedDefaultFeatures = [ "default" ];
+      };
+      "hermit-abi" = rec {
+        crateName = "hermit-abi";
+        version = "0.3.3";
+        edition = "2021";
+        sha256 = "1dyc8qsjh876n74a3rcz8h43s27nj1sypdhsn2ms61bd3b47wzyp";
+        authors = [
+          "Stefan Lankes"
+        ];
+        features = {
+          "alloc" = [ "dep:alloc" ];
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "core" = [ "dep:core" ];
+          "rustc-dep-of-std" = [ "core" "alloc" "compiler_builtins/rustc-dep-of-std" ];
+        };
+        resolvedDefaultFeatures = [ "default" ];
+      };
+      "hex" = rec {
+        crateName = "hex";
+        version = "0.4.3";
+        edition = "2018";
+        sha256 = "0w1a4davm1lgzpamwnba907aysmlrnygbqmfis2mqjx5m552a93z";
+        authors = [
+          "KokaKiwi <kokakiwi@kokakiwi.net>"
+        ];
+        features = {
+          "default" = [ "std" ];
+          "serde" = [ "dep:serde" ];
+          "std" = [ "alloc" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "std" ];
+      };
+      "hmac" = rec {
+        crateName = "hmac";
+        version = "0.11.0";
+        edition = "2018";
+        sha256 = "16z61aibdg4di40sqi4ks2s4rz6r29w4sx4gvblfph3yxch26aia";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        dependencies = [
+          {
+            name = "crypto-mac";
+            packageId = "crypto-mac";
+          }
+          {
+            name = "digest";
+            packageId = "digest 0.9.0";
+          }
+        ];
+        devDependencies = [
+          {
+            name = "crypto-mac";
+            packageId = "crypto-mac";
+            features = [ "dev" ];
+          }
+        ];
+        features = {
+          "std" = [ "crypto-mac/std" ];
+        };
+      };
+      "home" = rec {
+        crateName = "home";
+        version = "0.5.5";
+        edition = "2018";
+        sha256 = "1nqx1krijvpd03d96avsdyknd12h8hs3xhxwgqghf8v9xxzc4i2l";
+        authors = [
+          "Brian Anderson <andersrb@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "windows-sys";
+            packageId = "windows-sys 0.48.0";
+            target = { target, features }: (target."windows" or false);
+            features = [ "Win32_Foundation" "Win32_UI_Shell" ];
+          }
+        ];
+
+      };
+      "http" = rec {
+        crateName = "http";
+        version = "0.2.11";
+        edition = "2018";
+        sha256 = "1fwz3mhh86h5kfnr5767jlx9agpdggclq7xsqx930fflzakb2iw9";
+        authors = [
+          "Alex Crichton <alex@alexcrichton.com>"
+          "Carl Lerche <me@carllerche.com>"
+          "Sean McArthur <sean@seanmonstar.com>"
+        ];
+        dependencies = [
+          {
+            name = "bytes";
+            packageId = "bytes";
+          }
+          {
+            name = "fnv";
+            packageId = "fnv";
+          }
+          {
+            name = "itoa";
+            packageId = "itoa";
+          }
+        ];
+
+      };
+      "http-body" = rec {
+        crateName = "http-body";
+        version = "0.4.5";
+        edition = "2018";
+        sha256 = "1l967qwwlvhp198xdrnc0p5d7jwfcp6q2lm510j6zqw4s4b8zwym";
+        authors = [
+          "Carl Lerche <me@carllerche.com>"
+          "Lucio Franco <luciofranco14@gmail.com>"
+          "Sean McArthur <sean@seanmonstar.com>"
+        ];
+        dependencies = [
+          {
+            name = "bytes";
+            packageId = "bytes";
+          }
+          {
+            name = "http";
+            packageId = "http";
+          }
+          {
+            name = "pin-project-lite";
+            packageId = "pin-project-lite";
+          }
+        ];
+
+      };
+      "httparse" = rec {
+        crateName = "httparse";
+        version = "1.8.0";
+        edition = "2018";
+        sha256 = "010rrfahm1jss3p022fqf3j3jmm72vhn4iqhykahb9ynpaag75yq";
+        authors = [
+          "Sean McArthur <sean@seanmonstar.com>"
+        ];
+        features = {
+          "default" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "httpdate" = rec {
+        crateName = "httpdate";
+        version = "1.0.3";
+        edition = "2021";
+        sha256 = "1aa9rd2sac0zhjqh24c9xvir96g188zldkx0hr6dnnlx5904cfyz";
+        authors = [
+          "Pyfisch <pyfisch@posteo.org>"
+        ];
+
+      };
+      "hyper" = rec {
+        crateName = "hyper";
+        version = "0.14.27";
+        edition = "2018";
+        sha256 = "0s2l74p3harvjgb0bvaxlxgxq71vpfrzv0cqz2p9w8d8akbczcgz";
+        authors = [
+          "Sean McArthur <sean@seanmonstar.com>"
+        ];
+        dependencies = [
+          {
+            name = "bytes";
+            packageId = "bytes";
+          }
+          {
+            name = "futures-channel";
+            packageId = "futures-channel";
+          }
+          {
+            name = "futures-core";
+            packageId = "futures-core";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-util";
+            packageId = "futures-util";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "h2";
+            packageId = "h2";
+            optional = true;
+          }
+          {
+            name = "http";
+            packageId = "http";
+          }
+          {
+            name = "http-body";
+            packageId = "http-body";
+          }
+          {
+            name = "httparse";
+            packageId = "httparse";
+          }
+          {
+            name = "httpdate";
+            packageId = "httpdate";
+          }
+          {
+            name = "itoa";
+            packageId = "itoa";
+          }
+          {
+            name = "pin-project-lite";
+            packageId = "pin-project-lite";
+          }
+          {
+            name = "socket2";
+            packageId = "socket2 0.4.10";
+            optional = true;
+            features = [ "all" ];
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "sync" ];
+          }
+          {
+            name = "tower-service";
+            packageId = "tower-service";
+          }
+          {
+            name = "tracing";
+            packageId = "tracing";
+            usesDefaultFeatures = false;
+            features = [ "std" ];
+          }
+          {
+            name = "want";
+            packageId = "want";
+          }
+        ];
+        devDependencies = [
+          {
+            name = "futures-util";
+            packageId = "futures-util";
+            usesDefaultFeatures = false;
+            features = [ "alloc" ];
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "fs" "macros" "io-std" "io-util" "rt" "rt-multi-thread" "sync" "time" "test-util" ];
+          }
+        ];
+        features = {
+          "ffi" = [ "libc" ];
+          "full" = [ "client" "http1" "http2" "server" "stream" "runtime" ];
+          "h2" = [ "dep:h2" ];
+          "http2" = [ "h2" ];
+          "libc" = [ "dep:libc" ];
+          "runtime" = [ "tcp" "tokio/rt" "tokio/time" ];
+          "socket2" = [ "dep:socket2" ];
+          "tcp" = [ "socket2" "tokio/net" "tokio/rt" "tokio/time" ];
+        };
+        resolvedDefaultFeatures = [ "client" "default" "h2" "http1" "http2" "runtime" "socket2" "stream" "tcp" ];
+      };
+      "hyper-rustls" = rec {
+        crateName = "hyper-rustls";
+        version = "0.23.2";
+        edition = "2018";
+        sha256 = "0736s6a32dqr107f943xaz1n05flbinq6l19lq1wsrxkc5g9d20p";
+        dependencies = [
+          {
+            name = "http";
+            packageId = "http";
+          }
+          {
+            name = "hyper";
+            packageId = "hyper";
+            usesDefaultFeatures = false;
+            features = [ "client" ];
+          }
+          {
+            name = "log";
+            packageId = "log";
+            optional = true;
+          }
+          {
+            name = "rustls";
+            packageId = "rustls";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "rustls-native-certs";
+            packageId = "rustls-native-certs";
+            optional = true;
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+          }
+          {
+            name = "tokio-rustls";
+            packageId = "tokio-rustls";
+            usesDefaultFeatures = false;
+          }
+        ];
+        devDependencies = [
+          {
+            name = "hyper";
+            packageId = "hyper";
+            features = [ "full" ];
+          }
+          {
+            name = "rustls";
+            packageId = "rustls";
+            usesDefaultFeatures = false;
+            features = [ "tls12" ];
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "io-std" "macros" "net" "rt-multi-thread" ];
+          }
+        ];
+        features = {
+          "default" = [ "native-tokio" "http1" "tls12" "logging" ];
+          "http1" = [ "hyper/http1" ];
+          "http2" = [ "hyper/http2" ];
+          "log" = [ "dep:log" ];
+          "logging" = [ "log" "tokio-rustls/logging" "rustls/logging" ];
+          "native-tokio" = [ "tokio-runtime" "rustls-native-certs" ];
+          "rustls-native-certs" = [ "dep:rustls-native-certs" ];
+          "tls12" = [ "tokio-rustls/tls12" "rustls/tls12" ];
+          "tokio-runtime" = [ "hyper/runtime" ];
+          "webpki-roots" = [ "dep:webpki-roots" ];
+          "webpki-tokio" = [ "tokio-runtime" "webpki-roots" ];
+        };
+        resolvedDefaultFeatures = [ "http1" "http2" "log" "logging" "native-tokio" "rustls-native-certs" "tls12" "tokio-runtime" ];
+      };
+      "iana-time-zone" = rec {
+        crateName = "iana-time-zone";
+        version = "0.1.58";
+        edition = "2018";
+        sha256 = "081vcr8z8ddhl5r1ywif6grnswk01b2ac4nks2bhn8zzdimvh9l3";
+        authors = [
+          "Andrew Straw <strawman@astraw.com>"
+          "René Kijewski <rene.kijewski@fu-berlin.de>"
+          "Ryan Lopopolo <rjl@hyperbo.la>"
+        ];
+        dependencies = [
+          {
+            name = "android_system_properties";
+            packageId = "android_system_properties";
+            target = { target, features }: ("android" == target."os" or null);
+          }
+          {
+            name = "core-foundation-sys";
+            packageId = "core-foundation-sys";
+            target = { target, features }: (("macos" == target."os" or null) || ("ios" == target."os" or null));
+          }
+          {
+            name = "iana-time-zone-haiku";
+            packageId = "iana-time-zone-haiku";
+            target = { target, features }: ("haiku" == target."os" or null);
+          }
+          {
+            name = "js-sys";
+            packageId = "js-sys";
+            target = { target, features }: ("wasm32" == target."arch" or null);
+          }
+          {
+            name = "wasm-bindgen";
+            packageId = "wasm-bindgen";
+            target = { target, features }: ("wasm32" == target."arch" or null);
+          }
+          {
+            name = "windows-core";
+            packageId = "windows-core";
+            target = { target, features }: ("windows" == target."os" or null);
+          }
+        ];
+        features = { };
+        resolvedDefaultFeatures = [ "fallback" ];
+      };
+      "iana-time-zone-haiku" = rec {
+        crateName = "iana-time-zone-haiku";
+        version = "0.1.2";
+        edition = "2018";
+        sha256 = "17r6jmj31chn7xs9698r122mapq85mfnv98bb4pg6spm0si2f67k";
+        authors = [
+          "René Kijewski <crates.io@k6i.de>"
+        ];
+        buildDependencies = [
+          {
+            name = "cc";
+            packageId = "cc";
+          }
+        ];
+
+      };
+      "indexmap" = rec {
+        crateName = "indexmap";
+        version = "2.1.0";
+        edition = "2021";
+        sha256 = "07rxrqmryr1xfnmhrjlz8ic6jw28v6h5cig3ws2c9d0wifhy2c6m";
+        dependencies = [
+          {
+            name = "equivalent";
+            packageId = "equivalent";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "hashbrown";
+            packageId = "hashbrown";
+            usesDefaultFeatures = false;
+            features = [ "raw" ];
+          }
+        ];
+        features = {
+          "arbitrary" = [ "dep:arbitrary" ];
+          "default" = [ "std" ];
+          "quickcheck" = [ "dep:quickcheck" ];
+          "rayon" = [ "dep:rayon" ];
+          "rustc-rayon" = [ "dep:rustc-rayon" ];
+          "serde" = [ "dep:serde" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "indicatif" = rec {
+        crateName = "indicatif";
+        version = "0.17.7";
+        edition = "2021";
+        sha256 = "098ggvg7ps4097p5n9hmb3pqqy10bi8vjfzb7pci79xrklf78a7v";
+        dependencies = [
+          {
+            name = "console";
+            packageId = "console";
+            usesDefaultFeatures = false;
+            features = [ "ansi-parsing" ];
+          }
+          {
+            name = "instant";
+            packageId = "instant";
+            target = { target, features }: ("wasm32" == target."arch" or null);
+          }
+          {
+            name = "number_prefix";
+            packageId = "number_prefix";
+          }
+          {
+            name = "portable-atomic";
+            packageId = "portable-atomic";
+          }
+          {
+            name = "unicode-width";
+            packageId = "unicode-width";
+            optional = true;
+          }
+        ];
+        features = {
+          "default" = [ "unicode-width" "console/unicode-width" ];
+          "futures" = [ "dep:futures-core" ];
+          "improved_unicode" = [ "unicode-segmentation" "unicode-width" "console/unicode-width" ];
+          "in_memory" = [ "vt100" ];
+          "rayon" = [ "dep:rayon" ];
+          "tokio" = [ "dep:tokio" ];
+          "unicode-segmentation" = [ "dep:unicode-segmentation" ];
+          "unicode-width" = [ "dep:unicode-width" ];
+          "vt100" = [ "dep:vt100" ];
+        };
+        resolvedDefaultFeatures = [ "default" "unicode-width" ];
+      };
+      "instant" = rec {
+        crateName = "instant";
+        version = "0.1.12";
+        edition = "2018";
+        sha256 = "0b2bx5qdlwayriidhrag8vhy10kdfimfhmb3jnjmsz2h9j1bwnvs";
+        authors = [
+          "sebcrozet <developer@crozet.re>"
+        ];
+        dependencies = [
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+        ];
+        features = {
+          "js-sys" = [ "dep:js-sys" ];
+          "stdweb" = [ "dep:stdweb" ];
+          "wasm-bindgen" = [ "js-sys" "wasm-bindgen_rs" "web-sys" ];
+          "wasm-bindgen_rs" = [ "dep:wasm-bindgen_rs" ];
+          "web-sys" = [ "dep:web-sys" ];
+        };
+      };
+      "itertools" = rec {
+        crateName = "itertools";
+        version = "0.11.0";
+        edition = "2018";
+        sha256 = "0mzyqcc59azx9g5cg6fs8k529gvh4463smmka6jvzs3cd2jp7hdi";
+        authors = [
+          "bluss"
+        ];
+        dependencies = [
+          {
+            name = "either";
+            packageId = "either";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "default" = [ "use_std" ];
+          "use_std" = [ "use_alloc" "either/use_std" ];
+        };
+        resolvedDefaultFeatures = [ "use_alloc" ];
+      };
+      "itoa" = rec {
+        crateName = "itoa";
+        version = "1.0.9";
+        edition = "2018";
+        sha256 = "0f6cpb4yqzhkrhhg6kqsw3wnmmhdnnffi6r2xzy248gzi2v0l5dg";
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        features = {
+          "no-panic" = [ "dep:no-panic" ];
+        };
+      };
+      "jobserver" = rec {
+        crateName = "jobserver";
+        version = "0.1.27";
+        edition = "2018";
+        sha256 = "0z9w6vfqwbr6hfk9yaw7kydlh6f7k39xdlszxlh39in4acwzcdwc";
+        authors = [
+          "Alex Crichton <alex@alexcrichton.com>"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (target."unix" or false);
+          }
+        ];
+
+      };
+      "js-sys" = rec {
+        crateName = "js-sys";
+        version = "0.3.65";
+        edition = "2018";
+        sha256 = "1s1gaxgzpqfyygc7f2pwp9y128rh5f8zvsc4nm5yazgna9cw7h2l";
+        authors = [
+          "The wasm-bindgen Developers"
+        ];
+        dependencies = [
+          {
+            name = "wasm-bindgen";
+            packageId = "wasm-bindgen";
+          }
+        ];
+
+      };
+      "lazy_static" = rec {
+        crateName = "lazy_static";
+        version = "1.4.0";
+        edition = "2015";
+        sha256 = "0in6ikhw8mgl33wjv6q6xfrb5b9jr16q8ygjy803fay4zcisvaz2";
+        authors = [
+          "Marvin Löbel <loebel.marvin@gmail.com>"
+        ];
+        features = {
+          "spin" = [ "dep:spin" ];
+          "spin_no_std" = [ "spin" ];
+        };
+      };
+      "libc" = rec {
+        crateName = "libc";
+        version = "0.2.150";
+        edition = "2015";
+        sha256 = "0g10n8c830alndgjb8xk1i9kz5z727np90z1z81119pr8d3jmnc9";
+        authors = [
+          "The Rust Project Developers"
+        ];
+        features = {
+          "default" = [ "std" ];
+          "rustc-dep-of-std" = [ "align" "rustc-std-workspace-core" ];
+          "rustc-std-workspace-core" = [ "dep:rustc-std-workspace-core" ];
+          "use_std" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "extra_traits" "std" ];
+      };
+      "libm" = rec {
+        crateName = "libm";
+        version = "0.2.8";
+        edition = "2018";
+        sha256 = "0n4hk1rs8pzw8hdfmwn96c4568s93kfxqgcqswr7sajd2diaihjf";
+        authors = [
+          "Jorge Aparicio <jorge@japaric.io>"
+        ];
+        features = {
+          "musl-reference-tests" = [ "rand" ];
+          "rand" = [ "dep:rand" ];
+        };
+        resolvedDefaultFeatures = [ "default" ];
+      };
+      "libredox" = rec {
+        crateName = "libredox";
+        version = "0.0.1";
+        edition = "2021";
+        sha256 = "1s2fh4ikpp9xl0lsl01pi0n8pw1q9s3ld452vd8qh1v63v537j45";
+        authors = [
+          "4lDO2 <4lDO2@protonmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "bitflags";
+            packageId = "bitflags 2.4.1";
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+          }
+          {
+            name = "redox_syscall";
+            packageId = "redox_syscall 0.4.1";
+          }
+        ];
+        features = {
+          "default" = [ "scheme" "call" ];
+          "scheme" = [ "call" ];
+        };
+        resolvedDefaultFeatures = [ "call" ];
+      };
+      "linux-raw-sys" = rec {
+        crateName = "linux-raw-sys";
+        version = "0.4.11";
+        edition = "2021";
+        sha256 = "0adqqaya81s7k5r323g65pw6q85pxd1x4prz9whh5i4abysqi54n";
+        authors = [
+          "Dan Gohman <dev@sunfishcode.online>"
+        ];
+        features = {
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "core" = [ "dep:core" ];
+          "default" = [ "std" "general" "errno" ];
+          "rustc-dep-of-std" = [ "core" "compiler_builtins" "no_std" ];
+        };
+        resolvedDefaultFeatures = [ "elf" "errno" "general" "ioctl" "no_std" ];
+      };
+      "lock_api" = rec {
+        crateName = "lock_api";
+        version = "0.4.11";
+        edition = "2018";
+        sha256 = "0iggx0h4jx63xm35861106af3jkxq06fpqhpkhgw0axi2n38y5iw";
+        authors = [
+          "Amanieu d'Antras <amanieu@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "scopeguard";
+            packageId = "scopeguard";
+            usesDefaultFeatures = false;
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "autocfg";
+            packageId = "autocfg";
+          }
+        ];
+        features = {
+          "default" = [ "atomic_usize" ];
+          "owning_ref" = [ "dep:owning_ref" ];
+          "serde" = [ "dep:serde" ];
+        };
+        resolvedDefaultFeatures = [ "atomic_usize" "default" ];
+      };
+      "log" = rec {
+        crateName = "log";
+        version = "0.4.20";
+        edition = "2015";
+        sha256 = "13rf7wphnwd61vazpxr7fiycin6cb1g8fmvgqg18i464p0y1drmm";
+        authors = [
+          "The Rust Project Developers"
+        ];
+        features = {
+          "kv_unstable" = [ "value-bag" ];
+          "kv_unstable_serde" = [ "kv_unstable_std" "value-bag/serde" "serde" ];
+          "kv_unstable_std" = [ "std" "kv_unstable" "value-bag/error" ];
+          "kv_unstable_sval" = [ "kv_unstable" "value-bag/sval" "sval" "sval_ref" ];
+          "serde" = [ "dep:serde" ];
+          "sval" = [ "dep:sval" ];
+          "sval_ref" = [ "dep:sval_ref" ];
+          "value-bag" = [ "dep:value-bag" ];
+        };
+      };
+      "lz4" = rec {
+        crateName = "lz4";
+        version = "1.24.0";
+        edition = "2018";
+        crateBin = [ ];
+        sha256 = "1wad97k0asgvaj16ydd09gqs2yvgaanzcvqglrhffv7kdpc2v7ky";
+        authors = [
+          "Jens Heyens <jens.heyens@ewetel.net>"
+          "Artem V. Navrotskiy <bozaro@buzzsoft.ru>"
+          "Patrick Marks <pmarks@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+          }
+          {
+            name = "lz4-sys";
+            packageId = "lz4-sys";
+          }
+        ];
+
+      };
+      "lz4-sys" = rec {
+        crateName = "lz4-sys";
+        version = "1.9.4";
+        edition = "2015";
+        links = "lz4";
+        sha256 = "0059ik4xlvnss5qfh6l691psk4g3350ljxaykzv10yr0gqqppljp";
+        authors = [
+          "Jens Heyens <jens.heyens@ewetel.net>"
+          "Artem V. Navrotskiy <bozaro@buzzsoft.ru>"
+          "Patrick Marks <pmarks@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "cc";
+            packageId = "cc";
+          }
+        ];
+
+      };
+      "lzma-sys" = rec {
+        crateName = "lzma-sys";
+        version = "0.1.20";
+        edition = "2018";
+        links = "lzma";
+        sha256 = "09sxp20waxyglgn3cjz8qjkspb3ryz2fwx4rigkwvrk46ymh9njz";
+        authors = [
+          "Alex Crichton <alex@alexcrichton.com>"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "cc";
+            packageId = "cc";
+          }
+          {
+            name = "pkg-config";
+            packageId = "pkg-config";
+          }
+        ];
+        features = { };
+      };
+      "md-5" = rec {
+        crateName = "md-5";
+        version = "0.9.1";
+        edition = "2018";
+        sha256 = "059ajjacz1q3cms7vl6cvhdqs4qdw2nnwj9dq99ryzv0p6djfnkv";
+        libName = "md5";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        dependencies = [
+          {
+            name = "block-buffer";
+            packageId = "block-buffer 0.9.0";
+          }
+          {
+            name = "digest";
+            packageId = "digest 0.9.0";
+          }
+          {
+            name = "opaque-debug";
+            packageId = "opaque-debug";
+          }
+        ];
+        devDependencies = [
+          {
+            name = "digest";
+            packageId = "digest 0.9.0";
+            features = [ "dev" ];
+          }
+        ];
+        features = {
+          "asm" = [ "md5-asm" ];
+          "default" = [ "std" ];
+          "md5-asm" = [ "dep:md5-asm" ];
+          "std" = [ "digest/std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "memchr" = rec {
+        crateName = "memchr";
+        version = "2.6.4";
+        edition = "2021";
+        sha256 = "0rq1ka8790ns41j147npvxcqcl2anxyngsdimy85ag2api0fwrgn";
+        authors = [
+          "Andrew Gallant <jamslam@gmail.com>"
+          "bluss"
+        ];
+        features = {
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "core" = [ "dep:core" ];
+          "default" = [ "std" ];
+          "logging" = [ "dep:log" ];
+          "rustc-dep-of-std" = [ "core" "compiler_builtins" ];
+          "std" = [ "alloc" ];
+          "use_std" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "std" ];
+      };
+      "memmap2" = rec {
+        crateName = "memmap2";
+        version = "0.7.1";
+        edition = "2018";
+        sha256 = "1il82b0mw304jlwvl0m89aa8bj5dgmm3vbb0jg8lqlrk0p98i4zl";
+        authors = [
+          "Dan Burkert <dan@danburkert.com>"
+          "Yevhenii Reizner <razrfalcon@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (target."unix" or false);
+          }
+        ];
+        features = {
+          "stable_deref_trait" = [ "dep:stable_deref_trait" ];
+        };
+      };
+      "memoffset" = rec {
+        crateName = "memoffset";
+        version = "0.9.0";
+        edition = "2015";
+        sha256 = "0v20ihhdzkfw1jx00a7zjpk2dcp5qjq6lz302nyqamd9c4f4nqss";
+        authors = [
+          "Gilad Naaman <gilad.naaman@gmail.com>"
+        ];
+        buildDependencies = [
+          {
+            name = "autocfg";
+            packageId = "autocfg";
+          }
+        ];
+        features = { };
+        resolvedDefaultFeatures = [ "default" ];
+      };
+      "minimal-lexical" = rec {
+        crateName = "minimal-lexical";
+        version = "0.2.1";
+        edition = "2018";
+        sha256 = "16ppc5g84aijpri4jzv14rvcnslvlpphbszc7zzp6vfkddf4qdb8";
+        authors = [
+          "Alex Huszagh <ahuszagh@gmail.com>"
+        ];
+        features = {
+          "default" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "std" ];
+      };
+      "miniz_oxide" = rec {
+        crateName = "miniz_oxide";
+        version = "0.7.1";
+        edition = "2018";
+        sha256 = "1ivl3rbbdm53bzscrd01g60l46lz5krl270487d8lhjvwl5hx0g7";
+        authors = [
+          "Frommi <daniil.liferenko@gmail.com>"
+          "oyvindln <oyvindln@users.noreply.github.com>"
+        ];
+        dependencies = [
+          {
+            name = "adler";
+            packageId = "adler";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "alloc" = [ "dep:alloc" ];
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "core" = [ "dep:core" ];
+          "default" = [ "with-alloc" ];
+          "rustc-dep-of-std" = [ "core" "alloc" "compiler_builtins" "adler/rustc-dep-of-std" ];
+          "simd" = [ "simd-adler32" ];
+          "simd-adler32" = [ "dep:simd-adler32" ];
+        };
+        resolvedDefaultFeatures = [ "with-alloc" ];
+      };
+      "mio" = rec {
+        crateName = "mio";
+        version = "0.8.9";
+        edition = "2018";
+        sha256 = "1l23hg513c23nhcdzvk25caaj28mic6qgqadbn8axgj6bqf2ikix";
+        authors = [
+          "Carl Lerche <me@carllerche.com>"
+          "Thomas de Zeeuw <thomasdezeeuw@gmail.com>"
+          "Tokio Contributors <team@tokio.rs>"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: ("wasi" == target."os" or null);
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (target."unix" or false);
+          }
+          {
+            name = "wasi";
+            packageId = "wasi";
+            target = { target, features }: ("wasi" == target."os" or null);
+          }
+          {
+            name = "windows-sys";
+            packageId = "windows-sys 0.48.0";
+            target = { target, features }: (target."windows" or false);
+            features = [ "Win32_Foundation" "Win32_Networking_WinSock" "Win32_Storage_FileSystem" "Win32_System_IO" "Win32_System_WindowsProgramming" ];
+          }
+        ];
+        features = {
+          "default" = [ "log" ];
+          "log" = [ "dep:log" ];
+          "os-ext" = [ "os-poll" "windows-sys/Win32_System_Pipes" "windows-sys/Win32_Security" ];
+        };
+        resolvedDefaultFeatures = [ "net" "os-ext" "os-poll" ];
+      };
+      "multimap" = rec {
+        crateName = "multimap";
+        version = "0.8.3";
+        edition = "2015";
+        sha256 = "0sicyz4n500vdhgcxn4g8jz97cp1ijir1rnbgph3pmx9ckz4dkp5";
+        authors = [
+          "Håvar Nøvik <havar.novik@gmail.com>"
+        ];
+        features = {
+          "default" = [ "serde_impl" ];
+          "serde" = [ "dep:serde" ];
+          "serde_impl" = [ "serde" ];
+        };
+      };
+      "multiversion" = rec {
+        crateName = "multiversion";
+        version = "0.7.3";
+        edition = "2021";
+        sha256 = "0al7yrf489lqzxx291sx9566n7slk2njwlqrxbjhqxk1zvbvkixj";
+        authors = [
+          "Caleb Zulawski <caleb.zulawski@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "multiversion-macros";
+            packageId = "multiversion-macros";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "target-features";
+            packageId = "target-features";
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "std" = [ "multiversion-macros/std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "multiversion-macros" = rec {
+        crateName = "multiversion-macros";
+        version = "0.7.3";
+        edition = "2021";
+        sha256 = "1j1avbxw7jscyi7dmnywhlwbiny1fvg1vpp9fy4dc1pd022kva16";
+        procMacro = true;
+        authors = [
+          "Caleb Zulawski <caleb.zulawski@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn 1.0.109";
+            features = [ "full" "extra-traits" "visit-mut" ];
+          }
+          {
+            name = "target-features";
+            packageId = "target-features";
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "std" ];
+      };
+      "nix-compat" = rec {
+        crateName = "nix-compat";
+        version = "0.1.0";
+        edition = "2021";
+        crateBin = [ ];
+        # We can't filter paths with references in Nix 2.4
+        # See https://github.com/NixOS/nix/issues/5410
+        src =
+          if ((lib.versionOlder builtins.nixVersion "2.4pre20211007") || (lib.versionOlder "2.5" builtins.nixVersion))
+          then lib.cleanSourceWith { filter = sourceFilter; src = ../../nix-compat; }
+          else ../../nix-compat;
+        dependencies = [
+          {
+            name = "bitflags";
+            packageId = "bitflags 2.4.1";
+          }
+          {
+            name = "bstr";
+            packageId = "bstr";
+            features = [ "alloc" "unicode" "serde" ];
+          }
+          {
+            name = "data-encoding";
+            packageId = "data-encoding";
+          }
+          {
+            name = "ed25519";
+            packageId = "ed25519";
+          }
+          {
+            name = "ed25519-dalek";
+            packageId = "ed25519-dalek";
+          }
+          {
+            name = "glob";
+            packageId = "glob";
+          }
+          {
+            name = "nom";
+            packageId = "nom";
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+            features = [ "derive" ];
+          }
+          {
+            name = "serde_json";
+            packageId = "serde_json";
+          }
+          {
+            name = "sha2";
+            packageId = "sha2 0.10.8";
+          }
+          {
+            name = "thiserror";
+            packageId = "thiserror";
+          }
+        ];
+        devDependencies = [
+          {
+            name = "serde_json";
+            packageId = "serde_json";
+          }
+        ];
+        features = {
+          "async" = [ "futures-util" ];
+          "futures-util" = [ "dep:futures-util" ];
+        };
+      };
+      "nom" = rec {
+        crateName = "nom";
+        version = "7.1.3";
+        edition = "2018";
+        sha256 = "0jha9901wxam390jcf5pfa0qqfrgh8li787jx2ip0yk5b8y9hwyj";
+        authors = [
+          "contact@geoffroycouprie.com"
+        ];
+        dependencies = [
+          {
+            name = "memchr";
+            packageId = "memchr";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "minimal-lexical";
+            packageId = "minimal-lexical";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "std" = [ "alloc" "memchr/std" "minimal-lexical/std" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "std" ];
+      };
+      "now" = rec {
+        crateName = "now";
+        version = "0.1.3";
+        edition = "2018";
+        sha256 = "1l135786rb43rjfhwfdj7hi3b5zxxyl9gwf15yjz18cp8f3yk2bd";
+        authors = [
+          "Kilerd <blove694@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "chrono";
+            packageId = "chrono";
+            usesDefaultFeatures = false;
+            features = [ "clock" "std" ];
+          }
+        ];
+
+      };
+      "ntapi" = rec {
+        crateName = "ntapi";
+        version = "0.4.1";
+        edition = "2018";
+        sha256 = "1r38zhbwdvkis2mzs6671cm1p6djgsl49i7bwxzrvhwicdf8k8z8";
+        authors = [
+          "MSxDOS <melcodos@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "winapi";
+            packageId = "winapi";
+            features = [ "cfg" "evntrace" "in6addr" "inaddr" "minwinbase" "ntsecapi" "windef" "winioctl" ];
+          }
+        ];
+        features = {
+          "default" = [ "user" ];
+          "impl-default" = [ "winapi/impl-default" ];
+        };
+        resolvedDefaultFeatures = [ "default" "user" ];
+      };
+      "num-traits" = rec {
+        crateName = "num-traits";
+        version = "0.2.17";
+        edition = "2018";
+        sha256 = "0z16bi5zwgfysz6765v3rd6whfbjpihx3mhsn4dg8dzj2c221qrr";
+        authors = [
+          "The Rust Project Developers"
+        ];
+        dependencies = [
+          {
+            name = "libm";
+            packageId = "libm";
+            optional = true;
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "autocfg";
+            packageId = "autocfg";
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "libm" = [ "dep:libm" ];
+        };
+        resolvedDefaultFeatures = [ "default" "libm" "std" ];
+      };
+      "num_cpus" = rec {
+        crateName = "num_cpus";
+        version = "1.16.0";
+        edition = "2015";
+        sha256 = "0hra6ihpnh06dvfvz9ipscys0xfqa9ca9hzp384d5m02ssvgqqa1";
+        authors = [
+          "Sean McArthur <sean@seanmonstar.com>"
+        ];
+        dependencies = [
+          {
+            name = "hermit-abi";
+            packageId = "hermit-abi";
+            target = { target, features }: ("hermit" == target."os" or null);
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (!(target."windows" or false));
+          }
+        ];
+
+      };
+      "number_prefix" = rec {
+        crateName = "number_prefix";
+        version = "0.4.0";
+        edition = "2015";
+        sha256 = "1wvh13wvlajqxkb1filsfzbrnq0vrmrw298v2j3sy82z1rm282w3";
+        authors = [
+          "Benjamin Sago <ogham@bsago.me>"
+        ];
+        features = {
+          "default" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "object" = rec {
+        crateName = "object";
+        version = "0.32.1";
+        edition = "2018";
+        sha256 = "1c02x4kvqpnl3wn7gz9idm4jrbirbycyqjgiw6lm1g9k77fzkxcw";
+        dependencies = [
+          {
+            name = "memchr";
+            packageId = "memchr";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "all" = [ "read" "write" "std" "compression" "wasm" ];
+          "alloc" = [ "dep:alloc" ];
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "compression" = [ "dep:flate2" "dep:ruzstd" "std" ];
+          "core" = [ "dep:core" ];
+          "default" = [ "read" "compression" ];
+          "doc" = [ "read_core" "write_std" "std" "compression" "archive" "coff" "elf" "macho" "pe" "wasm" "xcoff" ];
+          "pe" = [ "coff" ];
+          "read" = [ "read_core" "archive" "coff" "elf" "macho" "pe" "xcoff" "unaligned" ];
+          "rustc-dep-of-std" = [ "core" "compiler_builtins" "alloc" "memchr/rustc-dep-of-std" ];
+          "std" = [ "memchr/std" ];
+          "unstable-all" = [ "all" "unstable" ];
+          "wasm" = [ "dep:wasmparser" ];
+          "write" = [ "write_std" "coff" "elf" "macho" "pe" "xcoff" ];
+          "write_core" = [ "dep:crc32fast" "dep:indexmap" "dep:hashbrown" ];
+          "write_std" = [ "write_core" "std" "indexmap?/std" "crc32fast?/std" ];
+        };
+        resolvedDefaultFeatures = [ "archive" "coff" "elf" "macho" "pe" "read_core" "unaligned" ];
+      };
+      "once_cell" = rec {
+        crateName = "once_cell";
+        version = "1.18.0";
+        edition = "2021";
+        sha256 = "0vapcd5ambwck95wyz3ymlim35jirgnqn9a0qmi19msymv95v2yx";
+        authors = [
+          "Aleksey Kladov <aleksey.kladov@gmail.com>"
+        ];
+        features = {
+          "alloc" = [ "race" ];
+          "atomic-polyfill" = [ "critical-section" ];
+          "critical-section" = [ "dep:critical-section" "dep:atomic-polyfill" ];
+          "default" = [ "std" ];
+          "parking_lot" = [ "dep:parking_lot_core" ];
+          "std" = [ "alloc" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "race" "std" "unstable" ];
+      };
+      "opaque-debug" = rec {
+        crateName = "opaque-debug";
+        version = "0.3.0";
+        edition = "2018";
+        sha256 = "1m8kzi4nd6shdqimn0mgb24f0hxslhnqd1whakyq06wcqd086jk2";
+        authors = [
+          "RustCrypto Developers"
+        ];
+
+      };
+      "openssl-probe" = rec {
+        crateName = "openssl-probe";
+        version = "0.1.5";
+        edition = "2015";
+        sha256 = "1kq18qm48rvkwgcggfkqq6pm948190czqc94d6bm2sir5hq1l0gz";
+        authors = [
+          "Alex Crichton <alex@alexcrichton.com>"
+        ];
+
+      };
+      "parking_lot 0.11.2" = rec {
+        crateName = "parking_lot";
+        version = "0.11.2";
+        edition = "2018";
+        sha256 = "16gzf41bxmm10x82bla8d6wfppy9ym3fxsmdjyvn61m66s0bf5vx";
+        authors = [
+          "Amanieu d'Antras <amanieu@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "instant";
+            packageId = "instant";
+          }
+          {
+            name = "lock_api";
+            packageId = "lock_api";
+          }
+          {
+            name = "parking_lot_core";
+            packageId = "parking_lot_core 0.8.6";
+          }
+        ];
+        features = {
+          "arc_lock" = [ "lock_api/arc_lock" ];
+          "deadlock_detection" = [ "parking_lot_core/deadlock_detection" ];
+          "nightly" = [ "parking_lot_core/nightly" "lock_api/nightly" ];
+          "owning_ref" = [ "lock_api/owning_ref" ];
+          "serde" = [ "lock_api/serde" ];
+          "stdweb" = [ "instant/stdweb" ];
+          "wasm-bindgen" = [ "instant/wasm-bindgen" ];
+        };
+        resolvedDefaultFeatures = [ "default" ];
+      };
+      "parking_lot 0.12.1" = rec {
+        crateName = "parking_lot";
+        version = "0.12.1";
+        edition = "2018";
+        sha256 = "13r2xk7mnxfc5g0g6dkdxqdqad99j7s7z8zhzz4npw5r0g0v4hip";
+        authors = [
+          "Amanieu d'Antras <amanieu@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "lock_api";
+            packageId = "lock_api";
+          }
+          {
+            name = "parking_lot_core";
+            packageId = "parking_lot_core 0.9.9";
+          }
+        ];
+        features = {
+          "arc_lock" = [ "lock_api/arc_lock" ];
+          "deadlock_detection" = [ "parking_lot_core/deadlock_detection" ];
+          "nightly" = [ "parking_lot_core/nightly" "lock_api/nightly" ];
+          "owning_ref" = [ "lock_api/owning_ref" ];
+          "serde" = [ "lock_api/serde" ];
+        };
+        resolvedDefaultFeatures = [ "default" ];
+      };
+      "parking_lot_core 0.8.6" = rec {
+        crateName = "parking_lot_core";
+        version = "0.8.6";
+        edition = "2018";
+        sha256 = "1p2nfcbr0b9lm9rglgm28k6mwyjwgm4knipsmqbgqaxdy3kcz8k0";
+        authors = [
+          "Amanieu d'Antras <amanieu@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "instant";
+            packageId = "instant";
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (target."unix" or false);
+          }
+          {
+            name = "redox_syscall";
+            packageId = "redox_syscall 0.2.16";
+            target = { target, features }: ("redox" == target."os" or null);
+          }
+          {
+            name = "smallvec";
+            packageId = "smallvec";
+          }
+          {
+            name = "winapi";
+            packageId = "winapi";
+            target = { target, features }: (target."windows" or false);
+            features = [ "winnt" "ntstatus" "minwindef" "winerror" "winbase" "errhandlingapi" "handleapi" ];
+          }
+        ];
+        features = {
+          "backtrace" = [ "dep:backtrace" ];
+          "deadlock_detection" = [ "petgraph" "thread-id" "backtrace" ];
+          "petgraph" = [ "dep:petgraph" ];
+          "thread-id" = [ "dep:thread-id" ];
+        };
+      };
+      "parking_lot_core 0.9.9" = rec {
+        crateName = "parking_lot_core";
+        version = "0.9.9";
+        edition = "2018";
+        sha256 = "13h0imw1aq86wj28gxkblhkzx6z1gk8q18n0v76qmmj6cliajhjc";
+        authors = [
+          "Amanieu d'Antras <amanieu@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (target."unix" or false);
+          }
+          {
+            name = "redox_syscall";
+            packageId = "redox_syscall 0.4.1";
+            target = { target, features }: ("redox" == target."os" or null);
+          }
+          {
+            name = "smallvec";
+            packageId = "smallvec";
+          }
+          {
+            name = "windows-targets";
+            packageId = "windows-targets 0.48.5";
+            target = { target, features }: (target."windows" or false);
+          }
+        ];
+        features = {
+          "backtrace" = [ "dep:backtrace" ];
+          "deadlock_detection" = [ "petgraph" "thread-id" "backtrace" ];
+          "petgraph" = [ "dep:petgraph" ];
+          "thread-id" = [ "dep:thread-id" ];
+        };
+      };
+      "parquet-format-safe" = rec {
+        crateName = "parquet-format-safe";
+        version = "0.2.4";
+        edition = "2021";
+        sha256 = "07wf6wf4jrxlq5p3xldxsnabp7jl06my2qp7kiwy9m3x2r5wac8i";
+        authors = [
+          "Apache Thrift contributors <dev@thrift.apache.org>"
+          "Jorge Leitao <jorgecarleitao@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "async-trait";
+            packageId = "async-trait";
+            optional = true;
+          }
+          {
+            name = "futures";
+            packageId = "futures";
+            optional = true;
+          }
+        ];
+        features = {
+          "async" = [ "futures" "async-trait" ];
+          "async-trait" = [ "dep:async-trait" ];
+          "full" = [ "async" ];
+          "futures" = [ "dep:futures" ];
+        };
+        resolvedDefaultFeatures = [ "async" "async-trait" "default" "futures" ];
+      };
+      "percent-encoding" = rec {
+        crateName = "percent-encoding";
+        version = "2.3.0";
+        edition = "2018";
+        sha256 = "152slflmparkh27hprw62sph8rv77wckzhwl2dhqk6bf563lfalv";
+        authors = [
+          "The rust-url developers"
+        ];
+        features = {
+          "default" = [ "std" ];
+          "std" = [ "alloc" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "std" ];
+      };
+      "petgraph" = rec {
+        crateName = "petgraph";
+        version = "0.6.4";
+        edition = "2018";
+        sha256 = "1ac6wfq5f5pzcv0nvzzfgjbwg2kwslpnzsw5wcmxlscfcb9azlz1";
+        authors = [
+          "bluss"
+          "mitchmindtree"
+        ];
+        dependencies = [
+          {
+            name = "fixedbitset";
+            packageId = "fixedbitset";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "indexmap";
+            packageId = "indexmap";
+          }
+        ];
+        features = {
+          "all" = [ "unstable" "quickcheck" "matrix_graph" "stable_graph" "graphmap" ];
+          "default" = [ "graphmap" "stable_graph" "matrix_graph" ];
+          "quickcheck" = [ "dep:quickcheck" ];
+          "serde" = [ "dep:serde" ];
+          "serde-1" = [ "serde" "serde_derive" ];
+          "serde_derive" = [ "dep:serde_derive" ];
+          "unstable" = [ "generate" ];
+        };
+      };
+      "pin-project-lite" = rec {
+        crateName = "pin-project-lite";
+        version = "0.2.13";
+        edition = "2018";
+        sha256 = "0n0bwr5qxlf0mhn2xkl36sy55118s9qmvx2yl5f3ixkb007lbywa";
+
+      };
+      "pin-utils" = rec {
+        crateName = "pin-utils";
+        version = "0.1.0";
+        edition = "2018";
+        sha256 = "117ir7vslsl2z1a7qzhws4pd01cg2d3338c47swjyvqv2n60v1wb";
+        authors = [
+          "Josef Brandl <mail@josefbrandl.de>"
+        ];
+
+      };
+      "pkcs8" = rec {
+        crateName = "pkcs8";
+        version = "0.10.2";
+        edition = "2021";
+        sha256 = "1dx7w21gvn07azszgqd3ryjhyphsrjrmq5mmz1fbxkj5g0vv4l7r";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        dependencies = [
+          {
+            name = "der";
+            packageId = "der";
+            features = [ "oid" ];
+          }
+          {
+            name = "spki";
+            packageId = "spki";
+          }
+        ];
+        features = {
+          "3des" = [ "encryption" "pkcs5/3des" ];
+          "alloc" = [ "der/alloc" "der/zeroize" "spki/alloc" ];
+          "des-insecure" = [ "encryption" "pkcs5/des-insecure" ];
+          "encryption" = [ "alloc" "pkcs5/alloc" "pkcs5/pbes2" "rand_core" ];
+          "getrandom" = [ "rand_core/getrandom" ];
+          "pem" = [ "alloc" "der/pem" "spki/pem" ];
+          "pkcs5" = [ "dep:pkcs5" ];
+          "rand_core" = [ "dep:rand_core" ];
+          "sha1-insecure" = [ "encryption" "pkcs5/sha1-insecure" ];
+          "std" = [ "alloc" "der/std" "spki/std" ];
+          "subtle" = [ "dep:subtle" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "std" ];
+      };
+      "pkg-config" = rec {
+        crateName = "pkg-config";
+        version = "0.3.27";
+        edition = "2015";
+        sha256 = "0r39ryh1magcq4cz5g9x88jllsnxnhcqr753islvyk4jp9h2h1r6";
+        authors = [
+          "Alex Crichton <alex@alexcrichton.com>"
+        ];
+
+      };
+      "planus" = rec {
+        crateName = "planus";
+        version = "0.3.1";
+        edition = "2021";
+        sha256 = "17x8mr175b9clg998xpi5z45f9fsspb0ncfnx2644bz817fr25pw";
+        dependencies = [
+          {
+            name = "array-init-cursor";
+            packageId = "array-init-cursor";
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "platforms" = rec {
+        crateName = "platforms";
+        version = "3.2.0";
+        edition = "2018";
+        sha256 = "1c6bzwn877aqdbbmyqsl753ycbciwvbdh4lpzijb8vrfb4zsprhl";
+        authors = [
+          "Tony Arcieri <bascule@gmail.com>"
+          "Sergey \"Shnatsel\" Davidoff <shnatsel@gmail.com>"
+        ];
+        features = {
+          "default" = [ "std" ];
+          "serde" = [ "dep:serde" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "polars" = rec {
+        crateName = "polars";
+        version = "0.35.4";
+        edition = "2021";
+        sha256 = "1mch7s185nfj74scc06bgfkwgp0n2axhp9wh17d25dvf4gwm53nz";
+        authors = [
+          "Ritchie Vink <ritchie46@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "getrandom";
+            packageId = "getrandom";
+            target = { target, features }: (builtins.elem "wasm" target."family");
+            features = [ "js" ];
+          }
+          {
+            name = "polars-core";
+            packageId = "polars-core";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-io";
+            packageId = "polars-io";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-lazy";
+            packageId = "polars-lazy";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-ops";
+            packageId = "polars-ops";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-sql";
+            packageId = "polars-sql";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "version_check";
+            packageId = "version_check";
+          }
+        ];
+        features = {
+          "abs" = [ "polars-ops/abs" "polars-lazy?/abs" ];
+          "algo" = [ "polars-algo" ];
+          "approx_unique" = [ "polars-lazy?/approx_unique" "polars-ops/approx_unique" ];
+          "arg_where" = [ "polars-lazy?/arg_where" ];
+          "asof_join" = [ "polars-core/asof_join" "polars-lazy?/asof_join" "polars-ops/asof_join" ];
+          "async" = [ "polars-lazy?/async" ];
+          "avro" = [ "polars-io" "polars-io/avro" ];
+          "avx512" = [ "polars-core/avx512" ];
+          "aws" = [ "async" "cloud" "polars-io/aws" ];
+          "azure" = [ "async" "cloud" "polars-io/azure" ];
+          "bench" = [ "lazy" ];
+          "bigidx" = [ "polars-core/bigidx" "polars-lazy?/bigidx" "polars-ops/big_idx" ];
+          "binary_encoding" = [ "polars-ops/binary_encoding" "polars-lazy?/binary_encoding" ];
+          "checked_arithmetic" = [ "polars-core/checked_arithmetic" ];
+          "chunked_ids" = [ "polars-lazy?/chunked_ids" "polars-core/chunked_ids" "polars-ops/chunked_ids" ];
+          "cloud" = [ "polars-lazy?/cloud" "polars-io/cloud" ];
+          "cloud_write" = [ "cloud" "polars-lazy?/cloud_write" ];
+          "coalesce" = [ "polars-lazy?/coalesce" ];
+          "concat_str" = [ "polars-lazy?/concat_str" ];
+          "cov" = [ "polars-lazy/cov" ];
+          "cross_join" = [ "polars-lazy?/cross_join" "polars-ops/cross_join" ];
+          "cse" = [ "polars-lazy?/cse" ];
+          "csv" = [ "polars-io" "polars-io/csv" "polars-lazy?/csv" "polars-sql?/csv" ];
+          "cum_agg" = [ "polars-ops/cum_agg" "polars-lazy?/cum_agg" ];
+          "cumulative_eval" = [ "polars-lazy?/cumulative_eval" ];
+          "cutqcut" = [ "polars-lazy?/cutqcut" ];
+          "dataframe_arithmetic" = [ "polars-core/dataframe_arithmetic" ];
+          "date_offset" = [ "polars-lazy?/date_offset" ];
+          "decompress" = [ "polars-io/decompress" ];
+          "decompress-fast" = [ "polars-io/decompress-fast" ];
+          "default" = [ "docs" "zip_with" "csv" "temporal" "fmt" "dtype-slim" ];
+          "describe" = [ "polars-core/describe" ];
+          "diagonal_concat" = [ "polars-core/diagonal_concat" "polars-lazy?/diagonal_concat" "polars-sql?/diagonal_concat" ];
+          "diff" = [ "polars-ops/diff" "polars-lazy?/diff" ];
+          "docs" = [ "polars-core/docs" ];
+          "docs-selection" = [ "csv" "json" "parquet" "ipc" "ipc_streaming" "dtype-full" "is_in" "rows" "docs" "strings" "object" "lazy" "temporal" "random" "zip_with" "round_series" "checked_arithmetic" "ndarray" "repeat_by" "is_first_distinct" "is_last_distinct" "asof_join" "cross_join" "concat_str" "string_to_integer" "decompress" "mode" "take_opt_iter" "cum_agg" "rolling_window" "interpolate" "diff" "rank" "range" "diagonal_concat" "horizontal_concat" "abs" "dot_diagram" "string_encoding" "product" "to_dummies" "describe" "list_eval" "cumulative_eval" "timezones" "arg_where" "propagate_nans" "coalesce" "dynamic_group_by" "extract_groups" ];
+          "dot_diagram" = [ "polars-lazy?/dot_diagram" ];
+          "dot_product" = [ "polars-core/dot_product" ];
+          "dtype-array" = [ "polars-core/dtype-array" "polars-lazy?/dtype-array" "polars-ops/dtype-array" ];
+          "dtype-categorical" = [ "polars-core/dtype-categorical" "polars-io/dtype-categorical" "polars-lazy?/dtype-categorical" "polars-ops/dtype-categorical" ];
+          "dtype-date" = [ "polars-core/dtype-date" "polars-lazy?/dtype-date" "polars-io/dtype-date" "polars-time?/dtype-date" "polars-core/dtype-date" "polars-ops/dtype-date" ];
+          "dtype-datetime" = [ "polars-core/dtype-datetime" "polars-lazy?/dtype-datetime" "polars-io/dtype-datetime" "polars-time?/dtype-datetime" "polars-ops/dtype-datetime" ];
+          "dtype-decimal" = [ "polars-core/dtype-decimal" "polars-lazy?/dtype-decimal" "polars-ops/dtype-decimal" "polars-io/dtype-decimal" ];
+          "dtype-duration" = [ "polars-core/dtype-duration" "polars-lazy?/dtype-duration" "polars-time?/dtype-duration" "polars-core/dtype-duration" "polars-ops/dtype-duration" ];
+          "dtype-full" = [ "dtype-date" "dtype-datetime" "dtype-duration" "dtype-time" "dtype-array" "dtype-i8" "dtype-i16" "dtype-decimal" "dtype-u8" "dtype-u16" "dtype-categorical" "dtype-struct" ];
+          "dtype-i16" = [ "polars-core/dtype-i16" "polars-lazy?/dtype-i16" "polars-ops/dtype-i16" ];
+          "dtype-i8" = [ "polars-core/dtype-i8" "polars-lazy?/dtype-i8" "polars-ops/dtype-i8" ];
+          "dtype-slim" = [ "dtype-date" "dtype-datetime" "dtype-duration" ];
+          "dtype-struct" = [ "polars-core/dtype-struct" "polars-lazy?/dtype-struct" "polars-ops/dtype-struct" "polars-io/dtype-struct" ];
+          "dtype-time" = [ "polars-core/dtype-time" "polars-io/dtype-time" "polars-time?/dtype-time" "polars-ops/dtype-time" ];
+          "dtype-u16" = [ "polars-core/dtype-u16" "polars-lazy?/dtype-u16" "polars-ops/dtype-u16" ];
+          "dtype-u8" = [ "polars-core/dtype-u8" "polars-lazy?/dtype-u8" "polars-ops/dtype-u8" ];
+          "dynamic_group_by" = [ "polars-core/dynamic_group_by" "polars-lazy?/dynamic_group_by" ];
+          "ewma" = [ "polars-ops/ewma" "polars-lazy?/ewma" ];
+          "extract_groups" = [ "polars-lazy?/extract_groups" ];
+          "extract_jsonpath" = [ "polars-core/strings" "polars-ops/extract_jsonpath" "polars-ops/strings" "polars-lazy?/extract_jsonpath" ];
+          "fmt" = [ "polars-core/fmt" ];
+          "fmt_no_tty" = [ "polars-core/fmt_no_tty" ];
+          "fused" = [ "polars-ops/fused" "polars-lazy?/fused" ];
+          "gcp" = [ "async" "cloud" "polars-io/gcp" ];
+          "group_by_list" = [ "polars-core/group_by_list" "polars-ops/group_by_list" ];
+          "horizontal_concat" = [ "polars-core/horizontal_concat" ];
+          "http" = [ "async" "cloud" "polars-io/http" ];
+          "interpolate" = [ "polars-ops/interpolate" "polars-lazy?/interpolate" ];
+          "ipc" = [ "polars-io" "polars-io/ipc" "polars-lazy?/ipc" "polars-sql?/ipc" ];
+          "ipc_streaming" = [ "polars-io" "polars-io/ipc_streaming" "polars-lazy?/ipc" ];
+          "is_first_distinct" = [ "polars-lazy?/is_first_distinct" "polars-ops/is_first_distinct" ];
+          "is_in" = [ "polars-lazy?/is_in" ];
+          "is_last_distinct" = [ "polars-lazy?/is_last_distinct" "polars-ops/is_last_distinct" ];
+          "is_unique" = [ "polars-lazy?/is_unique" "polars-ops/is_unique" ];
+          "json" = [ "polars-io" "polars-io/json" "polars-lazy?/json" "polars-sql?/json" "dtype-struct" ];
+          "lazy" = [ "polars-core/lazy" "polars-lazy" ];
+          "lazy_regex" = [ "polars-lazy?/regex" ];
+          "list_any_all" = [ "polars-lazy?/list_any_all" ];
+          "list_count" = [ "polars-ops/list_count" "polars-lazy?/list_count" ];
+          "list_drop_nulls" = [ "polars-lazy?/list_drop_nulls" ];
+          "list_eval" = [ "polars-lazy?/list_eval" ];
+          "list_gather" = [ "polars-ops/list_gather" "polars-lazy?/list_gather" ];
+          "list_sample" = [ "polars-lazy?/list_sample" ];
+          "list_sets" = [ "polars-lazy?/list_sets" ];
+          "list_to_struct" = [ "polars-ops/list_to_struct" "polars-lazy?/list_to_struct" ];
+          "log" = [ "polars-ops/log" "polars-lazy?/log" ];
+          "merge_sorted" = [ "polars-lazy?/merge_sorted" ];
+          "meta" = [ "polars-lazy?/meta" ];
+          "mode" = [ "polars-ops/mode" "polars-lazy?/mode" ];
+          "moment" = [ "polars-ops/moment" "polars-lazy?/moment" ];
+          "ndarray" = [ "polars-core/ndarray" ];
+          "nightly" = [ "polars-core/nightly" "polars-ops/nightly" "simd" "polars-lazy?/nightly" "polars-sql/nightly" ];
+          "object" = [ "polars-core/object" "polars-lazy?/object" "polars-io/object" ];
+          "parquet" = [ "polars-io" "polars-lazy?/parquet" "polars-io/parquet" "polars-sql?/parquet" ];
+          "partition_by" = [ "polars-core/partition_by" ];
+          "pct_change" = [ "polars-ops/pct_change" "polars-lazy?/pct_change" ];
+          "peaks" = [ "polars-lazy/peaks" ];
+          "performant" = [ "polars-core/performant" "chunked_ids" "dtype-u8" "dtype-u16" "dtype-struct" "cse" "polars-ops/performant" "streaming" "fused" ];
+          "pivot" = [ "polars-lazy?/pivot" ];
+          "polars-algo" = [ "dep:polars-algo" ];
+          "polars-io" = [ "dep:polars-io" ];
+          "polars-lazy" = [ "dep:polars-lazy" ];
+          "polars-sql" = [ "dep:polars-sql" ];
+          "polars-time" = [ "dep:polars-time" ];
+          "product" = [ "polars-core/product" ];
+          "propagate_nans" = [ "polars-lazy?/propagate_nans" ];
+          "random" = [ "polars-core/random" "polars-lazy?/random" "polars-ops/random" ];
+          "range" = [ "polars-lazy?/range" ];
+          "rank" = [ "polars-lazy?/rank" "polars-ops/rank" ];
+          "reinterpret" = [ "polars-core/reinterpret" ];
+          "repeat_by" = [ "polars-ops/repeat_by" "polars-lazy?/repeat_by" ];
+          "rle" = [ "polars-lazy?/rle" ];
+          "rolling_window" = [ "polars-core/rolling_window" "polars-lazy?/rolling_window" "polars-time/rolling_window" ];
+          "round_series" = [ "polars-ops/round_series" "polars-lazy?/round_series" ];
+          "row_hash" = [ "polars-core/row_hash" "polars-lazy?/row_hash" ];
+          "rows" = [ "polars-core/rows" ];
+          "search_sorted" = [ "polars-lazy?/search_sorted" ];
+          "semi_anti_join" = [ "polars-lazy?/semi_anti_join" "polars-ops/semi_anti_join" "polars-sql?/semi_anti_join" ];
+          "serde" = [ "polars-core/serde" ];
+          "serde-lazy" = [ "polars-core/serde-lazy" "polars-lazy?/serde" "polars-time?/serde" "polars-io/serde" "polars-ops/serde" ];
+          "sign" = [ "polars-lazy?/sign" ];
+          "simd" = [ "polars-core/simd" "polars-io/simd" "polars-ops/simd" ];
+          "sort_multiple" = [ "polars-core/sort_multiple" ];
+          "sql" = [ "polars-sql" ];
+          "streaming" = [ "polars-lazy?/streaming" ];
+          "string_encoding" = [ "polars-ops/string_encoding" "polars-lazy?/string_encoding" "polars-core/strings" ];
+          "string_pad" = [ "polars-lazy?/string_pad" "polars-ops/string_pad" ];
+          "string_to_integer" = [ "polars-lazy?/string_to_integer" "polars-ops/string_to_integer" ];
+          "strings" = [ "polars-core/strings" "polars-lazy?/strings" "polars-ops/strings" ];
+          "take_opt_iter" = [ "polars-core/take_opt_iter" ];
+          "temporal" = [ "polars-core/temporal" "polars-lazy?/temporal" "polars-io/temporal" "polars-time" ];
+          "test" = [ "lazy" "rolling_window" "rank" "round_series" "csv" "dtype-categorical" "cum_agg" "fmt" "diff" "abs" "parquet" "ipc" "ipc_streaming" "json" ];
+          "timezones" = [ "polars-core/timezones" "polars-lazy?/timezones" "polars-io/timezones" ];
+          "to_dummies" = [ "polars-ops/to_dummies" ];
+          "top_k" = [ "polars-lazy?/top_k" ];
+          "trigonometry" = [ "polars-lazy?/trigonometry" ];
+          "true_div" = [ "polars-lazy?/true_div" ];
+          "unique_counts" = [ "polars-ops/unique_counts" "polars-lazy?/unique_counts" ];
+          "zip_with" = [ "polars-core/zip_with" "polars-ops/zip_with" ];
+        };
+        resolvedDefaultFeatures = [ "dtype-struct" "lazy" "parquet" "polars-io" "polars-lazy" "polars-sql" "sql" ];
+      };
+      "polars-arrow" = rec {
+        crateName = "polars-arrow";
+        version = "0.35.4";
+        edition = "2021";
+        sha256 = "1rxa9dfsqy7mh4w9gy7y7kpig0wrzrjqi1axj43rnxyrlqq38l6x";
+        authors = [
+          "Jorge C. Leitao <jorgecarleitao@gmail.com>"
+          "Apache Arrow <dev@arrow.apache.org>"
+          "Ritchie Vink <ritchie46@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "ahash";
+            packageId = "ahash";
+          }
+          {
+            name = "arrow-format";
+            packageId = "arrow-format";
+            optional = true;
+            features = [ "ipc" ];
+          }
+          {
+            name = "atoi_simd";
+            packageId = "atoi_simd";
+            optional = true;
+          }
+          {
+            name = "bytemuck";
+            packageId = "bytemuck";
+            features = [ "derive" "extern_crate_alloc" ];
+          }
+          {
+            name = "chrono";
+            packageId = "chrono";
+            usesDefaultFeatures = false;
+            features = [ "std" ];
+          }
+          {
+            name = "dyn-clone";
+            packageId = "dyn-clone";
+          }
+          {
+            name = "either";
+            packageId = "either";
+          }
+          {
+            name = "ethnum";
+            packageId = "ethnum";
+          }
+          {
+            name = "fast-float";
+            packageId = "fast-float";
+            optional = true;
+          }
+          {
+            name = "foreign_vec";
+            packageId = "foreign_vec";
+          }
+          {
+            name = "futures";
+            packageId = "futures";
+            optional = true;
+          }
+          {
+            name = "getrandom";
+            packageId = "getrandom";
+            target = { target, features }: (pkgs.rust.lib.toRustTarget stdenv.hostPlatform == "wasm32-unknown-unknown");
+            features = [ "js" ];
+          }
+          {
+            name = "hashbrown";
+            packageId = "hashbrown";
+            features = [ "rayon" "ahash" ];
+          }
+          {
+            name = "itoa";
+            packageId = "itoa";
+            optional = true;
+          }
+          {
+            name = "lz4";
+            packageId = "lz4";
+            optional = true;
+          }
+          {
+            name = "multiversion";
+            packageId = "multiversion";
+            optional = true;
+          }
+          {
+            name = "num-traits";
+            packageId = "num-traits";
+          }
+          {
+            name = "polars-error";
+            packageId = "polars-error";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-utils";
+            packageId = "polars-utils";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "ryu";
+            packageId = "ryu";
+            optional = true;
+          }
+          {
+            name = "simdutf8";
+            packageId = "simdutf8";
+          }
+          {
+            name = "streaming-iterator";
+            packageId = "streaming-iterator";
+          }
+          {
+            name = "strength_reduce";
+            packageId = "strength_reduce";
+            optional = true;
+          }
+          {
+            name = "zstd";
+            packageId = "zstd";
+            optional = true;
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "rustc_version";
+            packageId = "rustc_version";
+          }
+        ];
+        features = {
+          "arrow-array" = [ "dep:arrow-array" ];
+          "arrow-buffer" = [ "dep:arrow-buffer" ];
+          "arrow-data" = [ "dep:arrow-data" ];
+          "arrow-format" = [ "dep:arrow-format" ];
+          "arrow-schema" = [ "dep:arrow-schema" ];
+          "arrow_rs" = [ "arrow-buffer" "arrow-schema" "arrow-data" "arrow-array" ];
+          "async-stream" = [ "dep:async-stream" ];
+          "atoi" = [ "dep:atoi" ];
+          "atoi_simd" = [ "dep:atoi_simd" ];
+          "avro-schema" = [ "dep:avro-schema" ];
+          "chrono-tz" = [ "dep:chrono-tz" ];
+          "compute" = [ "compute_aggregate" "compute_arithmetics" "compute_bitwise" "compute_boolean" "compute_boolean_kleene" "compute_cast" "compute_comparison" "compute_concatenate" "compute_filter" "compute_hash" "compute_if_then_else" "compute_take" "compute_temporal" ];
+          "compute_aggregate" = [ "multiversion" ];
+          "compute_arithmetics" = [ "strength_reduce" "compute_arithmetics_decimal" ];
+          "compute_arithmetics_decimal" = [ "strength_reduce" ];
+          "compute_cast" = [ "compute_take" "ryu" "atoi_simd" "itoa" "fast-float" ];
+          "compute_comparison" = [ "compute_take" "compute_boolean" ];
+          "compute_hash" = [ "multiversion" ];
+          "dtype-decimal" = [ "atoi" ];
+          "fast-float" = [ "dep:fast-float" ];
+          "full" = [ "arrow_rs" "io_ipc" "io_flight" "io_ipc_write_async" "io_ipc_read_async" "io_ipc_compression" "io_avro" "io_avro_compression" "io_avro_async" "regex-syntax" "compute" "chrono-tz" ];
+          "futures" = [ "dep:futures" ];
+          "hex" = [ "dep:hex" ];
+          "indexmap" = [ "dep:indexmap" ];
+          "io_avro" = [ "avro-schema" "polars-error/avro-schema" ];
+          "io_avro_async" = [ "avro-schema/async" ];
+          "io_avro_compression" = [ "avro-schema/compression" ];
+          "io_flight" = [ "io_ipc" "arrow-format/flight-data" ];
+          "io_ipc" = [ "arrow-format" "polars-error/arrow-format" ];
+          "io_ipc_compression" = [ "lz4" "zstd" ];
+          "io_ipc_read_async" = [ "io_ipc" "futures" "async-stream" ];
+          "io_ipc_write_async" = [ "io_ipc" "futures" ];
+          "itoa" = [ "dep:itoa" ];
+          "lz4" = [ "dep:lz4" ];
+          "multiversion" = [ "dep:multiversion" ];
+          "regex" = [ "dep:regex" ];
+          "regex-syntax" = [ "dep:regex-syntax" ];
+          "ryu" = [ "dep:ryu" ];
+          "serde" = [ "dep:serde" ];
+          "strength_reduce" = [ "dep:strength_reduce" ];
+          "zstd" = [ "dep:zstd" ];
+        };
+        resolvedDefaultFeatures = [ "arrow-format" "atoi_simd" "compute" "compute_aggregate" "compute_arithmetics" "compute_arithmetics_decimal" "compute_bitwise" "compute_boolean" "compute_boolean_kleene" "compute_cast" "compute_comparison" "compute_concatenate" "compute_filter" "compute_hash" "compute_if_then_else" "compute_take" "compute_temporal" "fast-float" "futures" "io_ipc" "io_ipc_compression" "io_ipc_write_async" "itoa" "lz4" "multiversion" "ryu" "strength_reduce" "strings" "temporal" "zstd" ];
+      };
+      "polars-core" = rec {
+        crateName = "polars-core";
+        version = "0.35.4";
+        edition = "2021";
+        sha256 = "1mnginlmgmlp167ij0r5lywvy50zns1cr8db1ikxxv2xwnwdawxf";
+        authors = [
+          "Ritchie Vink <ritchie46@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "ahash";
+            packageId = "ahash";
+          }
+          {
+            name = "bitflags";
+            packageId = "bitflags 2.4.1";
+          }
+          {
+            name = "bytemuck";
+            packageId = "bytemuck";
+            features = [ "derive" "extern_crate_alloc" ];
+          }
+          {
+            name = "chrono";
+            packageId = "chrono";
+            optional = true;
+            usesDefaultFeatures = false;
+            features = [ "std" ];
+          }
+          {
+            name = "either";
+            packageId = "either";
+          }
+          {
+            name = "hashbrown";
+            packageId = "hashbrown";
+            features = [ "rayon" "ahash" ];
+          }
+          {
+            name = "indexmap";
+            packageId = "indexmap";
+            features = [ "std" ];
+          }
+          {
+            name = "num-traits";
+            packageId = "num-traits";
+          }
+          {
+            name = "once_cell";
+            packageId = "once_cell";
+          }
+          {
+            name = "polars-arrow";
+            packageId = "polars-arrow";
+            rename = "arrow";
+            usesDefaultFeatures = false;
+            features = [ "compute_aggregate" "compute_arithmetics" "compute_boolean" "compute_boolean_kleene" "compute_cast" "compute_comparison" "compute_concatenate" "compute_filter" "compute_if_then_else" ];
+          }
+          {
+            name = "polars-error";
+            packageId = "polars-error";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-row";
+            packageId = "polars-row";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-utils";
+            packageId = "polars-utils";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "rand";
+            packageId = "rand";
+            optional = true;
+            features = [ "small_rng" "std" ];
+          }
+          {
+            name = "rand_distr";
+            packageId = "rand_distr";
+            optional = true;
+          }
+          {
+            name = "rayon";
+            packageId = "rayon";
+          }
+          {
+            name = "regex";
+            packageId = "regex";
+            optional = true;
+          }
+          {
+            name = "smartstring";
+            packageId = "smartstring";
+          }
+          {
+            name = "thiserror";
+            packageId = "thiserror";
+          }
+          {
+            name = "xxhash-rust";
+            packageId = "xxhash-rust";
+            features = [ "xxh3" ];
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "version_check";
+            packageId = "version_check";
+          }
+        ];
+        features = {
+          "arrow-array" = [ "dep:arrow-array" ];
+          "arrow_rs" = [ "arrow-array" "arrow/arrow_rs" ];
+          "bigidx" = [ "arrow/bigidx" "polars-utils/bigidx" ];
+          "chrono" = [ "dep:chrono" ];
+          "chrono-tz" = [ "dep:chrono-tz" ];
+          "comfy-table" = [ "dep:comfy-table" ];
+          "default" = [ "algorithm_group_by" ];
+          "docs-selection" = [ "ndarray" "rows" "docs" "strings" "object" "lazy" "temporal" "random" "zip_with" "checked_arithmetic" "is_first_distinct" "is_last_distinct" "asof_join" "dot_product" "row_hash" "rolling_window" "dtype-categorical" "dtype-decimal" "diagonal_concat" "horizontal_concat" "dataframe_arithmetic" "product" "describe" "chunked_ids" "partition_by" "algorithm_group_by" ];
+          "dtype-array" = [ "arrow/dtype-array" ];
+          "dtype-date" = [ "temporal" ];
+          "dtype-datetime" = [ "temporal" ];
+          "dtype-decimal" = [ "dep:itoap" "arrow/dtype-decimal" ];
+          "dtype-duration" = [ "temporal" ];
+          "dtype-time" = [ "temporal" ];
+          "dynamic_group_by" = [ "dtype-datetime" "dtype-date" ];
+          "fmt" = [ "comfy-table/tty" ];
+          "fmt_no_tty" = [ "comfy-table" ];
+          "ndarray" = [ "dep:ndarray" ];
+          "nightly" = [ "simd" "hashbrown/nightly" "polars-utils/nightly" "arrow/nightly" ];
+          "object" = [ "serde_json" ];
+          "performant" = [ "arrow/performant" "reinterpret" ];
+          "rand" = [ "dep:rand" ];
+          "rand_distr" = [ "dep:rand_distr" ];
+          "random" = [ "rand" "rand_distr" ];
+          "regex" = [ "dep:regex" ];
+          "serde" = [ "dep:serde" "smartstring/serde" "bitflags/serde" ];
+          "serde-lazy" = [ "serde" "arrow/serde" "indexmap/serde" "smartstring/serde" "chrono/serde" ];
+          "serde_json" = [ "dep:serde_json" ];
+          "simd" = [ "arrow/simd" ];
+          "strings" = [ "regex" "arrow/strings" "polars-error/regex" ];
+          "temporal" = [ "regex" "chrono" "polars-error/regex" ];
+          "timezones" = [ "chrono-tz" "arrow/chrono-tz" "arrow/timezones" ];
+        };
+        resolvedDefaultFeatures = [ "algorithm_group_by" "chrono" "chunked_ids" "dtype-date" "dtype-datetime" "dtype-duration" "dtype-struct" "dtype-time" "lazy" "rand" "rand_distr" "random" "regex" "reinterpret" "rows" "strings" "temporal" "zip_with" ];
+      };
+      "polars-error" = rec {
+        crateName = "polars-error";
+        version = "0.35.4";
+        edition = "2021";
+        sha256 = "127l5hazh5hn73j767cfyjwgbvvbab8hj53l1jp976daivb201gb";
+        authors = [
+          "Ritchie Vink <ritchie46@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "arrow-format";
+            packageId = "arrow-format";
+            optional = true;
+          }
+          {
+            name = "regex";
+            packageId = "regex";
+            optional = true;
+          }
+          {
+            name = "simdutf8";
+            packageId = "simdutf8";
+          }
+          {
+            name = "thiserror";
+            packageId = "thiserror";
+          }
+        ];
+        features = {
+          "arrow-format" = [ "dep:arrow-format" ];
+          "avro-schema" = [ "dep:avro-schema" ];
+          "object_store" = [ "dep:object_store" ];
+          "regex" = [ "dep:regex" ];
+        };
+        resolvedDefaultFeatures = [ "arrow-format" "regex" ];
+      };
+      "polars-io" = rec {
+        crateName = "polars-io";
+        version = "0.35.4";
+        edition = "2021";
+        sha256 = "01mwcdikw7y92xjhlsmj4wf0p9r3kvmhrvsbnsfh1mmc8l3hmqcn";
+        authors = [
+          "Ritchie Vink <ritchie46@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "ahash";
+            packageId = "ahash";
+          }
+          {
+            name = "async-trait";
+            packageId = "async-trait";
+            optional = true;
+          }
+          {
+            name = "bytes";
+            packageId = "bytes";
+          }
+          {
+            name = "futures";
+            packageId = "futures";
+            optional = true;
+          }
+          {
+            name = "home";
+            packageId = "home";
+            target = { target, features }: (!(builtins.elem "wasm" target."family"));
+          }
+          {
+            name = "memchr";
+            packageId = "memchr";
+          }
+          {
+            name = "memmap2";
+            packageId = "memmap2";
+            rename = "memmap";
+          }
+          {
+            name = "num-traits";
+            packageId = "num-traits";
+          }
+          {
+            name = "once_cell";
+            packageId = "once_cell";
+          }
+          {
+            name = "percent-encoding";
+            packageId = "percent-encoding";
+          }
+          {
+            name = "polars-arrow";
+            packageId = "polars-arrow";
+            rename = "arrow";
+            usesDefaultFeatures = false;
+            features = [ "compute_aggregate" "compute_arithmetics" "compute_boolean" "compute_boolean_kleene" "compute_cast" "compute_comparison" "compute_concatenate" "compute_filter" "compute_if_then_else" ];
+          }
+          {
+            name = "polars-core";
+            packageId = "polars-core";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-error";
+            packageId = "polars-error";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-parquet";
+            packageId = "polars-parquet";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-utils";
+            packageId = "polars-utils";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "rayon";
+            packageId = "rayon";
+          }
+          {
+            name = "regex";
+            packageId = "regex";
+          }
+          {
+            name = "smartstring";
+            packageId = "smartstring";
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+            optional = true;
+            features = [ "net" "rt-multi-thread" "time" "sync" ];
+          }
+          {
+            name = "tokio-util";
+            packageId = "tokio-util";
+            optional = true;
+            features = [ "io" "io-util" ];
+          }
+        ];
+        features = {
+          "async" = [ "async-trait" "futures" "tokio" "tokio-util" "arrow/io_ipc_write_async" "polars-error/regex" "polars-parquet?/async" ];
+          "async-trait" = [ "dep:async-trait" ];
+          "atoi_simd" = [ "dep:atoi_simd" ];
+          "avro" = [ "arrow/io_avro" "arrow/io_avro_compression" ];
+          "aws" = [ "object_store/aws" "cloud" "reqwest" ];
+          "azure" = [ "object_store/azure" "cloud" ];
+          "chrono" = [ "dep:chrono" ];
+          "chrono-tz" = [ "dep:chrono-tz" ];
+          "cloud" = [ "object_store" "async" "polars-error/object_store" "url" ];
+          "csv" = [ "atoi_simd" "polars-core/rows" "itoa" "ryu" "fast-float" "simdutf8" ];
+          "decompress" = [ "flate2/rust_backend" "zstd" ];
+          "decompress-fast" = [ "flate2/zlib-ng" "zstd" ];
+          "default" = [ "decompress" ];
+          "dtype-categorical" = [ "polars-core/dtype-categorical" ];
+          "dtype-date" = [ "polars-core/dtype-date" "polars-time/dtype-date" ];
+          "dtype-datetime" = [ "polars-core/dtype-datetime" "polars-core/temporal" "polars-time/dtype-datetime" "chrono" ];
+          "dtype-decimal" = [ "polars-core/dtype-decimal" ];
+          "dtype-struct" = [ "polars-core/dtype-struct" ];
+          "dtype-time" = [ "polars-core/dtype-time" "polars-core/temporal" "polars-time/dtype-time" ];
+          "fast-float" = [ "dep:fast-float" ];
+          "flate2" = [ "dep:flate2" ];
+          "fmt" = [ "polars-core/fmt" ];
+          "futures" = [ "dep:futures" ];
+          "gcp" = [ "object_store/gcp" "cloud" ];
+          "http" = [ "object_store/http" "cloud" ];
+          "ipc" = [ "arrow/io_ipc" "arrow/io_ipc_compression" ];
+          "ipc_streaming" = [ "arrow/io_ipc" "arrow/io_ipc_compression" ];
+          "itoa" = [ "dep:itoa" ];
+          "json" = [ "polars-json" "simd-json" "atoi_simd" "serde_json" "dtype-struct" "csv" ];
+          "object_store" = [ "dep:object_store" ];
+          "parquet" = [ "polars-parquet" "polars-parquet/compression" ];
+          "partition" = [ "polars-core/partition_by" ];
+          "polars-json" = [ "dep:polars-json" ];
+          "polars-parquet" = [ "dep:polars-parquet" ];
+          "polars-time" = [ "dep:polars-time" ];
+          "python" = [ "polars-error/python" ];
+          "reqwest" = [ "dep:reqwest" ];
+          "ryu" = [ "dep:ryu" ];
+          "serde" = [ "dep:serde" "polars-core/serde-lazy" ];
+          "serde_json" = [ "dep:serde_json" ];
+          "simd-json" = [ "dep:simd-json" ];
+          "simdutf8" = [ "dep:simdutf8" ];
+          "temporal" = [ "dtype-datetime" "dtype-date" "dtype-time" ];
+          "timezones" = [ "chrono-tz" "dtype-datetime" ];
+          "tokio" = [ "dep:tokio" ];
+          "tokio-util" = [ "dep:tokio-util" ];
+          "url" = [ "dep:url" ];
+          "zstd" = [ "dep:zstd" ];
+        };
+        resolvedDefaultFeatures = [ "async" "async-trait" "dtype-struct" "futures" "ipc" "lazy" "parquet" "polars-parquet" "tokio" "tokio-util" ];
+      };
+      "polars-lazy" = rec {
+        crateName = "polars-lazy";
+        version = "0.35.4";
+        edition = "2021";
+        sha256 = "084gp9qa1w9b7dglcmrvlx6xrcl7hw5nmlb26w6xvrjvf1czfm9m";
+        authors = [
+          "Ritchie Vink <ritchie46@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "ahash";
+            packageId = "ahash";
+          }
+          {
+            name = "bitflags";
+            packageId = "bitflags 2.4.1";
+          }
+          {
+            name = "glob";
+            packageId = "glob";
+          }
+          {
+            name = "once_cell";
+            packageId = "once_cell";
+          }
+          {
+            name = "polars-arrow";
+            packageId = "polars-arrow";
+            rename = "arrow";
+            usesDefaultFeatures = false;
+            features = [ "compute_aggregate" "compute_arithmetics" "compute_boolean" "compute_boolean_kleene" "compute_cast" "compute_comparison" "compute_concatenate" "compute_filter" "compute_if_then_else" ];
+          }
+          {
+            name = "polars-core";
+            packageId = "polars-core";
+            usesDefaultFeatures = false;
+            features = [ "lazy" "zip_with" "random" ];
+          }
+          {
+            name = "polars-io";
+            packageId = "polars-io";
+            usesDefaultFeatures = false;
+            features = [ "lazy" ];
+          }
+          {
+            name = "polars-ops";
+            packageId = "polars-ops";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-pipe";
+            packageId = "polars-pipe";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-plan";
+            packageId = "polars-plan";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-time";
+            packageId = "polars-time";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-utils";
+            packageId = "polars-utils";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "rayon";
+            packageId = "rayon";
+          }
+          {
+            name = "smartstring";
+            packageId = "smartstring";
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "version_check";
+            packageId = "version_check";
+          }
+        ];
+        features = {
+          "abs" = [ "polars-plan/abs" ];
+          "approx_unique" = [ "polars-plan/approx_unique" ];
+          "arg_where" = [ "polars-plan/arg_where" ];
+          "asof_join" = [ "polars-plan/asof_join" "polars-time" "polars-ops/asof_join" ];
+          "async" = [ "polars-plan/async" "polars-io/cloud" "polars-pipe?/async" ];
+          "bigidx" = [ "polars-plan/bigidx" ];
+          "binary_encoding" = [ "polars-plan/binary_encoding" ];
+          "chunked_ids" = [ "polars-plan/chunked_ids" "polars-core/chunked_ids" "polars-ops/chunked_ids" ];
+          "cloud" = [ "async" "polars-pipe?/cloud" "polars-plan/cloud" "tokio" "futures" ];
+          "cloud_write" = [ "cloud" ];
+          "coalesce" = [ "polars-plan/coalesce" ];
+          "concat_str" = [ "polars-plan/concat_str" ];
+          "cov" = [ "polars-ops/cov" "polars-plan/cov" ];
+          "cross_join" = [ "polars-plan/cross_join" "polars-pipe?/cross_join" "polars-ops/cross_join" ];
+          "cse" = [ "polars-plan/cse" ];
+          "csv" = [ "polars-io/csv" "polars-plan/csv" "polars-pipe?/csv" ];
+          "cum_agg" = [ "polars-plan/cum_agg" ];
+          "cutqcut" = [ "polars-plan/cutqcut" "polars-ops/cutqcut" ];
+          "date_offset" = [ "polars-plan/date_offset" ];
+          "diff" = [ "polars-plan/diff" "polars-plan/diff" ];
+          "dot_diagram" = [ "polars-plan/dot_diagram" ];
+          "dtype-array" = [ "polars-plan/dtype-array" "polars-pipe?/dtype-array" "polars-ops/dtype-array" ];
+          "dtype-categorical" = [ "polars-plan/dtype-categorical" "polars-pipe?/dtype-categorical" ];
+          "dtype-date" = [ "polars-plan/dtype-date" "polars-time/dtype-date" "temporal" ];
+          "dtype-datetime" = [ "polars-plan/dtype-datetime" "polars-time/dtype-datetime" "temporal" ];
+          "dtype-decimal" = [ "polars-plan/dtype-decimal" "polars-pipe?/dtype-decimal" ];
+          "dtype-duration" = [ "polars-plan/dtype-duration" "polars-time/dtype-duration" "temporal" ];
+          "dtype-i16" = [ "polars-plan/dtype-i16" "polars-pipe?/dtype-i16" ];
+          "dtype-i8" = [ "polars-plan/dtype-i8" "polars-pipe?/dtype-i8" ];
+          "dtype-struct" = [ "polars-plan/dtype-struct" ];
+          "dtype-time" = [ "polars-core/dtype-time" "temporal" ];
+          "dtype-u16" = [ "polars-plan/dtype-u16" "polars-pipe?/dtype-u16" ];
+          "dtype-u8" = [ "polars-plan/dtype-u8" "polars-pipe?/dtype-u8" ];
+          "dynamic_group_by" = [ "polars-plan/dynamic_group_by" "polars-time" "temporal" ];
+          "ewma" = [ "polars-plan/ewma" ];
+          "extract_groups" = [ "polars-plan/extract_groups" ];
+          "extract_jsonpath" = [ "polars-plan/extract_jsonpath" "polars-ops/extract_jsonpath" ];
+          "fmt" = [ "polars-core/fmt" "polars-plan/fmt" ];
+          "fused" = [ "polars-plan/fused" "polars-ops/fused" ];
+          "futures" = [ "dep:futures" ];
+          "interpolate" = [ "polars-plan/interpolate" ];
+          "ipc" = [ "polars-io/ipc" "polars-plan/ipc" "polars-pipe?/ipc" ];
+          "is_first_distinct" = [ "polars-plan/is_first_distinct" ];
+          "is_in" = [ "polars-plan/is_in" "polars-ops/is_in" ];
+          "is_last_distinct" = [ "polars-plan/is_last_distinct" ];
+          "is_unique" = [ "polars-plan/is_unique" ];
+          "json" = [ "polars-io/json" "polars-plan/json" "polars-json" ];
+          "list_any_all" = [ "polars-ops/list_any_all" "polars-plan/list_any_all" ];
+          "list_count" = [ "polars-ops/list_count" "polars-plan/list_count" ];
+          "list_drop_nulls" = [ "polars-ops/list_drop_nulls" "polars-plan/list_drop_nulls" ];
+          "list_gather" = [ "polars-ops/list_gather" "polars-plan/list_gather" ];
+          "list_sample" = [ "polars-ops/list_sample" "polars-plan/list_sample" ];
+          "list_sets" = [ "polars-plan/list_sets" "polars-ops/list_sets" ];
+          "list_to_struct" = [ "polars-plan/list_to_struct" ];
+          "log" = [ "polars-plan/log" ];
+          "merge_sorted" = [ "polars-plan/merge_sorted" ];
+          "meta" = [ "polars-plan/meta" ];
+          "mode" = [ "polars-plan/mode" ];
+          "moment" = [ "polars-plan/moment" "polars-ops/moment" ];
+          "nightly" = [ "polars-core/nightly" "polars-pipe?/nightly" "polars-plan/nightly" ];
+          "object" = [ "polars-plan/object" ];
+          "panic_on_schema" = [ "polars-plan/panic_on_schema" ];
+          "parquet" = [ "polars-io/parquet" "polars-plan/parquet" "polars-pipe?/parquet" ];
+          "pct_change" = [ "polars-plan/pct_change" ];
+          "peaks" = [ "polars-plan/peaks" ];
+          "pivot" = [ "polars-core/rows" "polars-ops/pivot" ];
+          "polars-json" = [ "dep:polars-json" ];
+          "polars-pipe" = [ "dep:polars-pipe" ];
+          "polars-time" = [ "dep:polars-time" ];
+          "propagate_nans" = [ "polars-plan/propagate_nans" ];
+          "pyo3" = [ "dep:pyo3" ];
+          "python" = [ "pyo3" "polars-plan/python" "polars-core/python" "polars-io/python" ];
+          "random" = [ "polars-plan/random" ];
+          "range" = [ "polars-plan/range" ];
+          "rank" = [ "polars-plan/rank" ];
+          "regex" = [ "polars-plan/regex" ];
+          "repeat_by" = [ "polars-plan/repeat_by" ];
+          "rle" = [ "polars-plan/rle" "polars-ops/rle" ];
+          "rolling_window" = [ "polars-plan/rolling_window" "polars-time/rolling_window" ];
+          "round_series" = [ "polars-plan/round_series" "polars-ops/round_series" ];
+          "row_hash" = [ "polars-plan/row_hash" ];
+          "search_sorted" = [ "polars-plan/search_sorted" ];
+          "semi_anti_join" = [ "polars-plan/semi_anti_join" ];
+          "serde" = [ "polars-plan/serde" "arrow/serde" "polars-core/serde-lazy" "polars-time?/serde" "polars-io/serde" "polars-ops/serde" ];
+          "sign" = [ "polars-plan/sign" ];
+          "streaming" = [ "chunked_ids" "polars-pipe" "polars-plan/streaming" "polars-ops/chunked_ids" ];
+          "string_encoding" = [ "polars-plan/string_encoding" ];
+          "string_pad" = [ "polars-plan/string_pad" ];
+          "string_to_integer" = [ "polars-plan/string_to_integer" ];
+          "strings" = [ "polars-plan/strings" ];
+          "temporal" = [ "dtype-datetime" "dtype-date" "dtype-time" "dtype-duration" "polars-plan/temporal" ];
+          "test" = [ "polars-plan/debugging" "panic_on_schema" "rolling_window" "rank" "round_series" "csv" "dtype-categorical" "cum_agg" "regex" "polars-core/fmt" "diff" "abs" "parquet" "ipc" "dtype-date" ];
+          "test_all" = [ "test" "strings" "regex" "ipc" "row_hash" "string_pad" "string_to_integer" "search_sorted" "top_k" "pivot" "semi_anti_join" "cse" ];
+          "timezones" = [ "polars-plan/timezones" ];
+          "tokio" = [ "dep:tokio" ];
+          "top_k" = [ "polars-plan/top_k" ];
+          "trigonometry" = [ "polars-plan/trigonometry" ];
+          "true_div" = [ "polars-plan/true_div" ];
+          "unique_counts" = [ "polars-plan/unique_counts" ];
+        };
+        resolvedDefaultFeatures = [ "abs" "cross_join" "cum_agg" "dtype-date" "dtype-datetime" "dtype-duration" "dtype-struct" "dtype-time" "is_in" "log" "meta" "parquet" "polars-time" "regex" "round_series" "strings" "temporal" "trigonometry" ];
+      };
+      "polars-ops" = rec {
+        crateName = "polars-ops";
+        version = "0.35.4";
+        edition = "2021";
+        sha256 = "0dpnxcgc57k8xvp4jmjhz8jwz8rclf62h22zjiwpzaka54cb4zhs";
+        authors = [
+          "Ritchie Vink <ritchie46@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "ahash";
+            packageId = "ahash";
+          }
+          {
+            name = "argminmax";
+            packageId = "argminmax";
+            usesDefaultFeatures = false;
+            features = [ "float" ];
+          }
+          {
+            name = "bytemuck";
+            packageId = "bytemuck";
+            features = [ "derive" "extern_crate_alloc" ];
+          }
+          {
+            name = "either";
+            packageId = "either";
+          }
+          {
+            name = "hashbrown";
+            packageId = "hashbrown";
+            features = [ "rayon" "ahash" ];
+          }
+          {
+            name = "indexmap";
+            packageId = "indexmap";
+            features = [ "std" ];
+          }
+          {
+            name = "memchr";
+            packageId = "memchr";
+          }
+          {
+            name = "num-traits";
+            packageId = "num-traits";
+          }
+          {
+            name = "polars-arrow";
+            packageId = "polars-arrow";
+            rename = "arrow";
+            usesDefaultFeatures = false;
+            features = [ "compute_aggregate" "compute_arithmetics" "compute_boolean" "compute_boolean_kleene" "compute_cast" "compute_comparison" "compute_concatenate" "compute_filter" "compute_if_then_else" ];
+          }
+          {
+            name = "polars-core";
+            packageId = "polars-core";
+            usesDefaultFeatures = false;
+            features = [ "algorithm_group_by" ];
+          }
+          {
+            name = "polars-error";
+            packageId = "polars-error";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-utils";
+            packageId = "polars-utils";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "rayon";
+            packageId = "rayon";
+          }
+          {
+            name = "regex";
+            packageId = "regex";
+          }
+          {
+            name = "smartstring";
+            packageId = "smartstring";
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "version_check";
+            packageId = "version_check";
+          }
+        ];
+        features = {
+          "asof_join" = [ "polars-core/asof_join" ];
+          "base64" = [ "dep:base64" ];
+          "big_idx" = [ "polars-core/bigidx" ];
+          "binary_encoding" = [ "base64" "hex" ];
+          "chrono" = [ "dep:chrono" ];
+          "chrono-tz" = [ "dep:chrono-tz" ];
+          "chunked_ids" = [ "polars-core/chunked_ids" ];
+          "cutqcut" = [ "dtype-categorical" "dtype-struct" ];
+          "dtype-array" = [ "polars-core/dtype-array" ];
+          "dtype-categorical" = [ "polars-core/dtype-categorical" ];
+          "dtype-date" = [ "polars-core/dtype-date" "polars-core/temporal" ];
+          "dtype-datetime" = [ "polars-core/dtype-datetime" "polars-core/temporal" ];
+          "dtype-decimal" = [ "polars-core/dtype-decimal" ];
+          "dtype-duration" = [ "polars-core/dtype-duration" "polars-core/temporal" ];
+          "dtype-i16" = [ "polars-core/dtype-i16" ];
+          "dtype-i8" = [ "polars-core/dtype-i8" ];
+          "dtype-struct" = [ "polars-core/dtype-struct" "polars-core/temporal" ];
+          "dtype-time" = [ "polars-core/dtype-time" "polars-core/temporal" ];
+          "dtype-u16" = [ "polars-core/dtype-u16" ];
+          "dtype-u8" = [ "polars-core/dtype-u8" ];
+          "extract_groups" = [ "dtype-struct" "polars-core/regex" ];
+          "extract_jsonpath" = [ "serde_json" "jsonpath_lib" "polars-json" ];
+          "group_by_list" = [ "polars-core/group_by_list" ];
+          "hex" = [ "dep:hex" ];
+          "is_in" = [ "polars-core/reinterpret" ];
+          "jsonpath_lib" = [ "dep:jsonpath_lib" ];
+          "list_to_struct" = [ "polars-core/dtype-struct" ];
+          "nightly" = [ "polars-utils/nightly" ];
+          "object" = [ "polars-core/object" ];
+          "pct_change" = [ "diff" ];
+          "performant" = [ "polars-core/performant" "fused" ];
+          "pivot" = [ "polars-core/reinterpret" ];
+          "polars-json" = [ "dep:polars-json" ];
+          "rand" = [ "dep:rand" ];
+          "rand_distr" = [ "dep:rand_distr" ];
+          "random" = [ "rand" "rand_distr" ];
+          "rank" = [ "rand" ];
+          "rle" = [ "dtype-struct" ];
+          "rolling_window" = [ "polars-core/rolling_window" ];
+          "serde" = [ "dep:serde" ];
+          "serde_json" = [ "dep:serde_json" ];
+          "simd" = [ "argminmax/nightly_simd" ];
+          "string_encoding" = [ "base64" "hex" ];
+          "string_pad" = [ "polars-core/strings" ];
+          "string_to_integer" = [ "polars-core/strings" ];
+          "strings" = [ "polars-core/strings" ];
+          "timezones" = [ "chrono-tz" "chrono" ];
+          "zip_with" = [ "polars-core/zip_with" ];
+        };
+        resolvedDefaultFeatures = [ "abs" "cross_join" "cum_agg" "dtype-struct" "is_in" "log" "round_series" "search_sorted" "strings" "zip_with" ];
+      };
+      "polars-parquet" = rec {
+        crateName = "polars-parquet";
+        version = "0.35.4";
+        edition = "2021";
+        sha256 = "1pb79wb1f31pk48lfm2w72hdfxqz6vv65iyxb072skfxnzj10q0l";
+        authors = [
+          "Jorge C. Leitao <jorgecarleitao@gmail.com>"
+          "Apache Arrow <dev@arrow.apache.org>"
+          "Ritchie Vink <ritchie46@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "ahash";
+            packageId = "ahash";
+          }
+          {
+            name = "async-stream";
+            packageId = "async-stream";
+            optional = true;
+          }
+          {
+            name = "base64";
+            packageId = "base64 0.21.5";
+          }
+          {
+            name = "brotli";
+            packageId = "brotli";
+            optional = true;
+          }
+          {
+            name = "ethnum";
+            packageId = "ethnum";
+          }
+          {
+            name = "flate2";
+            packageId = "flate2";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures";
+            packageId = "futures";
+            optional = true;
+          }
+          {
+            name = "lz4";
+            packageId = "lz4";
+            optional = true;
+          }
+          {
+            name = "num-traits";
+            packageId = "num-traits";
+          }
+          {
+            name = "parquet-format-safe";
+            packageId = "parquet-format-safe";
+          }
+          {
+            name = "polars-arrow";
+            packageId = "polars-arrow";
+            rename = "arrow";
+            usesDefaultFeatures = false;
+            features = [ "compute_aggregate" "compute_arithmetics" "compute_boolean" "compute_boolean_kleene" "compute_cast" "compute_comparison" "compute_concatenate" "compute_filter" "compute_if_then_else" "io_ipc" ];
+          }
+          {
+            name = "polars-error";
+            packageId = "polars-error";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-utils";
+            packageId = "polars-utils";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "seq-macro";
+            packageId = "seq-macro";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "simdutf8";
+            packageId = "simdutf8";
+          }
+          {
+            name = "snap";
+            packageId = "snap";
+            optional = true;
+          }
+          {
+            name = "streaming-decompression";
+            packageId = "streaming-decompression";
+          }
+          {
+            name = "zstd";
+            packageId = "zstd";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "async" = [ "async-stream" "futures" "parquet-format-safe/async" ];
+          "async-stream" = [ "dep:async-stream" ];
+          "bloom_filter" = [ "xxhash-rust" ];
+          "brotli" = [ "dep:brotli" ];
+          "compression" = [ "zstd" "gzip" "snappy" "lz4" "brotli" ];
+          "fallible-streaming-iterator" = [ "dep:fallible-streaming-iterator" ];
+          "flate2" = [ "dep:flate2" ];
+          "futures" = [ "dep:futures" ];
+          "gzip" = [ "flate2/rust_backend" ];
+          "gzip_zlib_ng" = [ "flate2/zlib-ng" ];
+          "lz4" = [ "dep:lz4" ];
+          "serde" = [ "dep:serde" ];
+          "serde_types" = [ "serde" ];
+          "snap" = [ "dep:snap" ];
+          "snappy" = [ "snap" ];
+          "xxhash-rust" = [ "dep:xxhash-rust" ];
+          "zstd" = [ "dep:zstd" ];
+        };
+        resolvedDefaultFeatures = [ "async" "async-stream" "brotli" "compression" "flate2" "futures" "gzip" "lz4" "snap" "snappy" "zstd" ];
+      };
+      "polars-pipe" = rec {
+        crateName = "polars-pipe";
+        version = "0.35.4";
+        edition = "2021";
+        sha256 = "1v5xniw8yxx9cnksc4bf169b3p7gcl6dzryzgfd2m4scyrylw2b6";
+        authors = [
+          "Ritchie Vink <ritchie46@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "crossbeam-channel";
+            packageId = "crossbeam-channel";
+          }
+          {
+            name = "crossbeam-queue";
+            packageId = "crossbeam-queue";
+          }
+          {
+            name = "enum_dispatch";
+            packageId = "enum_dispatch";
+          }
+          {
+            name = "hashbrown";
+            packageId = "hashbrown";
+            features = [ "rayon" "ahash" ];
+          }
+          {
+            name = "num-traits";
+            packageId = "num-traits";
+          }
+          {
+            name = "polars-arrow";
+            packageId = "polars-arrow";
+            rename = "arrow";
+            usesDefaultFeatures = false;
+            features = [ "compute_aggregate" "compute_arithmetics" "compute_boolean" "compute_boolean_kleene" "compute_cast" "compute_comparison" "compute_concatenate" "compute_filter" "compute_if_then_else" ];
+          }
+          {
+            name = "polars-core";
+            packageId = "polars-core";
+            usesDefaultFeatures = false;
+            features = [ "lazy" "zip_with" "random" "rows" "chunked_ids" ];
+          }
+          {
+            name = "polars-io";
+            packageId = "polars-io";
+            usesDefaultFeatures = false;
+            features = [ "ipc" ];
+          }
+          {
+            name = "polars-ops";
+            packageId = "polars-ops";
+            usesDefaultFeatures = false;
+            features = [ "search_sorted" ];
+          }
+          {
+            name = "polars-plan";
+            packageId = "polars-plan";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-row";
+            packageId = "polars-row";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-utils";
+            packageId = "polars-utils";
+            usesDefaultFeatures = false;
+            features = [ "sysinfo" ];
+          }
+          {
+            name = "rayon";
+            packageId = "rayon";
+          }
+          {
+            name = "smartstring";
+            packageId = "smartstring";
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "version_check";
+            packageId = "version_check";
+          }
+        ];
+        features = {
+          "async" = [ "polars-plan/async" "polars-io/async" ];
+          "cloud" = [ "async" "polars-io/cloud" "polars-plan/cloud" "tokio" "futures" ];
+          "cross_join" = [ "polars-ops/cross_join" ];
+          "csv" = [ "polars-plan/csv" "polars-io/csv" ];
+          "dtype-array" = [ "polars-core/dtype-array" ];
+          "dtype-categorical" = [ "polars-core/dtype-categorical" ];
+          "dtype-decimal" = [ "polars-core/dtype-decimal" ];
+          "dtype-i16" = [ "polars-core/dtype-i16" ];
+          "dtype-i8" = [ "polars-core/dtype-i8" ];
+          "dtype-u16" = [ "polars-core/dtype-u16" ];
+          "dtype-u8" = [ "polars-core/dtype-u8" ];
+          "futures" = [ "dep:futures" ];
+          "ipc" = [ "polars-plan/ipc" "polars-io/ipc" ];
+          "nightly" = [ "polars-core/nightly" "polars-utils/nightly" "hashbrown/nightly" ];
+          "parquet" = [ "polars-plan/parquet" "polars-io/parquet" "polars-io/async" ];
+          "test" = [ "polars-core/chunked_ids" ];
+          "tokio" = [ "dep:tokio" ];
+        };
+        resolvedDefaultFeatures = [ "cross_join" "parquet" ];
+      };
+      "polars-plan" = rec {
+        crateName = "polars-plan";
+        version = "0.35.4";
+        edition = "2021";
+        sha256 = "1g2z0g0hpg05jp4n9s6m6m32adrjrdlq6zxd5c9lp1ggb04jmqqh";
+        authors = [
+          "Ritchie Vink <ritchie46@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "ahash";
+            packageId = "ahash";
+          }
+          {
+            name = "bytemuck";
+            packageId = "bytemuck";
+            features = [ "derive" "extern_crate_alloc" ];
+          }
+          {
+            name = "once_cell";
+            packageId = "once_cell";
+          }
+          {
+            name = "percent-encoding";
+            packageId = "percent-encoding";
+          }
+          {
+            name = "polars-arrow";
+            packageId = "polars-arrow";
+            rename = "arrow";
+            usesDefaultFeatures = false;
+            features = [ "compute_aggregate" "compute_arithmetics" "compute_boolean" "compute_boolean_kleene" "compute_cast" "compute_comparison" "compute_concatenate" "compute_filter" "compute_if_then_else" ];
+          }
+          {
+            name = "polars-core";
+            packageId = "polars-core";
+            usesDefaultFeatures = false;
+            features = [ "lazy" "zip_with" "random" ];
+          }
+          {
+            name = "polars-io";
+            packageId = "polars-io";
+            usesDefaultFeatures = false;
+            features = [ "lazy" ];
+          }
+          {
+            name = "polars-ops";
+            packageId = "polars-ops";
+            usesDefaultFeatures = false;
+            features = [ "zip_with" ];
+          }
+          {
+            name = "polars-parquet";
+            packageId = "polars-parquet";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-time";
+            packageId = "polars-time";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-utils";
+            packageId = "polars-utils";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "rayon";
+            packageId = "rayon";
+          }
+          {
+            name = "regex";
+            packageId = "regex";
+            optional = true;
+          }
+          {
+            name = "smartstring";
+            packageId = "smartstring";
+          }
+          {
+            name = "strum_macros";
+            packageId = "strum_macros";
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "version_check";
+            packageId = "version_check";
+          }
+        ];
+        features = {
+          "abs" = [ "polars-ops/abs" ];
+          "approx_unique" = [ "polars-ops/approx_unique" ];
+          "asof_join" = [ "polars-core/asof_join" "polars-time" "polars-ops/asof_join" ];
+          "async" = [ "polars-io/async" ];
+          "bigidx" = [ "polars-core/bigidx" ];
+          "binary_encoding" = [ "polars-ops/binary_encoding" ];
+          "chrono" = [ "dep:chrono" ];
+          "chrono-tz" = [ "dep:chrono-tz" ];
+          "chunked_ids" = [ "polars-core/chunked_ids" ];
+          "ciborium" = [ "dep:ciborium" ];
+          "cloud" = [ "async" "polars-io/cloud" ];
+          "cov" = [ "polars-ops/cov" ];
+          "cross_join" = [ "polars-ops/cross_join" ];
+          "csv" = [ "polars-io/csv" ];
+          "cum_agg" = [ "polars-ops/cum_agg" ];
+          "cutqcut" = [ "polars-ops/cutqcut" ];
+          "date_offset" = [ "polars-time" "chrono" ];
+          "diff" = [ "polars-ops/diff" ];
+          "dtype-array" = [ "polars-core/dtype-array" "polars-ops/dtype-array" ];
+          "dtype-categorical" = [ "polars-core/dtype-categorical" ];
+          "dtype-date" = [ "polars-core/dtype-date" "polars-time/dtype-date" "temporal" ];
+          "dtype-datetime" = [ "polars-core/dtype-datetime" "polars-time/dtype-datetime" "temporal" ];
+          "dtype-decimal" = [ "polars-core/dtype-decimal" ];
+          "dtype-duration" = [ "polars-core/dtype-duration" "polars-time/dtype-duration" "temporal" ];
+          "dtype-i16" = [ "polars-core/dtype-i16" ];
+          "dtype-i8" = [ "polars-core/dtype-i8" ];
+          "dtype-struct" = [ "polars-core/dtype-struct" ];
+          "dtype-time" = [ "polars-core/dtype-time" "polars-time/dtype-time" ];
+          "dtype-u16" = [ "polars-core/dtype-u16" ];
+          "dtype-u8" = [ "polars-core/dtype-u8" ];
+          "dynamic_group_by" = [ "polars-core/dynamic_group_by" ];
+          "ewma" = [ "polars-ops/ewma" ];
+          "extract_groups" = [ "regex" "dtype-struct" "polars-ops/extract_groups" ];
+          "extract_jsonpath" = [ "polars-ops/extract_jsonpath" ];
+          "ffi_plugin" = [ "libloading" "polars-ffi" ];
+          "fmt" = [ "polars-core/fmt" ];
+          "fused" = [ "polars-ops/fused" ];
+          "futures" = [ "dep:futures" ];
+          "interpolate" = [ "polars-ops/interpolate" ];
+          "ipc" = [ "polars-io/ipc" ];
+          "is_first_distinct" = [ "polars-core/is_first_distinct" "polars-ops/is_first_distinct" ];
+          "is_in" = [ "polars-ops/is_in" ];
+          "is_last_distinct" = [ "polars-core/is_last_distinct" "polars-ops/is_last_distinct" ];
+          "is_unique" = [ "polars-ops/is_unique" ];
+          "json" = [ "polars-io/json" ];
+          "libloading" = [ "dep:libloading" ];
+          "list_any_all" = [ "polars-ops/list_any_all" ];
+          "list_count" = [ "polars-ops/list_count" ];
+          "list_drop_nulls" = [ "polars-ops/list_drop_nulls" ];
+          "list_gather" = [ "polars-ops/list_gather" ];
+          "list_sample" = [ "polars-ops/list_sample" ];
+          "list_sets" = [ "polars-ops/list_sets" ];
+          "list_to_struct" = [ "polars-ops/list_to_struct" ];
+          "log" = [ "polars-ops/log" ];
+          "merge_sorted" = [ "polars-ops/merge_sorted" ];
+          "mode" = [ "polars-ops/mode" ];
+          "moment" = [ "polars-ops/moment" ];
+          "nightly" = [ "polars-utils/nightly" "polars-ops/nightly" ];
+          "object" = [ "polars-core/object" ];
+          "parquet" = [ "polars-io/parquet" "polars-parquet" ];
+          "pct_change" = [ "polars-ops/pct_change" ];
+          "peaks" = [ "polars-ops/peaks" ];
+          "pivot" = [ "polars-core/rows" "polars-ops/pivot" ];
+          "polars-ffi" = [ "dep:polars-ffi" ];
+          "polars-parquet" = [ "dep:polars-parquet" ];
+          "polars-time" = [ "dep:polars-time" ];
+          "propagate_nans" = [ "polars-ops/propagate_nans" ];
+          "python" = [ "dep:pyo3" "ciborium" ];
+          "random" = [ "polars-core/random" ];
+          "rank" = [ "polars-ops/rank" ];
+          "regex" = [ "dep:regex" ];
+          "repeat_by" = [ "polars-ops/repeat_by" ];
+          "rle" = [ "polars-ops/rle" ];
+          "rolling_window" = [ "polars-core/rolling_window" "polars-time/rolling_window" "polars-ops/rolling_window" "polars-time/rolling_window" ];
+          "round_series" = [ "polars-ops/round_series" ];
+          "row_hash" = [ "polars-core/row_hash" "polars-ops/hash" ];
+          "search_sorted" = [ "polars-ops/search_sorted" ];
+          "semi_anti_join" = [ "polars-ops/semi_anti_join" ];
+          "serde" = [ "dep:serde" "polars-core/serde-lazy" "polars-time/serde" "polars-io/serde" "polars-ops/serde" ];
+          "string_encoding" = [ "polars-ops/string_encoding" ];
+          "string_pad" = [ "polars-ops/string_pad" ];
+          "string_to_integer" = [ "polars-ops/string_to_integer" ];
+          "strings" = [ "polars-core/strings" "polars-ops/strings" ];
+          "temporal" = [ "polars-core/temporal" "dtype-date" "dtype-datetime" "dtype-time" ];
+          "timezones" = [ "chrono-tz" "polars-time/timezones" "polars-core/timezones" "regex" ];
+          "top_k" = [ "polars-ops/top_k" ];
+          "unique_counts" = [ "polars-ops/unique_counts" ];
+        };
+        resolvedDefaultFeatures = [ "abs" "cross_join" "cum_agg" "dtype-date" "dtype-datetime" "dtype-duration" "dtype-struct" "dtype-time" "is_in" "log" "meta" "parquet" "polars-parquet" "polars-time" "regex" "round_series" "strings" "temporal" "trigonometry" ];
+      };
+      "polars-row" = rec {
+        crateName = "polars-row";
+        version = "0.35.4";
+        edition = "2021";
+        sha256 = "1b3d8pdxz12dzg75nqb7b2p83l15c1z4r6589sknp462ra0sndfi";
+        authors = [
+          "Ritchie Vink <ritchie46@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "polars-arrow";
+            packageId = "polars-arrow";
+            rename = "arrow";
+            usesDefaultFeatures = false;
+            features = [ "compute_aggregate" "compute_arithmetics" "compute_boolean" "compute_boolean_kleene" "compute_cast" "compute_comparison" "compute_concatenate" "compute_filter" "compute_if_then_else" ];
+          }
+          {
+            name = "polars-error";
+            packageId = "polars-error";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-utils";
+            packageId = "polars-utils";
+            usesDefaultFeatures = false;
+          }
+        ];
+
+      };
+      "polars-sql" = rec {
+        crateName = "polars-sql";
+        version = "0.35.4";
+        edition = "2021";
+        sha256 = "17jiflfmyymz1fdk2mrsdri2yqs1h7rqn66y3yny79a9d1wdgnxq";
+        authors = [
+          "Ritchie Vink <ritchie46@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "polars-arrow";
+            packageId = "polars-arrow";
+            rename = "arrow";
+            usesDefaultFeatures = false;
+            features = [ "compute_aggregate" "compute_arithmetics" "compute_boolean" "compute_boolean_kleene" "compute_cast" "compute_comparison" "compute_concatenate" "compute_filter" "compute_if_then_else" ];
+          }
+          {
+            name = "polars-core";
+            packageId = "polars-core";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-error";
+            packageId = "polars-error";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-lazy";
+            packageId = "polars-lazy";
+            usesDefaultFeatures = false;
+            features = [ "strings" "cross_join" "trigonometry" "abs" "round_series" "log" "regex" "is_in" "meta" "cum_agg" "dtype-date" ];
+          }
+          {
+            name = "polars-plan";
+            packageId = "polars-plan";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "rand";
+            packageId = "rand";
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+          }
+          {
+            name = "serde_json";
+            packageId = "serde_json";
+          }
+          {
+            name = "sqlparser";
+            packageId = "sqlparser";
+          }
+        ];
+        features = {
+          "csv" = [ "polars-lazy/csv" ];
+          "diagonal_concat" = [ "polars-lazy/diagonal_concat" ];
+          "ipc" = [ "polars-lazy/ipc" ];
+          "json" = [ "polars-lazy/json" ];
+          "parquet" = [ "polars-lazy/parquet" ];
+          "semi_anti_join" = [ "polars-lazy/semi_anti_join" ];
+        };
+        resolvedDefaultFeatures = [ "parquet" ];
+      };
+      "polars-time" = rec {
+        crateName = "polars-time";
+        version = "0.35.4";
+        edition = "2021";
+        sha256 = "0lgyk3fpp7krbyfra51pb4fqn6m3hk5gbj61fdvn3pffx5wnzrda";
+        authors = [
+          "Ritchie Vink <ritchie46@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "atoi";
+            packageId = "atoi";
+          }
+          {
+            name = "chrono";
+            packageId = "chrono";
+            usesDefaultFeatures = false;
+            features = [ "std" ];
+          }
+          {
+            name = "now";
+            packageId = "now";
+          }
+          {
+            name = "once_cell";
+            packageId = "once_cell";
+          }
+          {
+            name = "polars-arrow";
+            packageId = "polars-arrow";
+            rename = "arrow";
+            usesDefaultFeatures = false;
+            features = [ "compute_aggregate" "compute_arithmetics" "compute_boolean" "compute_boolean_kleene" "compute_cast" "compute_comparison" "compute_concatenate" "compute_filter" "compute_if_then_else" "compute" "temporal" ];
+          }
+          {
+            name = "polars-core";
+            packageId = "polars-core";
+            usesDefaultFeatures = false;
+            features = [ "dtype-datetime" "dtype-duration" "dtype-time" "dtype-date" ];
+          }
+          {
+            name = "polars-error";
+            packageId = "polars-error";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-ops";
+            packageId = "polars-ops";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-utils";
+            packageId = "polars-utils";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "regex";
+            packageId = "regex";
+          }
+          {
+            name = "smartstring";
+            packageId = "smartstring";
+          }
+        ];
+        features = {
+          "chrono-tz" = [ "dep:chrono-tz" ];
+          "dtype-date" = [ "polars-core/dtype-date" "polars-core/temporal" ];
+          "dtype-datetime" = [ "polars-core/dtype-date" "polars-core/temporal" ];
+          "dtype-duration" = [ "polars-core/dtype-duration" "polars-core/temporal" ];
+          "dtype-time" = [ "polars-core/dtype-time" "polars-core/temporal" ];
+          "fmt" = [ "polars-core/fmt" ];
+          "rolling_window" = [ "polars-core/rolling_window" "dtype-duration" ];
+          "serde" = [ "dep:serde" ];
+          "test" = [ "dtype-date" "dtype-datetime" "polars-core/fmt" ];
+          "timezones" = [ "chrono-tz" "dtype-datetime" "polars-core/timezones" "arrow/timezones" "polars-ops/timezones" ];
+        };
+        resolvedDefaultFeatures = [ "dtype-date" "dtype-datetime" "dtype-duration" "dtype-time" ];
+      };
+      "polars-utils" = rec {
+        crateName = "polars-utils";
+        version = "0.35.4";
+        edition = "2021";
+        sha256 = "1z7v7h54p883ww64mqpn4cphzwv0fd2bgsn8b1lx8qgyd60ycv6s";
+        authors = [
+          "Ritchie Vink <ritchie46@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "ahash";
+            packageId = "ahash";
+          }
+          {
+            name = "bytemuck";
+            packageId = "bytemuck";
+            features = [ "derive" "extern_crate_alloc" ];
+          }
+          {
+            name = "hashbrown";
+            packageId = "hashbrown";
+            features = [ "rayon" "ahash" ];
+          }
+          {
+            name = "indexmap";
+            packageId = "indexmap";
+            features = [ "std" ];
+          }
+          {
+            name = "num-traits";
+            packageId = "num-traits";
+          }
+          {
+            name = "once_cell";
+            packageId = "once_cell";
+          }
+          {
+            name = "polars-error";
+            packageId = "polars-error";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "rayon";
+            packageId = "rayon";
+          }
+          {
+            name = "smartstring";
+            packageId = "smartstring";
+          }
+          {
+            name = "sysinfo";
+            packageId = "sysinfo";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "version_check";
+            packageId = "version_check";
+          }
+        ];
+        features = {
+          "sysinfo" = [ "dep:sysinfo" ];
+        };
+        resolvedDefaultFeatures = [ "sysinfo" ];
+      };
+      "portable-atomic" = rec {
+        crateName = "portable-atomic";
+        version = "1.5.1";
+        edition = "2018";
+        sha256 = "0fxg0i7n3wmffbfn95nwi062srdg40bwkj5143w1kk6pgw7apk1v";
+        features = {
+          "critical-section" = [ "dep:critical-section" ];
+          "default" = [ "fallback" ];
+          "serde" = [ "dep:serde" ];
+        };
+        resolvedDefaultFeatures = [ "default" "fallback" ];
+      };
+      "ppv-lite86" = rec {
+        crateName = "ppv-lite86";
+        version = "0.2.17";
+        edition = "2018";
+        sha256 = "1pp6g52aw970adv3x2310n7glqnji96z0a9wiamzw89ibf0ayh2v";
+        authors = [
+          "The CryptoCorrosion Contributors"
+        ];
+        features = {
+          "default" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "simd" "std" ];
+      };
+      "prettyplease" = rec {
+        crateName = "prettyplease";
+        version = "0.2.15";
+        edition = "2021";
+        links = "prettyplease02";
+        sha256 = "17az47j29q76gnyqvd5giryjz2fp7zw7vzcka1rb8ndbfgbmn05f";
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "syn";
+            packageId = "syn 2.0.39";
+            usesDefaultFeatures = false;
+            features = [ "full" ];
+          }
+        ];
+        devDependencies = [
+          {
+            name = "syn";
+            packageId = "syn 2.0.39";
+            usesDefaultFeatures = false;
+            features = [ "parsing" ];
+          }
+        ];
+        features = {
+          "verbatim" = [ "syn/parsing" ];
+        };
+      };
+      "proc-macro2" = rec {
+        crateName = "proc-macro2";
+        version = "1.0.69";
+        edition = "2021";
+        sha256 = "1nljgyllbm3yr3pa081bf83gxh6l4zvjqzaldw7v4mj9xfgihk0k";
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+          "Alex Crichton <alex@alexcrichton.com>"
+        ];
+        dependencies = [
+          {
+            name = "unicode-ident";
+            packageId = "unicode-ident";
+          }
+        ];
+        features = {
+          "default" = [ "proc-macro" ];
+        };
+        resolvedDefaultFeatures = [ "default" "proc-macro" ];
+      };
+      "prost" = rec {
+        crateName = "prost";
+        version = "0.12.2";
+        edition = "2021";
+        sha256 = "04k3c8d2k554gcbhii04canz6g1m6wbx00cdxdnzcal8qw7l2njs";
+        authors = [
+          "Dan Burkert <dan@danburkert.com>"
+          "Lucio Franco <luciofranco14@gmail.com"
+          "Tokio Contributors <team@tokio.rs>"
+        ];
+        dependencies = [
+          {
+            name = "bytes";
+            packageId = "bytes";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "prost-derive";
+            packageId = "prost-derive";
+            optional = true;
+          }
+        ];
+        features = {
+          "default" = [ "prost-derive" "std" ];
+          "prost-derive" = [ "dep:prost-derive" ];
+        };
+        resolvedDefaultFeatures = [ "default" "prost-derive" "std" ];
+      };
+      "prost-build" = rec {
+        crateName = "prost-build";
+        version = "0.12.2";
+        edition = "2021";
+        sha256 = "0f57mmf6cg7f4401x9s3fgdc1idnz7i1nxxjxyzi2jbhr22d18qz";
+        authors = [
+          "Dan Burkert <dan@danburkert.com>"
+          "Lucio Franco <luciofranco14@gmail.com>"
+          "Tokio Contributors <team@tokio.rs>"
+        ];
+        dependencies = [
+          {
+            name = "bytes";
+            packageId = "bytes";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "heck";
+            packageId = "heck";
+          }
+          {
+            name = "itertools";
+            packageId = "itertools";
+            usesDefaultFeatures = false;
+            features = [ "use_alloc" ];
+          }
+          {
+            name = "log";
+            packageId = "log";
+          }
+          {
+            name = "multimap";
+            packageId = "multimap";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "once_cell";
+            packageId = "once_cell";
+          }
+          {
+            name = "petgraph";
+            packageId = "petgraph";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "prettyplease";
+            packageId = "prettyplease";
+            optional = true;
+          }
+          {
+            name = "prost";
+            packageId = "prost";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "prost-types";
+            packageId = "prost-types";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "regex";
+            packageId = "regex";
+            usesDefaultFeatures = false;
+            features = [ "std" "unicode-bool" ];
+          }
+          {
+            name = "syn";
+            packageId = "syn 2.0.39";
+            optional = true;
+            features = [ "full" ];
+          }
+          {
+            name = "tempfile";
+            packageId = "tempfile";
+          }
+          {
+            name = "which";
+            packageId = "which";
+          }
+        ];
+        features = {
+          "cleanup-markdown" = [ "pulldown-cmark" "pulldown-cmark-to-cmark" ];
+          "default" = [ "format" ];
+          "format" = [ "prettyplease" "syn" ];
+          "prettyplease" = [ "dep:prettyplease" ];
+          "pulldown-cmark" = [ "dep:pulldown-cmark" ];
+          "pulldown-cmark-to-cmark" = [ "dep:pulldown-cmark-to-cmark" ];
+          "syn" = [ "dep:syn" ];
+        };
+        resolvedDefaultFeatures = [ "default" "format" "prettyplease" "syn" ];
+      };
+      "prost-derive" = rec {
+        crateName = "prost-derive";
+        version = "0.12.2";
+        edition = "2021";
+        sha256 = "1g268fzmswaf6rx1sm8000h6a4rigd4b6zg55wysi95cvyjifmq6";
+        procMacro = true;
+        authors = [
+          "Dan Burkert <dan@danburkert.com>"
+          "Lucio Franco <luciofranco14@gmail.com>"
+          "Tokio Contributors <team@tokio.rs>"
+        ];
+        dependencies = [
+          {
+            name = "anyhow";
+            packageId = "anyhow";
+          }
+          {
+            name = "itertools";
+            packageId = "itertools";
+            usesDefaultFeatures = false;
+            features = [ "use_alloc" ];
+          }
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn 2.0.39";
+            features = [ "extra-traits" ];
+          }
+        ];
+
+      };
+      "prost-types" = rec {
+        crateName = "prost-types";
+        version = "0.12.2";
+        edition = "2021";
+        sha256 = "0lls5w7yh2jxi764kd9k44dwskrr85j2fs335wg2i47m6qig6fc3";
+        authors = [
+          "Dan Burkert <dan@danburkert.com>"
+          "Lucio Franco <luciofranco14@gmail.com"
+          "Tokio Contributors <team@tokio.rs>"
+        ];
+        dependencies = [
+          {
+            name = "prost";
+            packageId = "prost";
+            usesDefaultFeatures = false;
+            features = [ "prost-derive" ];
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "std" = [ "prost/std" ];
+        };
+      };
+      "quote" = rec {
+        crateName = "quote";
+        version = "1.0.33";
+        edition = "2018";
+        sha256 = "1biw54hbbr12wdwjac55z1m2x2rylciw83qnjn564a3096jgqrsj";
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "default" = [ "proc-macro" ];
+          "proc-macro" = [ "proc-macro2/proc-macro" ];
+        };
+        resolvedDefaultFeatures = [ "default" "proc-macro" ];
+      };
+      "rand" = rec {
+        crateName = "rand";
+        version = "0.8.5";
+        edition = "2018";
+        sha256 = "013l6931nn7gkc23jz5mm3qdhf93jjf0fg64nz2lp4i51qd8vbrl";
+        authors = [
+          "The Rand Project Developers"
+          "The Rust Project Developers"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+            optional = true;
+            usesDefaultFeatures = false;
+            target = { target, features }: (target."unix" or false);
+          }
+          {
+            name = "rand_chacha";
+            packageId = "rand_chacha";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "rand_core";
+            packageId = "rand_core";
+          }
+        ];
+        features = {
+          "alloc" = [ "rand_core/alloc" ];
+          "default" = [ "std" "std_rng" ];
+          "getrandom" = [ "rand_core/getrandom" ];
+          "libc" = [ "dep:libc" ];
+          "log" = [ "dep:log" ];
+          "packed_simd" = [ "dep:packed_simd" ];
+          "rand_chacha" = [ "dep:rand_chacha" ];
+          "serde" = [ "dep:serde" ];
+          "serde1" = [ "serde" "rand_core/serde1" ];
+          "simd_support" = [ "packed_simd" ];
+          "std" = [ "rand_core/std" "rand_chacha/std" "alloc" "getrandom" "libc" ];
+          "std_rng" = [ "rand_chacha" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "getrandom" "libc" "rand_chacha" "small_rng" "std" "std_rng" ];
+      };
+      "rand_chacha" = rec {
+        crateName = "rand_chacha";
+        version = "0.3.1";
+        edition = "2018";
+        sha256 = "123x2adin558xbhvqb8w4f6syjsdkmqff8cxwhmjacpsl1ihmhg6";
+        authors = [
+          "The Rand Project Developers"
+          "The Rust Project Developers"
+          "The CryptoCorrosion Contributors"
+        ];
+        dependencies = [
+          {
+            name = "ppv-lite86";
+            packageId = "ppv-lite86";
+            usesDefaultFeatures = false;
+            features = [ "simd" ];
+          }
+          {
+            name = "rand_core";
+            packageId = "rand_core";
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "serde" = [ "dep:serde" ];
+          "serde1" = [ "serde" ];
+          "std" = [ "ppv-lite86/std" ];
+        };
+        resolvedDefaultFeatures = [ "std" ];
+      };
+      "rand_core" = rec {
+        crateName = "rand_core";
+        version = "0.6.4";
+        edition = "2018";
+        sha256 = "0b4j2v4cb5krak1pv6kakv4sz6xcwbrmy2zckc32hsigbrwy82zc";
+        authors = [
+          "The Rand Project Developers"
+          "The Rust Project Developers"
+        ];
+        dependencies = [
+          {
+            name = "getrandom";
+            packageId = "getrandom";
+            optional = true;
+          }
+        ];
+        features = {
+          "getrandom" = [ "dep:getrandom" ];
+          "serde" = [ "dep:serde" ];
+          "serde1" = [ "serde" ];
+          "std" = [ "alloc" "getrandom" "getrandom/std" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "getrandom" "std" ];
+      };
+      "rand_distr" = rec {
+        crateName = "rand_distr";
+        version = "0.4.3";
+        edition = "2018";
+        sha256 = "0cgfwg3z0pkqhrl0x90c77kx70r6g9z4m6fxq9v0h2ibr2dhpjrj";
+        authors = [
+          "The Rand Project Developers"
+        ];
+        dependencies = [
+          {
+            name = "num-traits";
+            packageId = "num-traits";
+            usesDefaultFeatures = false;
+            features = [ "libm" ];
+          }
+          {
+            name = "rand";
+            packageId = "rand";
+            usesDefaultFeatures = false;
+          }
+        ];
+        devDependencies = [
+          {
+            name = "rand";
+            packageId = "rand";
+            usesDefaultFeatures = false;
+            features = [ "std_rng" "std" "small_rng" ];
+          }
+        ];
+        features = {
+          "alloc" = [ "rand/alloc" ];
+          "default" = [ "std" ];
+          "serde" = [ "dep:serde" ];
+          "serde1" = [ "serde" "rand/serde1" ];
+          "std" = [ "alloc" "rand/std" ];
+          "std_math" = [ "num-traits/std" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "std" ];
+      };
+      "rayon" = rec {
+        crateName = "rayon";
+        version = "1.8.0";
+        edition = "2021";
+        sha256 = "1cfdnvchf7j4cpha5jkcrrsr61li9i9lp5ak7xdq6d3pvc1xn9ww";
+        authors = [
+          "Niko Matsakis <niko@alum.mit.edu>"
+          "Josh Stone <cuviper@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "either";
+            packageId = "either";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "rayon-core";
+            packageId = "rayon-core";
+          }
+        ];
+
+      };
+      "rayon-core" = rec {
+        crateName = "rayon-core";
+        version = "1.12.0";
+        edition = "2021";
+        links = "rayon-core";
+        sha256 = "1vaq0q71yfvcwlmia0iqf6ixj2fibjcf2xjy92n1m1izv1mgpqsw";
+        authors = [
+          "Niko Matsakis <niko@alum.mit.edu>"
+          "Josh Stone <cuviper@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "crossbeam-deque";
+            packageId = "crossbeam-deque";
+          }
+          {
+            name = "crossbeam-utils";
+            packageId = "crossbeam-utils";
+          }
+        ];
+
+      };
+      "redox_syscall 0.2.16" = rec {
+        crateName = "redox_syscall";
+        version = "0.2.16";
+        edition = "2018";
+        sha256 = "16jicm96kjyzm802cxdd1k9jmcph0db1a4lhslcnhjsvhp0mhnpv";
+        libName = "syscall";
+        authors = [
+          "Jeremy Soller <jackpot51@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "bitflags";
+            packageId = "bitflags 1.3.2";
+          }
+        ];
+
+      };
+      "redox_syscall 0.4.1" = rec {
+        crateName = "redox_syscall";
+        version = "0.4.1";
+        edition = "2018";
+        sha256 = "1aiifyz5dnybfvkk4cdab9p2kmphag1yad6iknc7aszlxxldf8j7";
+        libName = "syscall";
+        authors = [
+          "Jeremy Soller <jackpot51@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "bitflags";
+            packageId = "bitflags 1.3.2";
+          }
+        ];
+        features = {
+          "core" = [ "dep:core" ];
+          "rustc-dep-of-std" = [ "core" "bitflags/rustc-dep-of-std" ];
+        };
+      };
+      "redox_users" = rec {
+        crateName = "redox_users";
+        version = "0.4.4";
+        edition = "2021";
+        sha256 = "1d1c7dhbb62sh8jrq9dhvqcyxqsh3wg8qknsi94iwq3r0wh7k151";
+        authors = [
+          "Jose Narvaez <goyox86@gmail.com>"
+          "Wesley Hershberger <mggmugginsmc@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "getrandom";
+            packageId = "getrandom";
+            features = [ "std" ];
+          }
+          {
+            name = "libredox";
+            packageId = "libredox";
+            usesDefaultFeatures = false;
+            features = [ "call" ];
+          }
+          {
+            name = "thiserror";
+            packageId = "thiserror";
+          }
+        ];
+        features = {
+          "auth" = [ "rust-argon2" "zeroize" ];
+          "default" = [ "auth" ];
+          "rust-argon2" = [ "dep:rust-argon2" ];
+          "zeroize" = [ "dep:zeroize" ];
+        };
+      };
+      "regex" = rec {
+        crateName = "regex";
+        version = "1.10.2";
+        edition = "2021";
+        sha256 = "0hxkd814n4irind8im5c9am221ri6bprx49nc7yxv02ykhd9a2rq";
+        authors = [
+          "The Rust Project Developers"
+          "Andrew Gallant <jamslam@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "aho-corasick";
+            packageId = "aho-corasick";
+            optional = true;
+          }
+          {
+            name = "memchr";
+            packageId = "memchr";
+            optional = true;
+          }
+          {
+            name = "regex-automata";
+            packageId = "regex-automata";
+            usesDefaultFeatures = false;
+            features = [ "alloc" "syntax" "meta" "nfa-pikevm" ];
+          }
+          {
+            name = "regex-syntax";
+            packageId = "regex-syntax";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "default" = [ "std" "perf" "unicode" "regex-syntax/default" ];
+          "logging" = [ "aho-corasick?/logging" "memchr?/logging" "regex-automata/logging" ];
+          "perf" = [ "perf-cache" "perf-dfa" "perf-onepass" "perf-backtrack" "perf-inline" "perf-literal" ];
+          "perf-backtrack" = [ "regex-automata/nfa-backtrack" ];
+          "perf-dfa" = [ "regex-automata/hybrid" ];
+          "perf-dfa-full" = [ "regex-automata/dfa-build" "regex-automata/dfa-search" ];
+          "perf-inline" = [ "regex-automata/perf-inline" ];
+          "perf-literal" = [ "dep:aho-corasick" "dep:memchr" "regex-automata/perf-literal" ];
+          "perf-onepass" = [ "regex-automata/dfa-onepass" ];
+          "std" = [ "aho-corasick?/std" "memchr?/std" "regex-automata/std" "regex-syntax/std" ];
+          "unicode" = [ "unicode-age" "unicode-bool" "unicode-case" "unicode-gencat" "unicode-perl" "unicode-script" "unicode-segment" "regex-automata/unicode" "regex-syntax/unicode" ];
+          "unicode-age" = [ "regex-automata/unicode-age" "regex-syntax/unicode-age" ];
+          "unicode-bool" = [ "regex-automata/unicode-bool" "regex-syntax/unicode-bool" ];
+          "unicode-case" = [ "regex-automata/unicode-case" "regex-syntax/unicode-case" ];
+          "unicode-gencat" = [ "regex-automata/unicode-gencat" "regex-syntax/unicode-gencat" ];
+          "unicode-perl" = [ "regex-automata/unicode-perl" "regex-automata/unicode-word-boundary" "regex-syntax/unicode-perl" ];
+          "unicode-script" = [ "regex-automata/unicode-script" "regex-syntax/unicode-script" ];
+          "unicode-segment" = [ "regex-automata/unicode-segment" "regex-syntax/unicode-segment" ];
+          "unstable" = [ "pattern" ];
+          "use_std" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "perf" "perf-backtrack" "perf-cache" "perf-dfa" "perf-inline" "perf-literal" "perf-onepass" "std" "unicode" "unicode-age" "unicode-bool" "unicode-case" "unicode-gencat" "unicode-perl" "unicode-script" "unicode-segment" ];
+      };
+      "regex-automata" = rec {
+        crateName = "regex-automata";
+        version = "0.4.3";
+        edition = "2021";
+        sha256 = "0gs8q9yhd3kcg4pr00ag4viqxnh5l7jpyb9fsfr8hzh451w4r02z";
+        authors = [
+          "The Rust Project Developers"
+          "Andrew Gallant <jamslam@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "aho-corasick";
+            packageId = "aho-corasick";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "memchr";
+            packageId = "memchr";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "regex-syntax";
+            packageId = "regex-syntax";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "default" = [ "std" "syntax" "perf" "unicode" "meta" "nfa" "dfa" "hybrid" ];
+          "dfa" = [ "dfa-build" "dfa-search" "dfa-onepass" ];
+          "dfa-build" = [ "nfa-thompson" "dfa-search" ];
+          "dfa-onepass" = [ "nfa-thompson" ];
+          "hybrid" = [ "alloc" "nfa-thompson" ];
+          "internal-instrument" = [ "internal-instrument-pikevm" ];
+          "internal-instrument-pikevm" = [ "logging" "std" ];
+          "logging" = [ "dep:log" "aho-corasick?/logging" "memchr?/logging" ];
+          "meta" = [ "syntax" "nfa-pikevm" ];
+          "nfa" = [ "nfa-thompson" "nfa-pikevm" "nfa-backtrack" ];
+          "nfa-backtrack" = [ "nfa-thompson" ];
+          "nfa-pikevm" = [ "nfa-thompson" ];
+          "nfa-thompson" = [ "alloc" ];
+          "perf" = [ "perf-inline" "perf-literal" ];
+          "perf-literal" = [ "perf-literal-substring" "perf-literal-multisubstring" ];
+          "perf-literal-multisubstring" = [ "std" "dep:aho-corasick" ];
+          "perf-literal-substring" = [ "aho-corasick?/perf-literal" "dep:memchr" ];
+          "std" = [ "regex-syntax?/std" "memchr?/std" "aho-corasick?/std" "alloc" ];
+          "syntax" = [ "dep:regex-syntax" "alloc" ];
+          "unicode" = [ "unicode-age" "unicode-bool" "unicode-case" "unicode-gencat" "unicode-perl" "unicode-script" "unicode-segment" "unicode-word-boundary" "regex-syntax?/unicode" ];
+          "unicode-age" = [ "regex-syntax?/unicode-age" ];
+          "unicode-bool" = [ "regex-syntax?/unicode-bool" ];
+          "unicode-case" = [ "regex-syntax?/unicode-case" ];
+          "unicode-gencat" = [ "regex-syntax?/unicode-gencat" ];
+          "unicode-perl" = [ "regex-syntax?/unicode-perl" ];
+          "unicode-script" = [ "regex-syntax?/unicode-script" ];
+          "unicode-segment" = [ "regex-syntax?/unicode-segment" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "dfa-onepass" "dfa-search" "hybrid" "meta" "nfa-backtrack" "nfa-pikevm" "nfa-thompson" "perf-inline" "perf-literal" "perf-literal-multisubstring" "perf-literal-substring" "std" "syntax" "unicode" "unicode-age" "unicode-bool" "unicode-case" "unicode-gencat" "unicode-perl" "unicode-script" "unicode-segment" "unicode-word-boundary" ];
+      };
+      "regex-syntax" = rec {
+        crateName = "regex-syntax";
+        version = "0.8.2";
+        edition = "2021";
+        sha256 = "17rd2s8xbiyf6lb4aj2nfi44zqlj98g2ays8zzj2vfs743k79360";
+        authors = [
+          "The Rust Project Developers"
+          "Andrew Gallant <jamslam@gmail.com>"
+        ];
+        features = {
+          "arbitrary" = [ "dep:arbitrary" ];
+          "default" = [ "std" "unicode" ];
+          "unicode" = [ "unicode-age" "unicode-bool" "unicode-case" "unicode-gencat" "unicode-perl" "unicode-script" "unicode-segment" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" "unicode" "unicode-age" "unicode-bool" "unicode-case" "unicode-gencat" "unicode-perl" "unicode-script" "unicode-segment" ];
+      };
+      "ring 0.16.20" = rec {
+        crateName = "ring";
+        version = "0.16.20";
+        edition = "2018";
+        links = "ring-asm";
+        sha256 = "1z682xp7v38ayq9g9nkbhhfpj6ygralmlx7wdmsfv8rnw99cylrh";
+        authors = [
+          "Brian Smith <brian@briansmith.org>"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+            usesDefaultFeatures = false;
+            target = { target, features }: (("android" == target."os" or null) || ("linux" == target."os" or null));
+          }
+          {
+            name = "once_cell";
+            packageId = "once_cell";
+            optional = true;
+            usesDefaultFeatures = false;
+            target = { target, features }: (("android" == target."os" or null) || ("linux" == target."os" or null));
+            features = [ "std" ];
+          }
+          {
+            name = "once_cell";
+            packageId = "once_cell";
+            usesDefaultFeatures = false;
+            target = { target, features }: (("dragonfly" == target."os" or null) || ("freebsd" == target."os" or null) || ("illumos" == target."os" or null) || ("netbsd" == target."os" or null) || ("openbsd" == target."os" or null) || ("solaris" == target."os" or null));
+            features = [ "std" ];
+          }
+          {
+            name = "spin";
+            packageId = "spin 0.5.2";
+            usesDefaultFeatures = false;
+            target = { target, features }: (("x86" == target."arch" or null) || ("x86_64" == target."arch" or null) || ((("aarch64" == target."arch" or null) || ("arm" == target."arch" or null)) && (("android" == target."os" or null) || ("fuchsia" == target."os" or null) || ("linux" == target."os" or null))));
+          }
+          {
+            name = "untrusted";
+            packageId = "untrusted 0.7.1";
+          }
+          {
+            name = "web-sys";
+            packageId = "web-sys";
+            usesDefaultFeatures = false;
+            target = { target, features }: (("wasm32" == target."arch" or null) && ("unknown" == target."vendor" or null) && ("unknown" == target."os" or null) && ("" == target."env" or null));
+            features = [ "Crypto" "Window" ];
+          }
+          {
+            name = "winapi";
+            packageId = "winapi";
+            usesDefaultFeatures = false;
+            target = { target, features }: ("windows" == target."os" or null);
+            features = [ "ntsecapi" "wtypesbase" ];
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "cc";
+            packageId = "cc";
+            usesDefaultFeatures = false;
+          }
+        ];
+        devDependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+            usesDefaultFeatures = false;
+            target = { target, features }: ((target."unix" or false) || (target."windows" or false));
+          }
+        ];
+        features = {
+          "default" = [ "alloc" "dev_urandom_fallback" ];
+          "dev_urandom_fallback" = [ "once_cell" ];
+          "once_cell" = [ "dep:once_cell" ];
+          "std" = [ "alloc" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "dev_urandom_fallback" "once_cell" ];
+      };
+      "ring 0.17.5" = rec {
+        crateName = "ring";
+        version = "0.17.5";
+        edition = "2021";
+        links = "ring_core_0_17_5";
+        sha256 = "02sd768l7594rm3jw048z7kkml7zcyw4ir62p6cxirap8wq0a0pv";
+        authors = [
+          "Brian Smith <brian@briansmith.org>"
+        ];
+        dependencies = [
+          {
+            name = "getrandom";
+            packageId = "getrandom";
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            usesDefaultFeatures = false;
+            target = { target, features }: (("android" == target."os" or null) || ("linux" == target."os" or null));
+          }
+          {
+            name = "spin";
+            packageId = "spin 0.9.8";
+            usesDefaultFeatures = false;
+            target = { target, features }: (("x86" == target."arch" or null) || ("x86_64" == target."arch" or null) || ((("aarch64" == target."arch" or null) || ("arm" == target."arch" or null)) && (("android" == target."os" or null) || ("fuchsia" == target."os" or null) || ("linux" == target."os" or null) || ("windows" == target."os" or null))));
+            features = [ "once" ];
+          }
+          {
+            name = "untrusted";
+            packageId = "untrusted 0.9.0";
+          }
+          {
+            name = "windows-sys";
+            packageId = "windows-sys 0.48.0";
+            target = { target, features }: (("aarch64" == target."arch" or null) && ("windows" == target."os" or null));
+            features = [ "Win32_Foundation" "Win32_System_Threading" ];
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "cc";
+            packageId = "cc";
+            usesDefaultFeatures = false;
+          }
+        ];
+        devDependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+            usesDefaultFeatures = false;
+            target = { target, features }: ((target."unix" or false) || (target."windows" or false) || ("wasi" == target."os" or null));
+          }
+        ];
+        features = {
+          "default" = [ "alloc" "dev_urandom_fallback" ];
+          "std" = [ "alloc" ];
+          "wasm32_unknown_unknown_js" = [ "getrandom/js" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "dev_urandom_fallback" ];
+      };
+      "rusoto_core" = rec {
+        crateName = "rusoto_core";
+        version = "0.48.0";
+        edition = "2018";
+        sha256 = "18ig9x4n68cgfvhzkyhl9w2qlhk945xczbb9c8r52dd79ss0vcqx";
+        authors = [
+          "Anthony DiMarco <ocramida@gmail.com>"
+          "Jimmy Cuadra <jimmy@jimmycuadra.com>"
+          "Matthew Mayer <matthewkmayer@gmail.com>"
+          "Nikita Pekin <contact@nikitapek.in>"
+        ];
+        dependencies = [
+          {
+            name = "async-trait";
+            packageId = "async-trait";
+          }
+          {
+            name = "base64";
+            packageId = "base64 0.13.1";
+          }
+          {
+            name = "bytes";
+            packageId = "bytes";
+          }
+          {
+            name = "crc32fast";
+            packageId = "crc32fast";
+          }
+          {
+            name = "futures";
+            packageId = "futures";
+          }
+          {
+            name = "http";
+            packageId = "http";
+          }
+          {
+            name = "hyper";
+            packageId = "hyper";
+            features = [ "client" "http1" "http2" "tcp" ];
+          }
+          {
+            name = "hyper-rustls";
+            packageId = "hyper-rustls";
+            optional = true;
+            usesDefaultFeatures = false;
+            features = [ "http1" "http2" "tls12" "logging" ];
+          }
+          {
+            name = "lazy_static";
+            packageId = "lazy_static";
+          }
+          {
+            name = "log";
+            packageId = "log";
+          }
+          {
+            name = "rusoto_credential";
+            packageId = "rusoto_credential";
+          }
+          {
+            name = "rusoto_signature";
+            packageId = "rusoto_signature";
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+            features = [ "derive" ];
+          }
+          {
+            name = "serde_json";
+            packageId = "serde_json";
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "time" "io-util" ];
+          }
+          {
+            name = "xml-rs";
+            packageId = "xml-rs";
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "rustc_version";
+            packageId = "rustc_version";
+          }
+        ];
+        devDependencies = [
+          {
+            name = "serde_json";
+            packageId = "serde_json";
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "macros" ];
+          }
+        ];
+        features = {
+          "default" = [ "native-tls" ];
+          "encoding" = [ "flate2" ];
+          "flate2" = [ "dep:flate2" ];
+          "hyper-rustls" = [ "dep:hyper-rustls" ];
+          "hyper-tls" = [ "dep:hyper-tls" ];
+          "native-tls" = [ "hyper-tls" ];
+          "nightly-testing" = [ "rusoto_credential/nightly-testing" ];
+          "rustls" = [ "hyper-rustls/native-tokio" ];
+          "rustls-webpki" = [ "hyper-rustls/webpki-tokio" ];
+        };
+        resolvedDefaultFeatures = [ "hyper-rustls" "rustls" ];
+      };
+      "rusoto_credential" = rec {
+        crateName = "rusoto_credential";
+        version = "0.48.0";
+        edition = "2018";
+        sha256 = "019dq3aq6hnfg4xvxdfsnrba08dwvciz0km4nr3n1basvc9nq2pf";
+        authors = [
+          "Anthony DiMarco <ocramida@gmail.com>"
+          "Jimmy Cuadra <jimmy@jimmycuadra.com>"
+          "Matthew Mayer <matthewkmayer@gmail.com>"
+          "Nikita Pekin <contact@nikitapek.in>"
+        ];
+        dependencies = [
+          {
+            name = "async-trait";
+            packageId = "async-trait";
+          }
+          {
+            name = "chrono";
+            packageId = "chrono";
+            usesDefaultFeatures = false;
+            features = [ "clock" "serde" ];
+          }
+          {
+            name = "dirs-next";
+            packageId = "dirs-next";
+          }
+          {
+            name = "futures";
+            packageId = "futures";
+          }
+          {
+            name = "hyper";
+            packageId = "hyper";
+            features = [ "client" "http1" "tcp" "stream" ];
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+            features = [ "derive" ];
+          }
+          {
+            name = "serde_json";
+            packageId = "serde_json";
+          }
+          {
+            name = "shlex";
+            packageId = "shlex";
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "process" "sync" "time" ];
+          }
+          {
+            name = "zeroize";
+            packageId = "zeroize";
+          }
+        ];
+        devDependencies = [
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "macros" "rt-multi-thread" ];
+          }
+        ];
+        features = { };
+      };
+      "rusoto_s3" = rec {
+        crateName = "rusoto_s3";
+        version = "0.48.0";
+        edition = "2018";
+        sha256 = "0kdiqljcq1wg26mp0vnn2wwjj0slxya63mhjnjqgc49l31vldbks";
+        authors = [
+          "Anthony DiMarco <ocramida@gmail.com>"
+          "Jimmy Cuadra <jimmy@jimmycuadra.com>"
+          "Matthew Mayer <matthewkmayer@gmail.com>"
+          "Nikita Pekin <contact@nikitapek.in>"
+        ];
+        dependencies = [
+          {
+            name = "async-trait";
+            packageId = "async-trait";
+          }
+          {
+            name = "bytes";
+            packageId = "bytes";
+          }
+          {
+            name = "futures";
+            packageId = "futures";
+          }
+          {
+            name = "rusoto_core";
+            packageId = "rusoto_core";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "xml-rs";
+            packageId = "xml-rs";
+          }
+        ];
+        features = {
+          "default" = [ "native-tls" ];
+          "deserialize_structs" = [ "bytes/serde" "serde" "serde_derive" ];
+          "native-tls" = [ "rusoto_core/native-tls" ];
+          "rustls" = [ "rusoto_core/rustls" ];
+          "serde" = [ "dep:serde" ];
+          "serde_derive" = [ "dep:serde_derive" ];
+          "serialize_structs" = [ "bytes/serde" "serde" "serde_derive" ];
+        };
+        resolvedDefaultFeatures = [ "rustls" ];
+      };
+      "rusoto_signature" = rec {
+        crateName = "rusoto_signature";
+        version = "0.48.0";
+        edition = "2018";
+        sha256 = "0wjjn3n3a01xxc1kdwqkrbw6zkgc4w8ia6r93s9lfj4b3i4rbbm5";
+        authors = [
+          "Anthony DiMarco <ocramida@gmail.com>"
+          "Jimmy Cuadra <jimmy@jimmycuadra.com>"
+          "Matthew Mayer <matthewkmayer@gmail.com>"
+          "Nikita Pekin <contact@nikitapek.in>"
+        ];
+        dependencies = [
+          {
+            name = "base64";
+            packageId = "base64 0.13.1";
+          }
+          {
+            name = "bytes";
+            packageId = "bytes";
+          }
+          {
+            name = "chrono";
+            packageId = "chrono";
+            usesDefaultFeatures = false;
+            features = [ "clock" ];
+          }
+          {
+            name = "digest";
+            packageId = "digest 0.9.0";
+          }
+          {
+            name = "futures";
+            packageId = "futures";
+          }
+          {
+            name = "hex";
+            packageId = "hex";
+          }
+          {
+            name = "hmac";
+            packageId = "hmac";
+          }
+          {
+            name = "http";
+            packageId = "http";
+          }
+          {
+            name = "hyper";
+            packageId = "hyper";
+            features = [ "stream" ];
+          }
+          {
+            name = "log";
+            packageId = "log";
+          }
+          {
+            name = "md-5";
+            packageId = "md-5";
+          }
+          {
+            name = "percent-encoding";
+            packageId = "percent-encoding";
+          }
+          {
+            name = "pin-project-lite";
+            packageId = "pin-project-lite";
+          }
+          {
+            name = "rusoto_credential";
+            packageId = "rusoto_credential";
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+          }
+          {
+            name = "sha2";
+            packageId = "sha2 0.9.9";
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "macros" "rt-multi-thread" ];
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "rustc_version";
+            packageId = "rustc_version";
+          }
+        ];
+        devDependencies = [
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "io-util" ];
+          }
+        ];
+
+      };
+      "rustc-demangle" = rec {
+        crateName = "rustc-demangle";
+        version = "0.1.23";
+        edition = "2015";
+        sha256 = "0xnbk2bmyzshacjm2g1kd4zzv2y2az14bw3sjccq5qkpmsfvn9nn";
+        authors = [
+          "Alex Crichton <alex@alexcrichton.com>"
+        ];
+        features = {
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "core" = [ "dep:core" ];
+          "rustc-dep-of-std" = [ "core" "compiler_builtins" ];
+        };
+      };
+      "rustc_version" = rec {
+        crateName = "rustc_version";
+        version = "0.4.0";
+        edition = "2018";
+        sha256 = "0rpk9rcdk405xhbmgclsh4pai0svn49x35aggl4nhbkd4a2zb85z";
+        authors = [
+          "Dirkjan Ochtman <dirkjan@ochtman.nl>"
+          "Marvin Löbel <loebel.marvin@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "semver";
+            packageId = "semver";
+          }
+        ];
+
+      };
+      "rustix" = rec {
+        crateName = "rustix";
+        version = "0.38.24";
+        edition = "2021";
+        sha256 = "0d72f5q2csk5mff87jrzlfgpxv44c2f8s0m183f9r920qgb83ncs";
+        authors = [
+          "Dan Gohman <dev@sunfishcode.online>"
+          "Jakub Konka <kubkon@jakubkonka.com>"
+        ];
+        dependencies = [
+          {
+            name = "bitflags";
+            packageId = "bitflags 2.4.1";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "errno";
+            packageId = "errno";
+            rename = "libc_errno";
+            optional = true;
+            usesDefaultFeatures = false;
+            target = { target, features }: ((!(target."rustix_use_libc" or false)) && (!(target."miri" or false)) && ("linux" == target."os" or null) && ("little" == target."endian" or null) && (("arm" == target."arch" or null) || (("aarch64" == target."arch" or null) && ("64" == target."pointer_width" or null)) || ("riscv64" == target."arch" or null) || ((target."rustix_use_experimental_asm" or false) && ("powerpc64" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips32r6" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips64" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips64r6" == target."arch" or null)) || ("x86" == target."arch" or null) || (("x86_64" == target."arch" or null) && ("64" == target."pointer_width" or null))));
+          }
+          {
+            name = "errno";
+            packageId = "errno";
+            rename = "libc_errno";
+            usesDefaultFeatures = false;
+            target = { target, features }: ((!(target."windows" or false)) && ((target."rustix_use_libc" or false) || (target."miri" or false) || (!(("linux" == target."os" or null) && ("little" == target."endian" or null) && (("arm" == target."arch" or null) || (("aarch64" == target."arch" or null) && ("64" == target."pointer_width" or null)) || ("riscv64" == target."arch" or null) || ((target."rustix_use_experimental_asm" or false) && ("powerpc64" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips32r6" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips64" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips64r6" == target."arch" or null)) || ("x86" == target."arch" or null) || (("x86_64" == target."arch" or null) && ("64" == target."pointer_width" or null)))))));
+          }
+          {
+            name = "errno";
+            packageId = "errno";
+            rename = "libc_errno";
+            usesDefaultFeatures = false;
+            target = { target, features }: (target."windows" or false);
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            optional = true;
+            usesDefaultFeatures = false;
+            target = { target, features }: ((!(target."rustix_use_libc" or false)) && (!(target."miri" or false)) && ("linux" == target."os" or null) && ("little" == target."endian" or null) && (("arm" == target."arch" or null) || (("aarch64" == target."arch" or null) && ("64" == target."pointer_width" or null)) || ("riscv64" == target."arch" or null) || ((target."rustix_use_experimental_asm" or false) && ("powerpc64" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips32r6" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips64" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips64r6" == target."arch" or null)) || ("x86" == target."arch" or null) || (("x86_64" == target."arch" or null) && ("64" == target."pointer_width" or null))));
+            features = [ "extra_traits" ];
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            usesDefaultFeatures = false;
+            target = { target, features }: ((!(target."windows" or false)) && ((target."rustix_use_libc" or false) || (target."miri" or false) || (!(("linux" == target."os" or null) && ("little" == target."endian" or null) && (("arm" == target."arch" or null) || (("aarch64" == target."arch" or null) && ("64" == target."pointer_width" or null)) || ("riscv64" == target."arch" or null) || ((target."rustix_use_experimental_asm" or false) && ("powerpc64" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips32r6" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips64" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips64r6" == target."arch" or null)) || ("x86" == target."arch" or null) || (("x86_64" == target."arch" or null) && ("64" == target."pointer_width" or null)))))));
+            features = [ "extra_traits" ];
+          }
+          {
+            name = "linux-raw-sys";
+            packageId = "linux-raw-sys";
+            usesDefaultFeatures = false;
+            target = { target, features }: ((("android" == target."os" or null) || ("linux" == target."os" or null)) && ((target."rustix_use_libc" or false) || (target."miri" or false) || (!(("linux" == target."os" or null) && ("little" == target."endian" or null) && (("arm" == target."arch" or null) || (("aarch64" == target."arch" or null) && ("64" == target."pointer_width" or null)) || ("riscv64" == target."arch" or null) || ((target."rustix_use_experimental_asm" or false) && ("powerpc64" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips32r6" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips64" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips64r6" == target."arch" or null)) || ("x86" == target."arch" or null) || (("x86_64" == target."arch" or null) && ("64" == target."pointer_width" or null)))))));
+            features = [ "general" "ioctl" "no_std" ];
+          }
+          {
+            name = "linux-raw-sys";
+            packageId = "linux-raw-sys";
+            usesDefaultFeatures = false;
+            target = { target, features }: ((!(target."rustix_use_libc" or false)) && (!(target."miri" or false)) && ("linux" == target."os" or null) && ("little" == target."endian" or null) && (("arm" == target."arch" or null) || (("aarch64" == target."arch" or null) && ("64" == target."pointer_width" or null)) || ("riscv64" == target."arch" or null) || ((target."rustix_use_experimental_asm" or false) && ("powerpc64" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips32r6" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips64" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips64r6" == target."arch" or null)) || ("x86" == target."arch" or null) || (("x86_64" == target."arch" or null) && ("64" == target."pointer_width" or null))));
+            features = [ "general" "errno" "ioctl" "no_std" "elf" ];
+          }
+          {
+            name = "windows-sys";
+            packageId = "windows-sys 0.48.0";
+            target = { target, features }: (target."windows" or false);
+            features = [ "Win32_Foundation" "Win32_Networking_WinSock" "Win32_NetworkManagement_IpHelper" "Win32_System_Threading" ];
+          }
+        ];
+        devDependencies = [
+          {
+            name = "errno";
+            packageId = "errno";
+            rename = "libc_errno";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+          }
+        ];
+        features = {
+          "all-apis" = [ "event" "fs" "io_uring" "mm" "mount" "net" "param" "pipe" "process" "procfs" "pty" "rand" "runtime" "shm" "stdio" "system" "termios" "thread" "time" ];
+          "default" = [ "std" "use-libc-auxv" ];
+          "io_uring" = [ "event" "fs" "net" "linux-raw-sys/io_uring" ];
+          "itoa" = [ "dep:itoa" ];
+          "libc" = [ "dep:libc" ];
+          "libc_errno" = [ "dep:libc_errno" ];
+          "linux_latest" = [ "linux_4_11" ];
+          "net" = [ "linux-raw-sys/net" "linux-raw-sys/netlink" "linux-raw-sys/if_ether" ];
+          "once_cell" = [ "dep:once_cell" ];
+          "param" = [ "fs" ];
+          "process" = [ "linux-raw-sys/prctl" ];
+          "procfs" = [ "once_cell" "itoa" "fs" ];
+          "pty" = [ "itoa" "fs" ];
+          "runtime" = [ "linux-raw-sys/prctl" ];
+          "rustc-dep-of-std" = [ "dep:core" "dep:alloc" "dep:compiler_builtins" "linux-raw-sys/rustc-dep-of-std" "bitflags/rustc-dep-of-std" "compiler_builtins?/rustc-dep-of-std" ];
+          "shm" = [ "fs" ];
+          "std" = [ "bitflags/std" "alloc" "libc?/std" "libc_errno?/std" ];
+          "system" = [ "linux-raw-sys/system" ];
+          "thread" = [ "linux-raw-sys/prctl" ];
+          "use-libc" = [ "libc_errno" "libc" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "fs" "std" "use-libc-auxv" ];
+      };
+      "rustls" = rec {
+        crateName = "rustls";
+        version = "0.20.9";
+        edition = "2018";
+        sha256 = "16byazb8jfr06kgbijy92bdk0ila806g6a00a6l9x64mqpgf700v";
+        dependencies = [
+          {
+            name = "log";
+            packageId = "log";
+            optional = true;
+          }
+          {
+            name = "ring";
+            packageId = "ring 0.16.20";
+          }
+          {
+            name = "sct";
+            packageId = "sct";
+          }
+          {
+            name = "webpki";
+            packageId = "webpki";
+            features = [ "alloc" "std" ];
+          }
+        ];
+        devDependencies = [
+          {
+            name = "log";
+            packageId = "log";
+          }
+        ];
+        features = {
+          "default" = [ "logging" "tls12" ];
+          "log" = [ "dep:log" ];
+          "logging" = [ "log" ];
+          "read_buf" = [ "rustversion" ];
+          "rustversion" = [ "dep:rustversion" ];
+        };
+        resolvedDefaultFeatures = [ "log" "logging" "tls12" ];
+      };
+      "rustls-native-certs" = rec {
+        crateName = "rustls-native-certs";
+        version = "0.6.3";
+        edition = "2021";
+        sha256 = "007zind70rd5rfsrkdcfm8vn09j8sg02phg9334kark6rdscxam9";
+        dependencies = [
+          {
+            name = "openssl-probe";
+            packageId = "openssl-probe";
+            target = { target, features }: ((target."unix" or false) && (!("macos" == target."os" or null)));
+          }
+          {
+            name = "rustls-pemfile";
+            packageId = "rustls-pemfile";
+          }
+          {
+            name = "schannel";
+            packageId = "schannel";
+            target = { target, features }: (target."windows" or false);
+          }
+          {
+            name = "security-framework";
+            packageId = "security-framework";
+            target = { target, features }: ("macos" == target."os" or null);
+          }
+        ];
+
+      };
+      "rustls-pemfile" = rec {
+        crateName = "rustls-pemfile";
+        version = "1.0.4";
+        edition = "2018";
+        sha256 = "1324n5bcns0rnw6vywr5agff3rwfvzphi7rmbyzwnv6glkhclx0w";
+        dependencies = [
+          {
+            name = "base64";
+            packageId = "base64 0.21.5";
+          }
+        ];
+
+      };
+      "rustversion" = rec {
+        crateName = "rustversion";
+        version = "1.0.14";
+        edition = "2018";
+        sha256 = "1x1pz1yynk5xzzrazk2svmidj69jhz89dz5vrc28sixl20x1iz3z";
+        procMacro = true;
+        build = "build/build.rs";
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+
+      };
+      "ryu" = rec {
+        crateName = "ryu";
+        version = "1.0.15";
+        edition = "2018";
+        sha256 = "0hfphpn1xnpzxwj8qg916ga1lyc33lc03lnf1gb3wwpglj6wrm0s";
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        features = {
+          "no-panic" = [ "dep:no-panic" ];
+        };
+      };
+      "schannel" = rec {
+        crateName = "schannel";
+        version = "0.1.22";
+        edition = "2018";
+        sha256 = "126zy5jb95fc5hvzyjwiq6lc81r08rdcn6affn00ispp9jzk6dqc";
+        authors = [
+          "Steven Fackler <sfackler@gmail.com>"
+          "Steffen Butzer <steffen.butzer@outlook.com>"
+        ];
+        dependencies = [
+          {
+            name = "windows-sys";
+            packageId = "windows-sys 0.48.0";
+            features = [ "Win32_Foundation" "Win32_Security_Cryptography" "Win32_Security_Authentication_Identity" "Win32_Security_Credentials" "Win32_System_Memory" ];
+          }
+        ];
+        devDependencies = [
+          {
+            name = "windows-sys";
+            packageId = "windows-sys 0.48.0";
+            features = [ "Win32_System_SystemInformation" "Win32_System_Time" ];
+          }
+        ];
+
+      };
+      "scopeguard" = rec {
+        crateName = "scopeguard";
+        version = "1.2.0";
+        edition = "2015";
+        sha256 = "0jcz9sd47zlsgcnm1hdw0664krxwb5gczlif4qngj2aif8vky54l";
+        authors = [
+          "bluss"
+        ];
+        features = {
+          "default" = [ "use_std" ];
+        };
+      };
+      "sct" = rec {
+        crateName = "sct";
+        version = "0.7.1";
+        edition = "2021";
+        sha256 = "056lmi2xkzdg1dbai6ha3n57s18cbip4pnmpdhyljli3m99n216s";
+        authors = [
+          "Joseph Birr-Pixton <jpixton@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "ring";
+            packageId = "ring 0.17.5";
+          }
+          {
+            name = "untrusted";
+            packageId = "untrusted 0.9.0";
+          }
+        ];
+
+      };
+      "security-framework" = rec {
+        crateName = "security-framework";
+        version = "2.9.2";
+        edition = "2021";
+        sha256 = "1pplxk15s5yxvi2m1sz5xfmjibp96cscdcl432w9jzbk0frlzdh5";
+        authors = [
+          "Steven Fackler <sfackler@gmail.com>"
+          "Kornel <kornel@geekhood.net>"
+        ];
+        dependencies = [
+          {
+            name = "bitflags";
+            packageId = "bitflags 1.3.2";
+          }
+          {
+            name = "core-foundation";
+            packageId = "core-foundation";
+          }
+          {
+            name = "core-foundation-sys";
+            packageId = "core-foundation-sys";
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+          }
+          {
+            name = "security-framework-sys";
+            packageId = "security-framework-sys";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "OSX_10_10" = [ "OSX_10_9" "security-framework-sys/OSX_10_10" ];
+          "OSX_10_11" = [ "OSX_10_10" "security-framework-sys/OSX_10_11" ];
+          "OSX_10_12" = [ "OSX_10_11" "security-framework-sys/OSX_10_12" ];
+          "OSX_10_13" = [ "OSX_10_12" "security-framework-sys/OSX_10_13" "alpn" "session-tickets" "serial-number-bigint" ];
+          "OSX_10_14" = [ "OSX_10_13" "security-framework-sys/OSX_10_14" ];
+          "OSX_10_15" = [ "OSX_10_14" "security-framework-sys/OSX_10_15" ];
+          "OSX_10_9" = [ "security-framework-sys/OSX_10_9" ];
+          "default" = [ "OSX_10_9" ];
+          "log" = [ "dep:log" ];
+          "serial-number-bigint" = [ "dep:num-bigint" ];
+        };
+        resolvedDefaultFeatures = [ "OSX_10_9" "default" ];
+      };
+      "security-framework-sys" = rec {
+        crateName = "security-framework-sys";
+        version = "2.9.1";
+        edition = "2021";
+        sha256 = "0yhciwlsy9dh0ps1gw3197kvyqx1bvc4knrhiznhid6kax196cp9";
+        authors = [
+          "Steven Fackler <sfackler@gmail.com>"
+          "Kornel <kornel@geekhood.net>"
+        ];
+        dependencies = [
+          {
+            name = "core-foundation-sys";
+            packageId = "core-foundation-sys";
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+          }
+        ];
+        features = {
+          "OSX_10_10" = [ "OSX_10_9" ];
+          "OSX_10_11" = [ "OSX_10_10" ];
+          "OSX_10_12" = [ "OSX_10_11" ];
+          "OSX_10_13" = [ "OSX_10_12" ];
+          "OSX_10_14" = [ "OSX_10_13" ];
+          "OSX_10_15" = [ "OSX_10_14" ];
+          "default" = [ "OSX_10_9" ];
+        };
+        resolvedDefaultFeatures = [ "OSX_10_9" ];
+      };
+      "semver" = rec {
+        crateName = "semver";
+        version = "1.0.20";
+        edition = "2018";
+        sha256 = "140hmbfa743hbmah1zjf07s8apavhvn04204qjigjiz5w6iscvw3";
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        features = {
+          "default" = [ "std" ];
+          "serde" = [ "dep:serde" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "seq-macro" = rec {
+        crateName = "seq-macro";
+        version = "0.3.5";
+        edition = "2018";
+        sha256 = "1d50kbaslrrd0374ivx15jg57f03y5xzil1wd2ajlvajzlkbzw53";
+        procMacro = true;
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+
+      };
+      "serde" = rec {
+        crateName = "serde";
+        version = "1.0.192";
+        edition = "2018";
+        sha256 = "00ghhaabyrnr2cn504lckyqzh3fwr8k7pxnhhardr1djhj2a18mw";
+        authors = [
+          "Erick Tryzelaar <erick.tryzelaar@gmail.com>"
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "serde_derive";
+            packageId = "serde_derive";
+            optional = true;
+          }
+          {
+            name = "serde_derive";
+            packageId = "serde_derive";
+            target = { target, features }: false;
+          }
+        ];
+        devDependencies = [
+          {
+            name = "serde_derive";
+            packageId = "serde_derive";
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "derive" = [ "serde_derive" ];
+          "serde_derive" = [ "dep:serde_derive" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "derive" "serde_derive" "std" ];
+      };
+      "serde_derive" = rec {
+        crateName = "serde_derive";
+        version = "1.0.192";
+        edition = "2015";
+        sha256 = "1hgvm47ffd748sx22z1da7mgcfjmpr60gqzkff0a9yn9przj1iyn";
+        procMacro = true;
+        authors = [
+          "Erick Tryzelaar <erick.tryzelaar@gmail.com>"
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn 2.0.39";
+          }
+        ];
+        features = { };
+        resolvedDefaultFeatures = [ "default" ];
+      };
+      "serde_json" = rec {
+        crateName = "serde_json";
+        version = "1.0.108";
+        edition = "2021";
+        sha256 = "0ssj59s7lpzqh1m50kfzlnrip0p0jg9lmhn4098i33a0mhz7w71x";
+        authors = [
+          "Erick Tryzelaar <erick.tryzelaar@gmail.com>"
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "itoa";
+            packageId = "itoa";
+          }
+          {
+            name = "ryu";
+            packageId = "ryu";
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+            usesDefaultFeatures = false;
+          }
+        ];
+        devDependencies = [
+          {
+            name = "serde";
+            packageId = "serde";
+            features = [ "derive" ];
+          }
+        ];
+        features = {
+          "alloc" = [ "serde/alloc" ];
+          "default" = [ "std" ];
+          "indexmap" = [ "dep:indexmap" ];
+          "preserve_order" = [ "indexmap" "std" ];
+          "std" = [ "serde/std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "sha2 0.10.8" = rec {
+        crateName = "sha2";
+        version = "0.10.8";
+        edition = "2018";
+        sha256 = "1j1x78zk9il95w9iv46dh9wm73r6xrgj32y6lzzw7bxws9dbfgbr";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        dependencies = [
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "cpufeatures";
+            packageId = "cpufeatures";
+            target = { target, features }: (("aarch64" == target."arch" or null) || ("x86_64" == target."arch" or null) || ("x86" == target."arch" or null));
+          }
+          {
+            name = "digest";
+            packageId = "digest 0.10.7";
+          }
+          {
+            name = "sha2-asm";
+            packageId = "sha2-asm";
+            optional = true;
+            target = { target, features }: (("aarch64" == target."arch" or null) || ("x86_64" == target."arch" or null) || ("x86" == target."arch" or null));
+          }
+        ];
+        devDependencies = [
+          {
+            name = "digest";
+            packageId = "digest 0.10.7";
+            features = [ "dev" ];
+          }
+        ];
+        features = {
+          "asm" = [ "sha2-asm" ];
+          "asm-aarch64" = [ "asm" ];
+          "default" = [ "std" ];
+          "oid" = [ "digest/oid" ];
+          "sha2-asm" = [ "dep:sha2-asm" ];
+          "std" = [ "digest/std" ];
+        };
+        resolvedDefaultFeatures = [ "asm" "default" "sha2-asm" "std" ];
+      };
+      "sha2 0.9.9" = rec {
+        crateName = "sha2";
+        version = "0.9.9";
+        edition = "2018";
+        sha256 = "006q2f0ar26xcjxqz8zsncfgz86zqa5dkwlwv03rhx1rpzhs2n2d";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        dependencies = [
+          {
+            name = "block-buffer";
+            packageId = "block-buffer 0.9.0";
+          }
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "cpufeatures";
+            packageId = "cpufeatures";
+            target = { target, features }: (("aarch64" == target."arch" or null) || ("x86_64" == target."arch" or null) || ("x86" == target."arch" or null));
+          }
+          {
+            name = "digest";
+            packageId = "digest 0.9.0";
+          }
+          {
+            name = "opaque-debug";
+            packageId = "opaque-debug";
+          }
+        ];
+        devDependencies = [
+          {
+            name = "digest";
+            packageId = "digest 0.9.0";
+            features = [ "dev" ];
+          }
+        ];
+        features = {
+          "asm" = [ "sha2-asm" ];
+          "asm-aarch64" = [ "asm" ];
+          "default" = [ "std" ];
+          "sha2-asm" = [ "dep:sha2-asm" ];
+          "std" = [ "digest/std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "sha2-asm" = rec {
+        crateName = "sha2-asm";
+        version = "0.6.3";
+        edition = "2018";
+        sha256 = "0kp480744vkwg3fqx98379nsdw1lzzzimd88v0qgpqqic03afyzj";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        buildDependencies = [
+          {
+            name = "cc";
+            packageId = "cc";
+          }
+        ];
+
+      };
+      "shlex" = rec {
+        crateName = "shlex";
+        version = "1.2.0";
+        edition = "2015";
+        sha256 = "1033pj9dyb76nm5yv597nnvj3zpvr2aw9rm5wy0gah3dk99f1km7";
+        authors = [
+          "comex <comexk@gmail.com>"
+          "Fenhl <fenhl@fenhl.net>"
+        ];
+        features = {
+          "default" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "signal-hook-registry" = rec {
+        crateName = "signal-hook-registry";
+        version = "1.4.1";
+        edition = "2015";
+        sha256 = "18crkkw5k82bvcx088xlf5g4n3772m24qhzgfan80nda7d3rn8nq";
+        authors = [
+          "Michal 'vorner' Vaner <vorner@vorner.cz>"
+          "Masaki Hara <ackie.h.gmai@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+          }
+        ];
+
+      };
+      "signature" = rec {
+        crateName = "signature";
+        version = "2.2.0";
+        edition = "2021";
+        sha256 = "1pi9hd5vqfr3q3k49k37z06p7gs5si0in32qia4mmr1dancr6m3p";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        dependencies = [
+          {
+            name = "rand_core";
+            packageId = "rand_core";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "derive" = [ "dep:derive" ];
+          "digest" = [ "dep:digest" ];
+          "rand_core" = [ "dep:rand_core" ];
+          "std" = [ "alloc" "rand_core?/std" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "std" ];
+      };
+      "simdutf8" = rec {
+        crateName = "simdutf8";
+        version = "0.1.4";
+        edition = "2018";
+        sha256 = "0fi6zvnldaw7g726wnm9vvpv4s89s5jsk7fgp3rg2l99amw64zzj";
+        authors = [
+          "Hans Kratz <hans@appfour.com>"
+        ];
+        features = {
+          "default" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "slab" = rec {
+        crateName = "slab";
+        version = "0.4.9";
+        edition = "2018";
+        sha256 = "0rxvsgir0qw5lkycrqgb1cxsvxzjv9bmx73bk5y42svnzfba94lg";
+        authors = [
+          "Carl Lerche <me@carllerche.com>"
+        ];
+        buildDependencies = [
+          {
+            name = "autocfg";
+            packageId = "autocfg";
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "serde" = [ "dep:serde" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "sled" = rec {
+        crateName = "sled";
+        version = "0.34.7";
+        edition = "2018";
+        sha256 = "0dcr2s7cylj5mb33ci3kpx7fz797jwvysnl5airrir9cgirv95kz";
+        authors = [
+          "Tyler Neely <t@jujit.su>"
+        ];
+        dependencies = [
+          {
+            name = "crc32fast";
+            packageId = "crc32fast";
+          }
+          {
+            name = "crossbeam-epoch";
+            packageId = "crossbeam-epoch";
+          }
+          {
+            name = "crossbeam-utils";
+            packageId = "crossbeam-utils";
+          }
+          {
+            name = "fs2";
+            packageId = "fs2";
+            target = { target, features }: (("linux" == target."os" or null) || ("macos" == target."os" or null) || ("windows" == target."os" or null));
+          }
+          {
+            name = "fxhash";
+            packageId = "fxhash";
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+          }
+          {
+            name = "log";
+            packageId = "log";
+          }
+          {
+            name = "parking_lot";
+            packageId = "parking_lot 0.11.2";
+          }
+        ];
+        devDependencies = [
+          {
+            name = "log";
+            packageId = "log";
+          }
+        ];
+        features = {
+          "backtrace" = [ "dep:backtrace" ];
+          "color-backtrace" = [ "dep:color-backtrace" ];
+          "compression" = [ "zstd" ];
+          "default" = [ "no_metrics" ];
+          "io_uring" = [ "rio" ];
+          "no_logs" = [ "log/max_level_off" ];
+          "pretty_backtrace" = [ "color-backtrace" ];
+          "rio" = [ "dep:rio" ];
+          "testing" = [ "event_log" "lock_free_delays" "compression" "failpoints" "backtrace" ];
+          "zstd" = [ "dep:zstd" ];
+        };
+        resolvedDefaultFeatures = [ "default" "no_metrics" ];
+      };
+      "smallvec" = rec {
+        crateName = "smallvec";
+        version = "1.11.2";
+        edition = "2018";
+        sha256 = "0w79x38f7c0np7hqfmzrif9zmn0avjvvm31b166zdk9d1aad1k2d";
+        authors = [
+          "The Servo Project Developers"
+        ];
+        features = {
+          "arbitrary" = [ "dep:arbitrary" ];
+          "const_new" = [ "const_generics" ];
+          "drain_keep_rest" = [ "drain_filter" ];
+          "serde" = [ "dep:serde" ];
+        };
+      };
+      "smartstring" = rec {
+        crateName = "smartstring";
+        version = "1.0.1";
+        edition = "2021";
+        sha256 = "0agf4x0jz79r30aqibyfjm1h9hrjdh0harcqcvb2vapv7rijrdrz";
+        authors = [
+          "Bodil Stokke <bodil@bodil.org>"
+        ];
+        dependencies = [
+          {
+            name = "static_assertions";
+            packageId = "static_assertions";
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "autocfg";
+            packageId = "autocfg";
+          }
+          {
+            name = "version_check";
+            packageId = "version_check";
+          }
+        ];
+        features = {
+          "arbitrary" = [ "dep:arbitrary" ];
+          "default" = [ "std" ];
+          "proptest" = [ "dep:proptest" ];
+          "serde" = [ "dep:serde" ];
+          "test" = [ "std" "arbitrary" "arbitrary/derive" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "snap" = rec {
+        crateName = "snap";
+        version = "1.1.0";
+        edition = "2018";
+        sha256 = "0c882cs4wbyi34nw8njpxa729gyi6sj71h8rj4ykbdvyxyv0m7sy";
+        authors = [
+          "Andrew Gallant <jamslam@gmail.com>"
+        ];
+
+      };
+      "socket2 0.4.10" = rec {
+        crateName = "socket2";
+        version = "0.4.10";
+        edition = "2018";
+        sha256 = "03ack54dxhgfifzsj14k7qa3r5c9wqy3v6mqhlim99cc03y1cycz";
+        authors = [
+          "Alex Crichton <alex@alexcrichton.com>"
+          "Thomas de Zeeuw <thomasdezeeuw@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (target."unix" or false);
+          }
+          {
+            name = "winapi";
+            packageId = "winapi";
+            target = { target, features }: (target."windows" or false);
+            features = [ "handleapi" "ws2ipdef" "ws2tcpip" ];
+          }
+        ];
+        features = { };
+        resolvedDefaultFeatures = [ "all" ];
+      };
+      "socket2 0.5.5" = rec {
+        crateName = "socket2";
+        version = "0.5.5";
+        edition = "2021";
+        sha256 = "1sgq315f1njky114ip7wcy83qlphv9qclprfjwvxcpfblmcsqpvv";
+        authors = [
+          "Alex Crichton <alex@alexcrichton.com>"
+          "Thomas de Zeeuw <thomasdezeeuw@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (target."unix" or false);
+          }
+          {
+            name = "windows-sys";
+            packageId = "windows-sys 0.48.0";
+            target = { target, features }: (target."windows" or false);
+            features = [ "Win32_Foundation" "Win32_Networking_WinSock" "Win32_System_IO" "Win32_System_Threading" "Win32_System_WindowsProgramming" ];
+          }
+        ];
+        features = { };
+        resolvedDefaultFeatures = [ "all" ];
+      };
+      "spin 0.5.2" = rec {
+        crateName = "spin";
+        version = "0.5.2";
+        edition = "2015";
+        sha256 = "0b84m6dbzrwf2kxylnw82d3dr8w06av7rfkr8s85fb5f43rwyqvf";
+        authors = [
+          "Mathijs van de Nes <git@mathijs.vd-nes.nl>"
+          "John Ericson <git@JohnEricson.me>"
+        ];
+
+      };
+      "spin 0.9.8" = rec {
+        crateName = "spin";
+        version = "0.9.8";
+        edition = "2015";
+        sha256 = "0rvam5r0p3a6qhc18scqpvpgb3ckzyqxpgdfyjnghh8ja7byi039";
+        authors = [
+          "Mathijs van de Nes <git@mathijs.vd-nes.nl>"
+          "John Ericson <git@JohnEricson.me>"
+          "Joshua Barretto <joshua.s.barretto@gmail.com>"
+        ];
+        features = {
+          "barrier" = [ "mutex" ];
+          "default" = [ "lock_api" "mutex" "spin_mutex" "rwlock" "once" "lazy" "barrier" ];
+          "fair_mutex" = [ "mutex" ];
+          "lazy" = [ "once" ];
+          "lock_api" = [ "lock_api_crate" ];
+          "lock_api_crate" = [ "dep:lock_api_crate" ];
+          "portable-atomic" = [ "dep:portable-atomic" ];
+          "portable_atomic" = [ "portable-atomic" ];
+          "spin_mutex" = [ "mutex" ];
+          "ticket_mutex" = [ "mutex" ];
+          "use_ticket_mutex" = [ "mutex" "ticket_mutex" ];
+        };
+        resolvedDefaultFeatures = [ "once" ];
+      };
+      "spki" = rec {
+        crateName = "spki";
+        version = "0.7.2";
+        edition = "2021";
+        sha256 = "0jhq00sv4w3psdi6li3vjjmspc6z2d9b1wc1srbljircy1p9j7lx";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        dependencies = [
+          {
+            name = "base64ct";
+            packageId = "base64ct";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "der";
+            packageId = "der";
+            features = [ "oid" ];
+          }
+        ];
+        features = {
+          "alloc" = [ "base64ct?/alloc" "der/alloc" ];
+          "arbitrary" = [ "std" "dep:arbitrary" "der/arbitrary" ];
+          "base64" = [ "dep:base64ct" ];
+          "fingerprint" = [ "sha2" ];
+          "pem" = [ "alloc" "der/pem" ];
+          "sha2" = [ "dep:sha2" ];
+          "std" = [ "der/std" "alloc" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "std" ];
+      };
+      "sqlparser" = rec {
+        crateName = "sqlparser";
+        version = "0.39.0";
+        edition = "2021";
+        sha256 = "1mrbqjdqr179qnhy43d0dnrl3yipsp4qyji5rc68j4fyrg14sfvl";
+        authors = [
+          "Andy Grove <andygrove73@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "log";
+            packageId = "log";
+          }
+        ];
+        features = {
+          "bigdecimal" = [ "dep:bigdecimal" ];
+          "default" = [ "std" ];
+          "json_example" = [ "serde_json" "serde" ];
+          "serde" = [ "dep:serde" ];
+          "serde_json" = [ "dep:serde_json" ];
+          "sqlparser_derive" = [ "dep:sqlparser_derive" ];
+          "visitor" = [ "sqlparser_derive" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "static_assertions" = rec {
+        crateName = "static_assertions";
+        version = "1.1.0";
+        edition = "2015";
+        sha256 = "0gsl6xmw10gvn3zs1rv99laj5ig7ylffnh71f9l34js4nr4r7sx2";
+        authors = [
+          "Nikolai Vazquez"
+        ];
+        features = { };
+      };
+      "streaming-decompression" = rec {
+        crateName = "streaming-decompression";
+        version = "0.1.2";
+        edition = "2018";
+        sha256 = "1wscqj3s30qknda778wf7z99mknk65p0h9hhs658l4pvkfqw6v5z";
+        dependencies = [
+          {
+            name = "fallible-streaming-iterator";
+            packageId = "fallible-streaming-iterator";
+          }
+        ];
+
+      };
+      "streaming-iterator" = rec {
+        crateName = "streaming-iterator";
+        version = "0.1.9";
+        edition = "2021";
+        sha256 = "0845zdv8qb7zwqzglpqc0830i43xh3fb6vqms155wz85qfvk28ib";
+        authors = [
+          "Steven Fackler <sfackler@gmail.com>"
+        ];
+        features = {
+          "std" = [ "alloc" ];
+        };
+      };
+      "strength_reduce" = rec {
+        crateName = "strength_reduce";
+        version = "0.2.4";
+        edition = "2015";
+        sha256 = "10jdq9dijjdkb20wg1dmwg447rnj37jbq0mwvbadvqi2gys5x2gy";
+        authors = [
+          "Elliott Mahler <join.together@gmail.com>"
+        ];
+
+      };
+      "strsim" = rec {
+        crateName = "strsim";
+        version = "0.10.0";
+        edition = "2015";
+        sha256 = "08s69r4rcrahwnickvi0kq49z524ci50capybln83mg6b473qivk";
+        authors = [
+          "Danny Guo <danny@dannyguo.com>"
+        ];
+
+      };
+      "strum_macros" = rec {
+        crateName = "strum_macros";
+        version = "0.25.3";
+        edition = "2018";
+        sha256 = "184y62g474zqb2f7n16x3ghvlyjbh50viw32p9w9l5lwmjlizp13";
+        procMacro = true;
+        authors = [
+          "Peter Glotfelty <peter.glotfelty@microsoft.com>"
+        ];
+        dependencies = [
+          {
+            name = "heck";
+            packageId = "heck";
+          }
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "rustversion";
+            packageId = "rustversion";
+          }
+          {
+            name = "syn";
+            packageId = "syn 2.0.39";
+            features = [ "parsing" "extra-traits" ];
+          }
+        ];
+
+      };
+      "subtle" = rec {
+        crateName = "subtle";
+        version = "2.4.1";
+        edition = "2015";
+        sha256 = "00b6jzh9gzb0h9n25g06nqr90z3xzqppfhhb260s1hjhh4pg7pkb";
+        authors = [
+          "Isis Lovecruft <isis@patternsinthevoid.net>"
+          "Henry de Valence <hdevalence@hdevalence.ca>"
+        ];
+        features = {
+          "default" = [ "std" "i128" ];
+        };
+      };
+      "syn 1.0.109" = rec {
+        crateName = "syn";
+        version = "1.0.109";
+        edition = "2018";
+        sha256 = "0ds2if4600bd59wsv7jjgfkayfzy3hnazs394kz6zdkmna8l3dkj";
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "unicode-ident";
+            packageId = "unicode-ident";
+          }
+        ];
+        features = {
+          "default" = [ "derive" "parsing" "printing" "clone-impls" "proc-macro" ];
+          "printing" = [ "quote" ];
+          "proc-macro" = [ "proc-macro2/proc-macro" "quote/proc-macro" ];
+          "quote" = [ "dep:quote" ];
+          "test" = [ "syn-test-suite/all-features" ];
+        };
+        resolvedDefaultFeatures = [ "clone-impls" "default" "derive" "extra-traits" "full" "parsing" "printing" "proc-macro" "quote" "visit-mut" ];
+      };
+      "syn 2.0.39" = rec {
+        crateName = "syn";
+        version = "2.0.39";
+        edition = "2021";
+        sha256 = "0ymyhxnk1yi4pzf72qk3lrdm9lgjwcrcwci0hhz5vx7wya88prr3";
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "unicode-ident";
+            packageId = "unicode-ident";
+          }
+        ];
+        features = {
+          "default" = [ "derive" "parsing" "printing" "clone-impls" "proc-macro" ];
+          "printing" = [ "quote" ];
+          "proc-macro" = [ "proc-macro2/proc-macro" "quote/proc-macro" ];
+          "quote" = [ "dep:quote" ];
+          "test" = [ "syn-test-suite/all-features" ];
+        };
+        resolvedDefaultFeatures = [ "clone-impls" "default" "derive" "extra-traits" "full" "parsing" "printing" "proc-macro" "quote" "visit" "visit-mut" ];
+      };
+      "sysinfo" = rec {
+        crateName = "sysinfo";
+        version = "0.29.10";
+        edition = "2018";
+        sha256 = "19cbs7d7fcq8cpfpr94n68h04d02lab8xg76j6la7b90shad260a";
+        authors = [
+          "Guillaume Gomez <guillaume1.gomez@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "core-foundation-sys";
+            packageId = "core-foundation-sys";
+            target = { target, features }: (("macos" == target."os" or null) || ("ios" == target."os" or null));
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (!(("unknown" == target."os" or null) || ("wasm32" == target."arch" or null)));
+          }
+          {
+            name = "ntapi";
+            packageId = "ntapi";
+            target = { target, features }: (target."windows" or false);
+          }
+          {
+            name = "once_cell";
+            packageId = "once_cell";
+            target = { target, features }: ((target."windows" or false) || ("linux" == target."os" or null) || ("android" == target."os" or null));
+          }
+          {
+            name = "winapi";
+            packageId = "winapi";
+            target = { target, features }: (target."windows" or false);
+            features = [ "errhandlingapi" "fileapi" "handleapi" "heapapi" "ifdef" "ioapiset" "minwindef" "pdh" "psapi" "synchapi" "sysinfoapi" "winbase" "winerror" "winioctl" "winnt" "oleauto" "wbemcli" "rpcdce" "combaseapi" "objidl" "powerbase" "netioapi" "lmcons" "lmaccess" "lmapibuf" "memoryapi" "ntlsa" "securitybaseapi" "shellapi" "std" "iphlpapi" "winsock2" "sddl" ];
+          }
+        ];
+        features = {
+          "apple-app-store" = [ "apple-sandbox" ];
+          "debug" = [ "libc/extra_traits" ];
+          "default" = [ "multithread" ];
+          "multithread" = [ "rayon" ];
+          "rayon" = [ "dep:rayon" ];
+          "serde" = [ "dep:serde" ];
+        };
+      };
+      "target-features" = rec {
+        crateName = "target-features";
+        version = "0.1.5";
+        edition = "2021";
+        sha256 = "1gb974chm9aj8ifkyibylxkyb5an4bf5y8dxb18pqmck698gmdfg";
+        authors = [
+          "Caleb Zulawski <caleb.zulawski@gmail.com>"
+        ];
+
+      };
+      "tempfile" = rec {
+        crateName = "tempfile";
+        version = "3.8.1";
+        edition = "2018";
+        sha256 = "1r88v07zdafzf46y63vs39rmzwl4vqd4g2c5qarz9mqa8nnavwby";
+        authors = [
+          "Steven Allen <steven@stebalien.com>"
+          "The Rust Project Developers"
+          "Ashley Mannix <ashleymannix@live.com.au>"
+          "Jason White <me@jasonwhite.io>"
+        ];
+        dependencies = [
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "fastrand";
+            packageId = "fastrand";
+          }
+          {
+            name = "redox_syscall";
+            packageId = "redox_syscall 0.4.1";
+            target = { target, features }: ("redox" == target."os" or null);
+          }
+          {
+            name = "rustix";
+            packageId = "rustix";
+            target = { target, features }: ((target."unix" or false) || ("wasi" == target."os" or null));
+            features = [ "fs" ];
+          }
+          {
+            name = "windows-sys";
+            packageId = "windows-sys 0.48.0";
+            target = { target, features }: (target."windows" or false);
+            features = [ "Win32_Storage_FileSystem" "Win32_Foundation" ];
+          }
+        ];
+        features = { };
+      };
+      "thiserror" = rec {
+        crateName = "thiserror";
+        version = "1.0.50";
+        edition = "2021";
+        sha256 = "1ll2sfbrxks8jja161zh1pgm3yssr7aawdmaa2xmcwcsbh7j39zr";
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "thiserror-impl";
+            packageId = "thiserror-impl";
+          }
+        ];
+
+      };
+      "thiserror-impl" = rec {
+        crateName = "thiserror-impl";
+        version = "1.0.50";
+        edition = "2021";
+        sha256 = "1f0lmam4765sfnwr4b1n00y14vxh10g0311mkk0adr80pi02wsr6";
+        procMacro = true;
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn 2.0.39";
+          }
+        ];
+
+      };
+      "tokio" = rec {
+        crateName = "tokio";
+        version = "1.34.0";
+        edition = "2021";
+        sha256 = "1fgmssdga42a2hn9spm9dh1v9ajpcbs4r3svmzvk9s0iciv19h6h";
+        authors = [
+          "Tokio Contributors <team@tokio.rs>"
+        ];
+        dependencies = [
+          {
+            name = "backtrace";
+            packageId = "backtrace";
+            target = { target, features }: (target."tokio_taskdump" or false);
+          }
+          {
+            name = "bytes";
+            packageId = "bytes";
+            optional = true;
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            optional = true;
+            target = { target, features }: (target."unix" or false);
+          }
+          {
+            name = "mio";
+            packageId = "mio";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "num_cpus";
+            packageId = "num_cpus";
+            optional = true;
+          }
+          {
+            name = "parking_lot";
+            packageId = "parking_lot 0.12.1";
+            optional = true;
+          }
+          {
+            name = "pin-project-lite";
+            packageId = "pin-project-lite";
+          }
+          {
+            name = "signal-hook-registry";
+            packageId = "signal-hook-registry";
+            optional = true;
+            target = { target, features }: (target."unix" or false);
+          }
+          {
+            name = "socket2";
+            packageId = "socket2 0.5.5";
+            optional = true;
+            target = { target, features }: (!(builtins.elem "wasm" target."family"));
+            features = [ "all" ];
+          }
+          {
+            name = "tokio-macros";
+            packageId = "tokio-macros";
+            optional = true;
+          }
+          {
+            name = "windows-sys";
+            packageId = "windows-sys 0.48.0";
+            optional = true;
+            target = { target, features }: (target."windows" or false);
+          }
+        ];
+        devDependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (target."unix" or false);
+          }
+          {
+            name = "socket2";
+            packageId = "socket2 0.5.5";
+            target = { target, features }: (!(builtins.elem "wasm" target."family"));
+          }
+          {
+            name = "windows-sys";
+            packageId = "windows-sys 0.48.0";
+            target = { target, features }: (target."windows" or false);
+            features = [ "Win32_Foundation" "Win32_Security_Authorization" ];
+          }
+        ];
+        features = {
+          "bytes" = [ "dep:bytes" ];
+          "full" = [ "fs" "io-util" "io-std" "macros" "net" "parking_lot" "process" "rt" "rt-multi-thread" "signal" "sync" "time" ];
+          "io-util" = [ "bytes" ];
+          "libc" = [ "dep:libc" ];
+          "macros" = [ "tokio-macros" ];
+          "mio" = [ "dep:mio" ];
+          "net" = [ "libc" "mio/os-poll" "mio/os-ext" "mio/net" "socket2" "windows-sys/Win32_Foundation" "windows-sys/Win32_Security" "windows-sys/Win32_Storage_FileSystem" "windows-sys/Win32_System_Pipes" "windows-sys/Win32_System_SystemServices" ];
+          "num_cpus" = [ "dep:num_cpus" ];
+          "parking_lot" = [ "dep:parking_lot" ];
+          "process" = [ "bytes" "libc" "mio/os-poll" "mio/os-ext" "mio/net" "signal-hook-registry" "windows-sys/Win32_Foundation" "windows-sys/Win32_System_Threading" "windows-sys/Win32_System_WindowsProgramming" ];
+          "rt-multi-thread" = [ "num_cpus" "rt" ];
+          "signal" = [ "libc" "mio/os-poll" "mio/net" "mio/os-ext" "signal-hook-registry" "windows-sys/Win32_Foundation" "windows-sys/Win32_System_Console" ];
+          "signal-hook-registry" = [ "dep:signal-hook-registry" ];
+          "socket2" = [ "dep:socket2" ];
+          "test-util" = [ "rt" "sync" "time" ];
+          "tokio-macros" = [ "dep:tokio-macros" ];
+          "tracing" = [ "dep:tracing" ];
+          "windows-sys" = [ "dep:windows-sys" ];
+        };
+        resolvedDefaultFeatures = [ "bytes" "default" "fs" "full" "io-std" "io-util" "libc" "macros" "mio" "net" "num_cpus" "parking_lot" "process" "rt" "rt-multi-thread" "signal" "signal-hook-registry" "socket2" "sync" "time" "tokio-macros" "windows-sys" ];
+      };
+      "tokio-macros" = rec {
+        crateName = "tokio-macros";
+        version = "2.2.0";
+        edition = "2021";
+        sha256 = "0fwjy4vdx1h9pi4g2nml72wi0fr27b5m954p13ji9anyy8l1x2jv";
+        procMacro = true;
+        authors = [
+          "Tokio Contributors <team@tokio.rs>"
+        ];
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn 2.0.39";
+            features = [ "full" ];
+          }
+        ];
+
+      };
+      "tokio-rustls" = rec {
+        crateName = "tokio-rustls";
+        version = "0.23.4";
+        edition = "2018";
+        sha256 = "0nfsmmi8l1lgpbfy6079d5i13984djzcxrdr9jc06ghi0cwyhgn4";
+        authors = [
+          "quininer kel <quininer@live.com>"
+        ];
+        dependencies = [
+          {
+            name = "rustls";
+            packageId = "rustls";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+          }
+          {
+            name = "webpki";
+            packageId = "webpki";
+          }
+        ];
+        devDependencies = [
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "full" ];
+          }
+        ];
+        features = {
+          "dangerous_configuration" = [ "rustls/dangerous_configuration" ];
+          "default" = [ "logging" "tls12" ];
+          "logging" = [ "rustls/logging" ];
+          "tls12" = [ "rustls/tls12" ];
+        };
+        resolvedDefaultFeatures = [ "logging" "tls12" ];
+      };
+      "tokio-util" = rec {
+        crateName = "tokio-util";
+        version = "0.7.10";
+        edition = "2021";
+        sha256 = "058y6x4mf0fsqji9rfyb77qbfyc50y4pk2spqgj6xsyr693z66al";
+        authors = [
+          "Tokio Contributors <team@tokio.rs>"
+        ];
+        dependencies = [
+          {
+            name = "bytes";
+            packageId = "bytes";
+          }
+          {
+            name = "futures-core";
+            packageId = "futures-core";
+          }
+          {
+            name = "futures-sink";
+            packageId = "futures-sink";
+          }
+          {
+            name = "pin-project-lite";
+            packageId = "pin-project-lite";
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "sync" ];
+          }
+          {
+            name = "tracing";
+            packageId = "tracing";
+            optional = true;
+            usesDefaultFeatures = false;
+            features = [ "std" ];
+          }
+        ];
+        devDependencies = [
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "full" ];
+          }
+        ];
+        features = {
+          "__docs_rs" = [ "futures-util" ];
+          "codec" = [ "tracing" ];
+          "compat" = [ "futures-io" ];
+          "full" = [ "codec" "compat" "io-util" "time" "net" "rt" ];
+          "futures-io" = [ "dep:futures-io" ];
+          "futures-util" = [ "dep:futures-util" ];
+          "hashbrown" = [ "dep:hashbrown" ];
+          "io-util" = [ "io" "tokio/rt" "tokio/io-util" ];
+          "net" = [ "tokio/net" ];
+          "rt" = [ "tokio/rt" "tokio/sync" "futures-util" "hashbrown" ];
+          "slab" = [ "dep:slab" ];
+          "time" = [ "tokio/time" "slab" ];
+          "tracing" = [ "dep:tracing" ];
+        };
+        resolvedDefaultFeatures = [ "codec" "default" "io" "io-util" "tracing" ];
+      };
+      "tower-service" = rec {
+        crateName = "tower-service";
+        version = "0.3.2";
+        edition = "2018";
+        sha256 = "0lmfzmmvid2yp2l36mbavhmqgsvzqf7r2wiwz73ml4xmwaf1rg5n";
+        authors = [
+          "Tower Maintainers <team@tower-rs.com>"
+        ];
+
+      };
+      "tracing" = rec {
+        crateName = "tracing";
+        version = "0.1.40";
+        edition = "2018";
+        sha256 = "1vv48dac9zgj9650pg2b4d0j3w6f3x9gbggf43scq5hrlysklln3";
+        authors = [
+          "Eliza Weisman <eliza@buoyant.io>"
+          "Tokio Contributors <team@tokio.rs>"
+        ];
+        dependencies = [
+          {
+            name = "pin-project-lite";
+            packageId = "pin-project-lite";
+          }
+          {
+            name = "tracing-core";
+            packageId = "tracing-core";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "attributes" = [ "tracing-attributes" ];
+          "default" = [ "std" "attributes" ];
+          "log" = [ "dep:log" ];
+          "log-always" = [ "log" ];
+          "std" = [ "tracing-core/std" ];
+          "tracing-attributes" = [ "dep:tracing-attributes" ];
+          "valuable" = [ "tracing-core/valuable" ];
+        };
+        resolvedDefaultFeatures = [ "std" ];
+      };
+      "tracing-core" = rec {
+        crateName = "tracing-core";
+        version = "0.1.32";
+        edition = "2018";
+        sha256 = "0m5aglin3cdwxpvbg6kz0r9r0k31j48n0kcfwsp6l49z26k3svf0";
+        authors = [
+          "Tokio Contributors <team@tokio.rs>"
+        ];
+        dependencies = [
+          {
+            name = "once_cell";
+            packageId = "once_cell";
+            optional = true;
+          }
+        ];
+        features = {
+          "default" = [ "std" "valuable/std" ];
+          "once_cell" = [ "dep:once_cell" ];
+          "std" = [ "once_cell" ];
+          "valuable" = [ "dep:valuable" ];
+        };
+        resolvedDefaultFeatures = [ "once_cell" "std" ];
+      };
+      "try-lock" = rec {
+        crateName = "try-lock";
+        version = "0.2.4";
+        edition = "2015";
+        sha256 = "1vc15paa4zi06ixsxihwbvfn24d708nsyg1ncgqwcrn42byyqa1m";
+        authors = [
+          "Sean McArthur <sean@seanmonstar.com>"
+        ];
+
+      };
+      "typenum" = rec {
+        crateName = "typenum";
+        version = "1.17.0";
+        edition = "2018";
+        sha256 = "09dqxv69m9lj9zvv6xw5vxaqx15ps0vxyy5myg33i0kbqvq0pzs2";
+        build = "build/main.rs";
+        authors = [
+          "Paho Lurie-Gregg <paho@paholg.com>"
+          "Andre Bogus <bogusandre@gmail.com>"
+        ];
+        features = {
+          "scale-info" = [ "dep:scale-info" ];
+          "scale_info" = [ "scale-info/derive" ];
+        };
+      };
+      "unicode-ident" = rec {
+        crateName = "unicode-ident";
+        version = "1.0.12";
+        edition = "2018";
+        sha256 = "0jzf1znfpb2gx8nr8mvmyqs1crnv79l57nxnbiszc7xf7ynbjm1k";
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+
+      };
+      "unicode-width" = rec {
+        crateName = "unicode-width";
+        version = "0.1.11";
+        edition = "2015";
+        sha256 = "11ds4ydhg8g7l06rlmh712q41qsrd0j0h00n1jm74kww3kqk65z5";
+        authors = [
+          "kwantam <kwantam@gmail.com>"
+          "Manish Goregaokar <manishsmail@gmail.com>"
+        ];
+        features = {
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "core" = [ "dep:core" ];
+          "rustc-dep-of-std" = [ "std" "core" "compiler_builtins" ];
+          "std" = [ "dep:std" ];
+        };
+        resolvedDefaultFeatures = [ "default" ];
+      };
+      "untrusted 0.7.1" = rec {
+        crateName = "untrusted";
+        version = "0.7.1";
+        edition = "2018";
+        sha256 = "0jkbqaj9d3v5a91pp3wp9mffvng1nhycx6sh4qkdd9qyr62ccmm1";
+        libPath = "src/untrusted.rs";
+        authors = [
+          "Brian Smith <brian@briansmith.org>"
+        ];
+
+      };
+      "untrusted 0.9.0" = rec {
+        crateName = "untrusted";
+        version = "0.9.0";
+        edition = "2018";
+        sha256 = "1ha7ib98vkc538x0z60gfn0fc5whqdd85mb87dvisdcaifi6vjwf";
+        authors = [
+          "Brian Smith <brian@briansmith.org>"
+        ];
+
+      };
+      "utf8parse" = rec {
+        crateName = "utf8parse";
+        version = "0.2.1";
+        edition = "2018";
+        sha256 = "02ip1a0az0qmc2786vxk2nqwsgcwf17d3a38fkf0q7hrmwh9c6vi";
+        authors = [
+          "Joe Wilm <joe@jwilm.com>"
+          "Christian Duerr <contact@christianduerr.com>"
+        ];
+        features = { };
+        resolvedDefaultFeatures = [ "default" ];
+      };
+      "version_check" = rec {
+        crateName = "version_check";
+        version = "0.9.4";
+        edition = "2015";
+        sha256 = "0gs8grwdlgh0xq660d7wr80x14vxbizmd8dbp29p2pdncx8lp1s9";
+        authors = [
+          "Sergio Benitez <sb@sergio.bz>"
+        ];
+
+      };
+      "want" = rec {
+        crateName = "want";
+        version = "0.3.1";
+        edition = "2018";
+        sha256 = "03hbfrnvqqdchb5kgxyavb9jabwza0dmh2vw5kg0dq8rxl57d9xz";
+        authors = [
+          "Sean McArthur <sean@seanmonstar.com>"
+        ];
+        dependencies = [
+          {
+            name = "try-lock";
+            packageId = "try-lock";
+          }
+        ];
+
+      };
+      "wasi" = rec {
+        crateName = "wasi";
+        version = "0.11.0+wasi-snapshot-preview1";
+        edition = "2018";
+        sha256 = "08z4hxwkpdpalxjps1ai9y7ihin26y9f476i53dv98v45gkqg3cw";
+        authors = [
+          "The Cranelift Project Developers"
+        ];
+        features = {
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "core" = [ "dep:core" ];
+          "default" = [ "std" ];
+          "rustc-dep-of-std" = [ "compiler_builtins" "core" "rustc-std-workspace-alloc" ];
+          "rustc-std-workspace-alloc" = [ "dep:rustc-std-workspace-alloc" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "wasm-bindgen" = rec {
+        crateName = "wasm-bindgen";
+        version = "0.2.88";
+        edition = "2018";
+        sha256 = "1khgsh4z9bga35mjhg41dl7523i69ffc5m8ckhqaw6ssyabc5bkx";
+        authors = [
+          "The wasm-bindgen Developers"
+        ];
+        dependencies = [
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "wasm-bindgen-macro";
+            packageId = "wasm-bindgen-macro";
+          }
+        ];
+        features = {
+          "default" = [ "spans" "std" ];
+          "enable-interning" = [ "std" ];
+          "gg-alloc" = [ "wasm-bindgen-test/gg-alloc" ];
+          "serde" = [ "dep:serde" ];
+          "serde-serialize" = [ "serde" "serde_json" "std" ];
+          "serde_json" = [ "dep:serde_json" ];
+          "spans" = [ "wasm-bindgen-macro/spans" ];
+          "strict-macro" = [ "wasm-bindgen-macro/strict-macro" ];
+          "xxx_debug_only_print_generated_code" = [ "wasm-bindgen-macro/xxx_debug_only_print_generated_code" ];
+        };
+        resolvedDefaultFeatures = [ "default" "spans" "std" ];
+      };
+      "wasm-bindgen-backend" = rec {
+        crateName = "wasm-bindgen-backend";
+        version = "0.2.88";
+        edition = "2018";
+        sha256 = "05zj8yl243rvs87rhicq2l1d6443lnm6k90khf744khf9ikg95z3";
+        authors = [
+          "The wasm-bindgen Developers"
+        ];
+        dependencies = [
+          {
+            name = "bumpalo";
+            packageId = "bumpalo";
+          }
+          {
+            name = "log";
+            packageId = "log";
+          }
+          {
+            name = "once_cell";
+            packageId = "once_cell";
+          }
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn 2.0.39";
+            features = [ "full" ];
+          }
+          {
+            name = "wasm-bindgen-shared";
+            packageId = "wasm-bindgen-shared";
+          }
+        ];
+        features = {
+          "extra-traits" = [ "syn/extra-traits" ];
+        };
+        resolvedDefaultFeatures = [ "spans" ];
+      };
+      "wasm-bindgen-macro" = rec {
+        crateName = "wasm-bindgen-macro";
+        version = "0.2.88";
+        edition = "2018";
+        sha256 = "1chn3wgw9awmvs0fpmazbqyc5rwfgy3pj7lzwczmzb887dxh2qar";
+        procMacro = true;
+        authors = [
+          "The wasm-bindgen Developers"
+        ];
+        dependencies = [
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "wasm-bindgen-macro-support";
+            packageId = "wasm-bindgen-macro-support";
+          }
+        ];
+        features = {
+          "spans" = [ "wasm-bindgen-macro-support/spans" ];
+          "strict-macro" = [ "wasm-bindgen-macro-support/strict-macro" ];
+        };
+        resolvedDefaultFeatures = [ "spans" ];
+      };
+      "wasm-bindgen-macro-support" = rec {
+        crateName = "wasm-bindgen-macro-support";
+        version = "0.2.88";
+        edition = "2018";
+        sha256 = "01rrzg3y1apqygsjz1jg0n7p831nm4kdyxmxyl85x7v6mf6kndf5";
+        authors = [
+          "The wasm-bindgen Developers"
+        ];
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn 2.0.39";
+            features = [ "visit" "full" ];
+          }
+          {
+            name = "wasm-bindgen-backend";
+            packageId = "wasm-bindgen-backend";
+          }
+          {
+            name = "wasm-bindgen-shared";
+            packageId = "wasm-bindgen-shared";
+          }
+        ];
+        features = {
+          "extra-traits" = [ "syn/extra-traits" ];
+          "spans" = [ "wasm-bindgen-backend/spans" ];
+        };
+        resolvedDefaultFeatures = [ "spans" ];
+      };
+      "wasm-bindgen-shared" = rec {
+        crateName = "wasm-bindgen-shared";
+        version = "0.2.88";
+        edition = "2018";
+        links = "wasm_bindgen";
+        sha256 = "02vmw2rzsla1qm0zgfng4kqz52xn8k54v8ads4g1macv09fnq10d";
+        authors = [
+          "The wasm-bindgen Developers"
+        ];
+
+      };
+      "web-sys" = rec {
+        crateName = "web-sys";
+        version = "0.3.65";
+        edition = "2018";
+        sha256 = "11ba406ca9qssc21c37v49sn2y2gsdn6c3nva4hjf8v3yv2rkd2x";
+        authors = [
+          "The wasm-bindgen Developers"
+        ];
+        dependencies = [
+          {
+            name = "js-sys";
+            packageId = "js-sys";
+          }
+          {
+            name = "wasm-bindgen";
+            packageId = "wasm-bindgen";
+          }
+        ];
+        features = {
+          "AbortSignal" = [ "EventTarget" ];
+          "AnalyserNode" = [ "AudioNode" "EventTarget" ];
+          "Animation" = [ "EventTarget" ];
+          "AnimationEvent" = [ "Event" ];
+          "AnimationPlaybackEvent" = [ "Event" ];
+          "Attr" = [ "EventTarget" "Node" ];
+          "AudioBufferSourceNode" = [ "AudioNode" "AudioScheduledSourceNode" "EventTarget" ];
+          "AudioContext" = [ "BaseAudioContext" "EventTarget" ];
+          "AudioDestinationNode" = [ "AudioNode" "EventTarget" ];
+          "AudioNode" = [ "EventTarget" ];
+          "AudioProcessingEvent" = [ "Event" ];
+          "AudioScheduledSourceNode" = [ "AudioNode" "EventTarget" ];
+          "AudioStreamTrack" = [ "EventTarget" "MediaStreamTrack" ];
+          "AudioTrackList" = [ "EventTarget" ];
+          "AudioWorklet" = [ "Worklet" ];
+          "AudioWorkletGlobalScope" = [ "WorkletGlobalScope" ];
+          "AudioWorkletNode" = [ "AudioNode" "EventTarget" ];
+          "AuthenticatorAssertionResponse" = [ "AuthenticatorResponse" ];
+          "AuthenticatorAttestationResponse" = [ "AuthenticatorResponse" ];
+          "BaseAudioContext" = [ "EventTarget" ];
+          "BatteryManager" = [ "EventTarget" ];
+          "BeforeUnloadEvent" = [ "Event" ];
+          "BiquadFilterNode" = [ "AudioNode" "EventTarget" ];
+          "BlobEvent" = [ "Event" ];
+          "Bluetooth" = [ "EventTarget" ];
+          "BluetoothAdvertisingEvent" = [ "Event" ];
+          "BluetoothDevice" = [ "EventTarget" ];
+          "BluetoothPermissionResult" = [ "EventTarget" "PermissionStatus" ];
+          "BluetoothRemoteGattCharacteristic" = [ "EventTarget" ];
+          "BluetoothRemoteGattService" = [ "EventTarget" ];
+          "BroadcastChannel" = [ "EventTarget" ];
+          "CanvasCaptureMediaStream" = [ "EventTarget" "MediaStream" ];
+          "CanvasCaptureMediaStreamTrack" = [ "EventTarget" "MediaStreamTrack" ];
+          "CdataSection" = [ "CharacterData" "EventTarget" "Node" "Text" ];
+          "ChannelMergerNode" = [ "AudioNode" "EventTarget" ];
+          "ChannelSplitterNode" = [ "AudioNode" "EventTarget" ];
+          "CharacterData" = [ "EventTarget" "Node" ];
+          "ChromeWorker" = [ "EventTarget" "Worker" ];
+          "Clipboard" = [ "EventTarget" ];
+          "ClipboardEvent" = [ "Event" ];
+          "CloseEvent" = [ "Event" ];
+          "Comment" = [ "CharacterData" "EventTarget" "Node" ];
+          "CompositionEvent" = [ "Event" "UiEvent" ];
+          "ConstantSourceNode" = [ "AudioNode" "AudioScheduledSourceNode" "EventTarget" ];
+          "ConvolverNode" = [ "AudioNode" "EventTarget" ];
+          "CssAnimation" = [ "Animation" "EventTarget" ];
+          "CssConditionRule" = [ "CssGroupingRule" "CssRule" ];
+          "CssCounterStyleRule" = [ "CssRule" ];
+          "CssFontFaceRule" = [ "CssRule" ];
+          "CssFontFeatureValuesRule" = [ "CssRule" ];
+          "CssGroupingRule" = [ "CssRule" ];
+          "CssImportRule" = [ "CssRule" ];
+          "CssKeyframeRule" = [ "CssRule" ];
+          "CssKeyframesRule" = [ "CssRule" ];
+          "CssMediaRule" = [ "CssConditionRule" "CssGroupingRule" "CssRule" ];
+          "CssNamespaceRule" = [ "CssRule" ];
+          "CssPageRule" = [ "CssRule" ];
+          "CssStyleRule" = [ "CssRule" ];
+          "CssStyleSheet" = [ "StyleSheet" ];
+          "CssSupportsRule" = [ "CssConditionRule" "CssGroupingRule" "CssRule" ];
+          "CssTransition" = [ "Animation" "EventTarget" ];
+          "CustomEvent" = [ "Event" ];
+          "DedicatedWorkerGlobalScope" = [ "EventTarget" "WorkerGlobalScope" ];
+          "DelayNode" = [ "AudioNode" "EventTarget" ];
+          "DeviceLightEvent" = [ "Event" ];
+          "DeviceMotionEvent" = [ "Event" ];
+          "DeviceOrientationEvent" = [ "Event" ];
+          "DeviceProximityEvent" = [ "Event" ];
+          "Document" = [ "EventTarget" "Node" ];
+          "DocumentFragment" = [ "EventTarget" "Node" ];
+          "DocumentTimeline" = [ "AnimationTimeline" ];
+          "DocumentType" = [ "EventTarget" "Node" ];
+          "DomMatrix" = [ "DomMatrixReadOnly" ];
+          "DomPoint" = [ "DomPointReadOnly" ];
+          "DomRect" = [ "DomRectReadOnly" ];
+          "DomRequest" = [ "EventTarget" ];
+          "DragEvent" = [ "Event" "MouseEvent" "UiEvent" ];
+          "DynamicsCompressorNode" = [ "AudioNode" "EventTarget" ];
+          "Element" = [ "EventTarget" "Node" ];
+          "ErrorEvent" = [ "Event" ];
+          "EventSource" = [ "EventTarget" ];
+          "ExtendableEvent" = [ "Event" ];
+          "ExtendableMessageEvent" = [ "Event" "ExtendableEvent" ];
+          "FetchEvent" = [ "Event" "ExtendableEvent" ];
+          "FetchObserver" = [ "EventTarget" ];
+          "File" = [ "Blob" ];
+          "FileReader" = [ "EventTarget" ];
+          "FileSystemDirectoryEntry" = [ "FileSystemEntry" ];
+          "FileSystemDirectoryHandle" = [ "FileSystemHandle" ];
+          "FileSystemFileEntry" = [ "FileSystemEntry" ];
+          "FileSystemFileHandle" = [ "FileSystemHandle" ];
+          "FileSystemWritableFileStream" = [ "WritableStream" ];
+          "FocusEvent" = [ "Event" "UiEvent" ];
+          "FontFaceSet" = [ "EventTarget" ];
+          "FontFaceSetLoadEvent" = [ "Event" ];
+          "GainNode" = [ "AudioNode" "EventTarget" ];
+          "GamepadAxisMoveEvent" = [ "Event" "GamepadEvent" ];
+          "GamepadButtonEvent" = [ "Event" "GamepadEvent" ];
+          "GamepadEvent" = [ "Event" ];
+          "GpuDevice" = [ "EventTarget" ];
+          "GpuInternalError" = [ "GpuError" ];
+          "GpuOutOfMemoryError" = [ "GpuError" ];
+          "GpuPipelineError" = [ "DomException" ];
+          "GpuUncapturedErrorEvent" = [ "Event" ];
+          "GpuValidationError" = [ "GpuError" ];
+          "HashChangeEvent" = [ "Event" ];
+          "Hid" = [ "EventTarget" ];
+          "HidConnectionEvent" = [ "Event" ];
+          "HidDevice" = [ "EventTarget" ];
+          "HidInputReportEvent" = [ "Event" ];
+          "HtmlAnchorElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlAreaElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlAudioElement" = [ "Element" "EventTarget" "HtmlElement" "HtmlMediaElement" "Node" ];
+          "HtmlBaseElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlBodyElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlBrElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlButtonElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlCanvasElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlDListElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlDataElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlDataListElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlDetailsElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlDialogElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlDirectoryElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlDivElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlDocument" = [ "Document" "EventTarget" "Node" ];
+          "HtmlElement" = [ "Element" "EventTarget" "Node" ];
+          "HtmlEmbedElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlFieldSetElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlFontElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlFormControlsCollection" = [ "HtmlCollection" ];
+          "HtmlFormElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlFrameElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlFrameSetElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlHeadElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlHeadingElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlHrElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlHtmlElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlIFrameElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlImageElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlInputElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlLabelElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlLegendElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlLiElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlLinkElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlMapElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlMediaElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlMenuElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlMenuItemElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlMetaElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlMeterElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlModElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlOListElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlObjectElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlOptGroupElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlOptionElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlOptionsCollection" = [ "HtmlCollection" ];
+          "HtmlOutputElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlParagraphElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlParamElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlPictureElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlPreElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlProgressElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlQuoteElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlScriptElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlSelectElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlSlotElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlSourceElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlSpanElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlStyleElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlTableCaptionElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlTableCellElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlTableColElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlTableElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlTableRowElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlTableSectionElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlTemplateElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlTextAreaElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlTimeElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlTitleElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlTrackElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlUListElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlUnknownElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlVideoElement" = [ "Element" "EventTarget" "HtmlElement" "HtmlMediaElement" "Node" ];
+          "IdbCursorWithValue" = [ "IdbCursor" ];
+          "IdbDatabase" = [ "EventTarget" ];
+          "IdbFileHandle" = [ "EventTarget" ];
+          "IdbFileRequest" = [ "DomRequest" "EventTarget" ];
+          "IdbLocaleAwareKeyRange" = [ "IdbKeyRange" ];
+          "IdbMutableFile" = [ "EventTarget" ];
+          "IdbOpenDbRequest" = [ "EventTarget" "IdbRequest" ];
+          "IdbRequest" = [ "EventTarget" ];
+          "IdbTransaction" = [ "EventTarget" ];
+          "IdbVersionChangeEvent" = [ "Event" ];
+          "IirFilterNode" = [ "AudioNode" "EventTarget" ];
+          "ImageCaptureErrorEvent" = [ "Event" ];
+          "ImageTrack" = [ "EventTarget" ];
+          "InputEvent" = [ "Event" "UiEvent" ];
+          "KeyboardEvent" = [ "Event" "UiEvent" ];
+          "KeyframeEffect" = [ "AnimationEffect" ];
+          "LocalMediaStream" = [ "EventTarget" "MediaStream" ];
+          "MediaDevices" = [ "EventTarget" ];
+          "MediaElementAudioSourceNode" = [ "AudioNode" "EventTarget" ];
+          "MediaEncryptedEvent" = [ "Event" ];
+          "MediaKeyError" = [ "Event" ];
+          "MediaKeyMessageEvent" = [ "Event" ];
+          "MediaKeySession" = [ "EventTarget" ];
+          "MediaQueryList" = [ "EventTarget" ];
+          "MediaQueryListEvent" = [ "Event" ];
+          "MediaRecorder" = [ "EventTarget" ];
+          "MediaRecorderErrorEvent" = [ "Event" ];
+          "MediaSource" = [ "EventTarget" ];
+          "MediaStream" = [ "EventTarget" ];
+          "MediaStreamAudioDestinationNode" = [ "AudioNode" "EventTarget" ];
+          "MediaStreamAudioSourceNode" = [ "AudioNode" "EventTarget" ];
+          "MediaStreamEvent" = [ "Event" ];
+          "MediaStreamTrack" = [ "EventTarget" ];
+          "MediaStreamTrackEvent" = [ "Event" ];
+          "MediaStreamTrackGenerator" = [ "EventTarget" "MediaStreamTrack" ];
+          "MessageEvent" = [ "Event" ];
+          "MessagePort" = [ "EventTarget" ];
+          "MidiAccess" = [ "EventTarget" ];
+          "MidiConnectionEvent" = [ "Event" ];
+          "MidiInput" = [ "EventTarget" "MidiPort" ];
+          "MidiMessageEvent" = [ "Event" ];
+          "MidiOutput" = [ "EventTarget" "MidiPort" ];
+          "MidiPort" = [ "EventTarget" ];
+          "MouseEvent" = [ "Event" "UiEvent" ];
+          "MouseScrollEvent" = [ "Event" "MouseEvent" "UiEvent" ];
+          "MutationEvent" = [ "Event" ];
+          "NetworkInformation" = [ "EventTarget" ];
+          "Node" = [ "EventTarget" ];
+          "Notification" = [ "EventTarget" ];
+          "NotificationEvent" = [ "Event" "ExtendableEvent" ];
+          "OfflineAudioCompletionEvent" = [ "Event" ];
+          "OfflineAudioContext" = [ "BaseAudioContext" "EventTarget" ];
+          "OfflineResourceList" = [ "EventTarget" ];
+          "OffscreenCanvas" = [ "EventTarget" ];
+          "OscillatorNode" = [ "AudioNode" "AudioScheduledSourceNode" "EventTarget" ];
+          "PageTransitionEvent" = [ "Event" ];
+          "PaintWorkletGlobalScope" = [ "WorkletGlobalScope" ];
+          "PannerNode" = [ "AudioNode" "EventTarget" ];
+          "PaymentMethodChangeEvent" = [ "Event" "PaymentRequestUpdateEvent" ];
+          "PaymentRequestUpdateEvent" = [ "Event" ];
+          "Performance" = [ "EventTarget" ];
+          "PerformanceMark" = [ "PerformanceEntry" ];
+          "PerformanceMeasure" = [ "PerformanceEntry" ];
+          "PerformanceNavigationTiming" = [ "PerformanceEntry" "PerformanceResourceTiming" ];
+          "PerformanceResourceTiming" = [ "PerformanceEntry" ];
+          "PermissionStatus" = [ "EventTarget" ];
+          "PointerEvent" = [ "Event" "MouseEvent" "UiEvent" ];
+          "PopStateEvent" = [ "Event" ];
+          "PopupBlockedEvent" = [ "Event" ];
+          "PresentationAvailability" = [ "EventTarget" ];
+          "PresentationConnection" = [ "EventTarget" ];
+          "PresentationConnectionAvailableEvent" = [ "Event" ];
+          "PresentationConnectionCloseEvent" = [ "Event" ];
+          "PresentationConnectionList" = [ "EventTarget" ];
+          "PresentationRequest" = [ "EventTarget" ];
+          "ProcessingInstruction" = [ "CharacterData" "EventTarget" "Node" ];
+          "ProgressEvent" = [ "Event" ];
+          "PromiseRejectionEvent" = [ "Event" ];
+          "PublicKeyCredential" = [ "Credential" ];
+          "PushEvent" = [ "Event" "ExtendableEvent" ];
+          "RadioNodeList" = [ "NodeList" ];
+          "RtcDataChannel" = [ "EventTarget" ];
+          "RtcDataChannelEvent" = [ "Event" ];
+          "RtcPeerConnection" = [ "EventTarget" ];
+          "RtcPeerConnectionIceEvent" = [ "Event" ];
+          "RtcTrackEvent" = [ "Event" ];
+          "RtcdtmfSender" = [ "EventTarget" ];
+          "RtcdtmfToneChangeEvent" = [ "Event" ];
+          "Screen" = [ "EventTarget" ];
+          "ScreenOrientation" = [ "EventTarget" ];
+          "ScriptProcessorNode" = [ "AudioNode" "EventTarget" ];
+          "ScrollAreaEvent" = [ "Event" "UiEvent" ];
+          "SecurityPolicyViolationEvent" = [ "Event" ];
+          "Serial" = [ "EventTarget" ];
+          "SerialPort" = [ "EventTarget" ];
+          "ServiceWorker" = [ "EventTarget" ];
+          "ServiceWorkerContainer" = [ "EventTarget" ];
+          "ServiceWorkerGlobalScope" = [ "EventTarget" "WorkerGlobalScope" ];
+          "ServiceWorkerRegistration" = [ "EventTarget" ];
+          "ShadowRoot" = [ "DocumentFragment" "EventTarget" "Node" ];
+          "SharedWorker" = [ "EventTarget" ];
+          "SharedWorkerGlobalScope" = [ "EventTarget" "WorkerGlobalScope" ];
+          "SourceBuffer" = [ "EventTarget" ];
+          "SourceBufferList" = [ "EventTarget" ];
+          "SpeechRecognition" = [ "EventTarget" ];
+          "SpeechRecognitionError" = [ "Event" ];
+          "SpeechRecognitionEvent" = [ "Event" ];
+          "SpeechSynthesis" = [ "EventTarget" ];
+          "SpeechSynthesisErrorEvent" = [ "Event" "SpeechSynthesisEvent" ];
+          "SpeechSynthesisEvent" = [ "Event" ];
+          "SpeechSynthesisUtterance" = [ "EventTarget" ];
+          "StereoPannerNode" = [ "AudioNode" "EventTarget" ];
+          "StorageEvent" = [ "Event" ];
+          "SubmitEvent" = [ "Event" ];
+          "SvgAnimateElement" = [ "Element" "EventTarget" "Node" "SvgAnimationElement" "SvgElement" ];
+          "SvgAnimateMotionElement" = [ "Element" "EventTarget" "Node" "SvgAnimationElement" "SvgElement" ];
+          "SvgAnimateTransformElement" = [ "Element" "EventTarget" "Node" "SvgAnimationElement" "SvgElement" ];
+          "SvgAnimationElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgCircleElement" = [ "Element" "EventTarget" "Node" "SvgElement" "SvgGeometryElement" "SvgGraphicsElement" ];
+          "SvgClipPathElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgComponentTransferFunctionElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgDefsElement" = [ "Element" "EventTarget" "Node" "SvgElement" "SvgGraphicsElement" ];
+          "SvgDescElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgElement" = [ "Element" "EventTarget" "Node" ];
+          "SvgEllipseElement" = [ "Element" "EventTarget" "Node" "SvgElement" "SvgGeometryElement" "SvgGraphicsElement" ];
+          "SvgFilterElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgForeignObjectElement" = [ "Element" "EventTarget" "Node" "SvgElement" "SvgGraphicsElement" ];
+          "SvgGeometryElement" = [ "Element" "EventTarget" "Node" "SvgElement" "SvgGraphicsElement" ];
+          "SvgGradientElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgGraphicsElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgImageElement" = [ "Element" "EventTarget" "Node" "SvgElement" "SvgGraphicsElement" ];
+          "SvgLineElement" = [ "Element" "EventTarget" "Node" "SvgElement" "SvgGeometryElement" "SvgGraphicsElement" ];
+          "SvgLinearGradientElement" = [ "Element" "EventTarget" "Node" "SvgElement" "SvgGradientElement" ];
+          "SvgMarkerElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgMaskElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgMetadataElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgPathElement" = [ "Element" "EventTarget" "Node" "SvgElement" "SvgGeometryElement" "SvgGraphicsElement" ];
+          "SvgPathSegArcAbs" = [ "SvgPathSeg" ];
+          "SvgPathSegArcRel" = [ "SvgPathSeg" ];
+          "SvgPathSegClosePath" = [ "SvgPathSeg" ];
+          "SvgPathSegCurvetoCubicAbs" = [ "SvgPathSeg" ];
+          "SvgPathSegCurvetoCubicRel" = [ "SvgPathSeg" ];
+          "SvgPathSegCurvetoCubicSmoothAbs" = [ "SvgPathSeg" ];
+          "SvgPathSegCurvetoCubicSmoothRel" = [ "SvgPathSeg" ];
+          "SvgPathSegCurvetoQuadraticAbs" = [ "SvgPathSeg" ];
+          "SvgPathSegCurvetoQuadraticRel" = [ "SvgPathSeg" ];
+          "SvgPathSegCurvetoQuadraticSmoothAbs" = [ "SvgPathSeg" ];
+          "SvgPathSegCurvetoQuadraticSmoothRel" = [ "SvgPathSeg" ];
+          "SvgPathSegLinetoAbs" = [ "SvgPathSeg" ];
+          "SvgPathSegLinetoHorizontalAbs" = [ "SvgPathSeg" ];
+          "SvgPathSegLinetoHorizontalRel" = [ "SvgPathSeg" ];
+          "SvgPathSegLinetoRel" = [ "SvgPathSeg" ];
+          "SvgPathSegLinetoVerticalAbs" = [ "SvgPathSeg" ];
+          "SvgPathSegLinetoVerticalRel" = [ "SvgPathSeg" ];
+          "SvgPathSegMovetoAbs" = [ "SvgPathSeg" ];
+          "SvgPathSegMovetoRel" = [ "SvgPathSeg" ];
+          "SvgPatternElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgPolygonElement" = [ "Element" "EventTarget" "Node" "SvgElement" "SvgGeometryElement" "SvgGraphicsElement" ];
+          "SvgPolylineElement" = [ "Element" "EventTarget" "Node" "SvgElement" "SvgGeometryElement" "SvgGraphicsElement" ];
+          "SvgRadialGradientElement" = [ "Element" "EventTarget" "Node" "SvgElement" "SvgGradientElement" ];
+          "SvgRectElement" = [ "Element" "EventTarget" "Node" "SvgElement" "SvgGeometryElement" "SvgGraphicsElement" ];
+          "SvgScriptElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgSetElement" = [ "Element" "EventTarget" "Node" "SvgAnimationElement" "SvgElement" ];
+          "SvgStopElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgStyleElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgSwitchElement" = [ "Element" "EventTarget" "Node" "SvgElement" "SvgGraphicsElement" ];
+          "SvgSymbolElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgTextContentElement" = [ "Element" "EventTarget" "Node" "SvgElement" "SvgGraphicsElement" ];
+          "SvgTextElement" = [ "Element" "EventTarget" "Node" "SvgElement" "SvgGraphicsElement" "SvgTextContentElement" "SvgTextPositioningElement" ];
+          "SvgTextPathElement" = [ "Element" "EventTarget" "Node" "SvgElement" "SvgGraphicsElement" "SvgTextContentElement" ];
+          "SvgTextPositioningElement" = [ "Element" "EventTarget" "Node" "SvgElement" "SvgGraphicsElement" "SvgTextContentElement" ];
+          "SvgTitleElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgUseElement" = [ "Element" "EventTarget" "Node" "SvgElement" "SvgGraphicsElement" ];
+          "SvgViewElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgaElement" = [ "Element" "EventTarget" "Node" "SvgElement" "SvgGraphicsElement" ];
+          "SvgfeBlendElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgfeColorMatrixElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgfeComponentTransferElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgfeCompositeElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgfeConvolveMatrixElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgfeDiffuseLightingElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgfeDisplacementMapElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgfeDistantLightElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgfeDropShadowElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgfeFloodElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgfeFuncAElement" = [ "Element" "EventTarget" "Node" "SvgComponentTransferFunctionElement" "SvgElement" ];
+          "SvgfeFuncBElement" = [ "Element" "EventTarget" "Node" "SvgComponentTransferFunctionElement" "SvgElement" ];
+          "SvgfeFuncGElement" = [ "Element" "EventTarget" "Node" "SvgComponentTransferFunctionElement" "SvgElement" ];
+          "SvgfeFuncRElement" = [ "Element" "EventTarget" "Node" "SvgComponentTransferFunctionElement" "SvgElement" ];
+          "SvgfeGaussianBlurElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgfeImageElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgfeMergeElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgfeMergeNodeElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgfeMorphologyElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgfeOffsetElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgfePointLightElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgfeSpecularLightingElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgfeSpotLightElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgfeTileElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgfeTurbulenceElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvggElement" = [ "Element" "EventTarget" "Node" "SvgElement" "SvgGraphicsElement" ];
+          "SvgmPathElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgsvgElement" = [ "Element" "EventTarget" "Node" "SvgElement" "SvgGraphicsElement" ];
+          "SvgtSpanElement" = [ "Element" "EventTarget" "Node" "SvgElement" "SvgGraphicsElement" "SvgTextContentElement" "SvgTextPositioningElement" ];
+          "TaskController" = [ "AbortController" ];
+          "TaskPriorityChangeEvent" = [ "Event" ];
+          "TaskSignal" = [ "AbortSignal" "EventTarget" ];
+          "TcpServerSocket" = [ "EventTarget" ];
+          "TcpServerSocketEvent" = [ "Event" ];
+          "TcpSocket" = [ "EventTarget" ];
+          "TcpSocketErrorEvent" = [ "Event" ];
+          "TcpSocketEvent" = [ "Event" ];
+          "Text" = [ "CharacterData" "EventTarget" "Node" ];
+          "TextTrack" = [ "EventTarget" ];
+          "TextTrackCue" = [ "EventTarget" ];
+          "TextTrackList" = [ "EventTarget" ];
+          "TimeEvent" = [ "Event" ];
+          "TouchEvent" = [ "Event" "UiEvent" ];
+          "TrackEvent" = [ "Event" ];
+          "TransitionEvent" = [ "Event" ];
+          "UiEvent" = [ "Event" ];
+          "Usb" = [ "EventTarget" ];
+          "UsbConnectionEvent" = [ "Event" ];
+          "UsbPermissionResult" = [ "EventTarget" "PermissionStatus" ];
+          "UserProximityEvent" = [ "Event" ];
+          "ValueEvent" = [ "Event" ];
+          "VideoStreamTrack" = [ "EventTarget" "MediaStreamTrack" ];
+          "VideoTrackList" = [ "EventTarget" ];
+          "VrDisplay" = [ "EventTarget" ];
+          "VttCue" = [ "EventTarget" "TextTrackCue" ];
+          "WakeLockSentinel" = [ "EventTarget" ];
+          "WaveShaperNode" = [ "AudioNode" "EventTarget" ];
+          "WebGlContextEvent" = [ "Event" ];
+          "WebKitCssMatrix" = [ "DomMatrix" "DomMatrixReadOnly" ];
+          "WebSocket" = [ "EventTarget" ];
+          "WebTransportError" = [ "DomException" ];
+          "WebTransportReceiveStream" = [ "ReadableStream" ];
+          "WebTransportSendStream" = [ "WritableStream" ];
+          "WheelEvent" = [ "Event" "MouseEvent" "UiEvent" ];
+          "Window" = [ "EventTarget" ];
+          "WindowClient" = [ "Client" ];
+          "Worker" = [ "EventTarget" ];
+          "WorkerDebuggerGlobalScope" = [ "EventTarget" ];
+          "WorkerGlobalScope" = [ "EventTarget" ];
+          "XmlDocument" = [ "Document" "EventTarget" "Node" ];
+          "XmlHttpRequest" = [ "EventTarget" "XmlHttpRequestEventTarget" ];
+          "XmlHttpRequestEventTarget" = [ "EventTarget" ];
+          "XmlHttpRequestUpload" = [ "EventTarget" "XmlHttpRequestEventTarget" ];
+          "XrBoundedReferenceSpace" = [ "EventTarget" "XrReferenceSpace" "XrSpace" ];
+          "XrInputSourceEvent" = [ "Event" ];
+          "XrInputSourcesChangeEvent" = [ "Event" ];
+          "XrJointPose" = [ "XrPose" ];
+          "XrJointSpace" = [ "EventTarget" "XrSpace" ];
+          "XrLayer" = [ "EventTarget" ];
+          "XrPermissionStatus" = [ "EventTarget" "PermissionStatus" ];
+          "XrReferenceSpace" = [ "EventTarget" "XrSpace" ];
+          "XrReferenceSpaceEvent" = [ "Event" ];
+          "XrSession" = [ "EventTarget" ];
+          "XrSessionEvent" = [ "Event" ];
+          "XrSpace" = [ "EventTarget" ];
+          "XrSystem" = [ "EventTarget" ];
+          "XrViewerPose" = [ "XrPose" ];
+          "XrWebGlLayer" = [ "EventTarget" "XrLayer" ];
+        };
+        resolvedDefaultFeatures = [ "Crypto" "EventTarget" "Window" ];
+      };
+      "webpki" = rec {
+        crateName = "webpki";
+        version = "0.22.4";
+        edition = "2018";
+        sha256 = "0lwv7jdlcqjjqqhxcrapnyk5bz4lvr12q444b50gzl3krsjswqzd";
+        authors = [
+          "Brian Smith <brian@briansmith.org>"
+        ];
+        dependencies = [
+          {
+            name = "ring";
+            packageId = "ring 0.17.5";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "untrusted";
+            packageId = "untrusted 0.9.0";
+          }
+        ];
+        features = {
+          "alloc" = [ "ring/alloc" ];
+          "std" = [ "alloc" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "std" ];
+      };
+      "which" = rec {
+        crateName = "which";
+        version = "4.4.2";
+        edition = "2021";
+        sha256 = "1ixzmx3svsv5hbdvd8vdhd3qwvf6ns8jdpif1wmwsy10k90j9fl7";
+        authors = [
+          "Harry Fei <tiziyuanfang@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "either";
+            packageId = "either";
+          }
+          {
+            name = "home";
+            packageId = "home";
+            target = { target, features }: ((target."windows" or false) || (target."unix" or false) || ("redox" == target."os" or null));
+          }
+          {
+            name = "once_cell";
+            packageId = "once_cell";
+            target = { target, features }: (target."windows" or false);
+          }
+          {
+            name = "rustix";
+            packageId = "rustix";
+            usesDefaultFeatures = false;
+            features = [ "fs" "std" ];
+          }
+        ];
+        features = {
+          "regex" = [ "dep:regex" ];
+        };
+      };
+      "winapi" = rec {
+        crateName = "winapi";
+        version = "0.3.9";
+        edition = "2015";
+        sha256 = "06gl025x418lchw1wxj64ycr7gha83m44cjr5sarhynd9xkrm0sw";
+        authors = [
+          "Peter Atashian <retep998@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "winapi-i686-pc-windows-gnu";
+            packageId = "winapi-i686-pc-windows-gnu";
+            target = { target, features }: (pkgs.rust.lib.toRustTarget stdenv.hostPlatform == "i686-pc-windows-gnu");
+          }
+          {
+            name = "winapi-x86_64-pc-windows-gnu";
+            packageId = "winapi-x86_64-pc-windows-gnu";
+            target = { target, features }: (pkgs.rust.lib.toRustTarget stdenv.hostPlatform == "x86_64-pc-windows-gnu");
+          }
+        ];
+        features = {
+          "debug" = [ "impl-debug" ];
+        };
+        resolvedDefaultFeatures = [ "cfg" "combaseapi" "errhandlingapi" "evntrace" "fileapi" "handleapi" "heapapi" "ifdef" "in6addr" "inaddr" "ioapiset" "iphlpapi" "knownfolders" "lmaccess" "lmapibuf" "lmcons" "memoryapi" "minwinbase" "minwindef" "netioapi" "ntlsa" "ntsecapi" "ntstatus" "objbase" "objidl" "oleauto" "pdh" "powerbase" "processthreadsapi" "psapi" "rpcdce" "sddl" "securitybaseapi" "shellapi" "shlobj" "std" "synchapi" "sysinfoapi" "wbemcli" "winbase" "windef" "winerror" "winioctl" "winnt" "winsock2" "ws2ipdef" "ws2tcpip" "wtypesbase" ];
+      };
+      "winapi-i686-pc-windows-gnu" = rec {
+        crateName = "winapi-i686-pc-windows-gnu";
+        version = "0.4.0";
+        edition = "2015";
+        sha256 = "1dmpa6mvcvzz16zg6d5vrfy4bxgg541wxrcip7cnshi06v38ffxc";
+        authors = [
+          "Peter Atashian <retep998@gmail.com>"
+        ];
+
+      };
+      "winapi-x86_64-pc-windows-gnu" = rec {
+        crateName = "winapi-x86_64-pc-windows-gnu";
+        version = "0.4.0";
+        edition = "2015";
+        sha256 = "0gqq64czqb64kskjryj8isp62m2sgvx25yyj3kpc2myh85w24bki";
+        authors = [
+          "Peter Atashian <retep998@gmail.com>"
+        ];
+
+      };
+      "windows-core" = rec {
+        crateName = "windows-core";
+        version = "0.51.1";
+        edition = "2021";
+        sha256 = "0r1f57hsshsghjyc7ypp2s0i78f7b1vr93w68sdb8baxyf2czy7i";
+        authors = [
+          "Microsoft"
+        ];
+        dependencies = [
+          {
+            name = "windows-targets";
+            packageId = "windows-targets 0.48.5";
+          }
+        ];
+        features = { };
+        resolvedDefaultFeatures = [ "default" ];
+      };
+      "windows-sys 0.45.0" = rec {
+        crateName = "windows-sys";
+        version = "0.45.0";
+        edition = "2018";
+        sha256 = "1l36bcqm4g89pknfp8r9rl1w4bn017q6a8qlx8viv0xjxzjkna3m";
+        authors = [
+          "Microsoft"
+        ];
+        dependencies = [
+          {
+            name = "windows-targets";
+            packageId = "windows-targets 0.42.2";
+            target = { target, features }: (!(target."windows_raw_dylib" or false));
+          }
+        ];
+        features = {
+          "Win32_Data" = [ "Win32" ];
+          "Win32_Data_HtmlHelp" = [ "Win32_Data" ];
+          "Win32_Data_RightsManagement" = [ "Win32_Data" ];
+          "Win32_Data_Xml" = [ "Win32_Data" ];
+          "Win32_Data_Xml_MsXml" = [ "Win32_Data_Xml" ];
+          "Win32_Data_Xml_XmlLite" = [ "Win32_Data_Xml" ];
+          "Win32_Devices" = [ "Win32" ];
+          "Win32_Devices_AllJoyn" = [ "Win32_Devices" ];
+          "Win32_Devices_BiometricFramework" = [ "Win32_Devices" ];
+          "Win32_Devices_Bluetooth" = [ "Win32_Devices" ];
+          "Win32_Devices_Communication" = [ "Win32_Devices" ];
+          "Win32_Devices_DeviceAccess" = [ "Win32_Devices" ];
+          "Win32_Devices_DeviceAndDriverInstallation" = [ "Win32_Devices" ];
+          "Win32_Devices_DeviceQuery" = [ "Win32_Devices" ];
+          "Win32_Devices_Display" = [ "Win32_Devices" ];
+          "Win32_Devices_Enumeration" = [ "Win32_Devices" ];
+          "Win32_Devices_Enumeration_Pnp" = [ "Win32_Devices_Enumeration" ];
+          "Win32_Devices_Fax" = [ "Win32_Devices" ];
+          "Win32_Devices_FunctionDiscovery" = [ "Win32_Devices" ];
+          "Win32_Devices_Geolocation" = [ "Win32_Devices" ];
+          "Win32_Devices_HumanInterfaceDevice" = [ "Win32_Devices" ];
+          "Win32_Devices_ImageAcquisition" = [ "Win32_Devices" ];
+          "Win32_Devices_PortableDevices" = [ "Win32_Devices" ];
+          "Win32_Devices_Properties" = [ "Win32_Devices" ];
+          "Win32_Devices_Pwm" = [ "Win32_Devices" ];
+          "Win32_Devices_Sensors" = [ "Win32_Devices" ];
+          "Win32_Devices_SerialCommunication" = [ "Win32_Devices" ];
+          "Win32_Devices_Tapi" = [ "Win32_Devices" ];
+          "Win32_Devices_Usb" = [ "Win32_Devices" ];
+          "Win32_Devices_WebServicesOnDevices" = [ "Win32_Devices" ];
+          "Win32_Foundation" = [ "Win32" ];
+          "Win32_Gaming" = [ "Win32" ];
+          "Win32_Globalization" = [ "Win32" ];
+          "Win32_Graphics" = [ "Win32" ];
+          "Win32_Graphics_Dwm" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Gdi" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Hlsl" = [ "Win32_Graphics" ];
+          "Win32_Graphics_OpenGL" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Printing" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Printing_PrintTicket" = [ "Win32_Graphics_Printing" ];
+          "Win32_Management" = [ "Win32" ];
+          "Win32_Management_MobileDeviceManagementRegistration" = [ "Win32_Management" ];
+          "Win32_Media" = [ "Win32" ];
+          "Win32_Media_Audio" = [ "Win32_Media" ];
+          "Win32_Media_Audio_Apo" = [ "Win32_Media_Audio" ];
+          "Win32_Media_Audio_DirectMusic" = [ "Win32_Media_Audio" ];
+          "Win32_Media_Audio_Endpoints" = [ "Win32_Media_Audio" ];
+          "Win32_Media_Audio_XAudio2" = [ "Win32_Media_Audio" ];
+          "Win32_Media_DeviceManager" = [ "Win32_Media" ];
+          "Win32_Media_DxMediaObjects" = [ "Win32_Media" ];
+          "Win32_Media_KernelStreaming" = [ "Win32_Media" ];
+          "Win32_Media_LibrarySharingServices" = [ "Win32_Media" ];
+          "Win32_Media_MediaPlayer" = [ "Win32_Media" ];
+          "Win32_Media_Multimedia" = [ "Win32_Media" ];
+          "Win32_Media_Speech" = [ "Win32_Media" ];
+          "Win32_Media_Streaming" = [ "Win32_Media" ];
+          "Win32_Media_WindowsMediaFormat" = [ "Win32_Media" ];
+          "Win32_NetworkManagement" = [ "Win32" ];
+          "Win32_NetworkManagement_Dhcp" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_Dns" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_InternetConnectionWizard" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_IpHelper" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_MobileBroadband" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_Multicast" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_Ndis" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_NetBios" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_NetManagement" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_NetShell" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_NetworkDiagnosticsFramework" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_NetworkPolicyServer" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_P2P" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_QoS" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_Rras" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_Snmp" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WNet" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WebDav" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WiFi" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WindowsConnectNow" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WindowsConnectionManager" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WindowsFilteringPlatform" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WindowsFirewall" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WindowsNetworkVirtualization" = [ "Win32_NetworkManagement" ];
+          "Win32_Networking" = [ "Win32" ];
+          "Win32_Networking_ActiveDirectory" = [ "Win32_Networking" ];
+          "Win32_Networking_BackgroundIntelligentTransferService" = [ "Win32_Networking" ];
+          "Win32_Networking_Clustering" = [ "Win32_Networking" ];
+          "Win32_Networking_HttpServer" = [ "Win32_Networking" ];
+          "Win32_Networking_Ldap" = [ "Win32_Networking" ];
+          "Win32_Networking_NetworkListManager" = [ "Win32_Networking" ];
+          "Win32_Networking_RemoteDifferentialCompression" = [ "Win32_Networking" ];
+          "Win32_Networking_WebSocket" = [ "Win32_Networking" ];
+          "Win32_Networking_WinHttp" = [ "Win32_Networking" ];
+          "Win32_Networking_WinInet" = [ "Win32_Networking" ];
+          "Win32_Networking_WinSock" = [ "Win32_Networking" ];
+          "Win32_Networking_WindowsWebServices" = [ "Win32_Networking" ];
+          "Win32_Security" = [ "Win32" ];
+          "Win32_Security_AppLocker" = [ "Win32_Security" ];
+          "Win32_Security_Authentication" = [ "Win32_Security" ];
+          "Win32_Security_Authentication_Identity" = [ "Win32_Security_Authentication" ];
+          "Win32_Security_Authentication_Identity_Provider" = [ "Win32_Security_Authentication_Identity" ];
+          "Win32_Security_Authorization" = [ "Win32_Security" ];
+          "Win32_Security_Authorization_UI" = [ "Win32_Security_Authorization" ];
+          "Win32_Security_ConfigurationSnapin" = [ "Win32_Security" ];
+          "Win32_Security_Credentials" = [ "Win32_Security" ];
+          "Win32_Security_Cryptography" = [ "Win32_Security" ];
+          "Win32_Security_Cryptography_Catalog" = [ "Win32_Security_Cryptography" ];
+          "Win32_Security_Cryptography_Certificates" = [ "Win32_Security_Cryptography" ];
+          "Win32_Security_Cryptography_Sip" = [ "Win32_Security_Cryptography" ];
+          "Win32_Security_Cryptography_UI" = [ "Win32_Security_Cryptography" ];
+          "Win32_Security_DiagnosticDataQuery" = [ "Win32_Security" ];
+          "Win32_Security_DirectoryServices" = [ "Win32_Security" ];
+          "Win32_Security_EnterpriseData" = [ "Win32_Security" ];
+          "Win32_Security_ExtensibleAuthenticationProtocol" = [ "Win32_Security" ];
+          "Win32_Security_Isolation" = [ "Win32_Security" ];
+          "Win32_Security_LicenseProtection" = [ "Win32_Security" ];
+          "Win32_Security_NetworkAccessProtection" = [ "Win32_Security" ];
+          "Win32_Security_Tpm" = [ "Win32_Security" ];
+          "Win32_Security_WinTrust" = [ "Win32_Security" ];
+          "Win32_Security_WinWlx" = [ "Win32_Security" ];
+          "Win32_Storage" = [ "Win32" ];
+          "Win32_Storage_Cabinets" = [ "Win32_Storage" ];
+          "Win32_Storage_CloudFilters" = [ "Win32_Storage" ];
+          "Win32_Storage_Compression" = [ "Win32_Storage" ];
+          "Win32_Storage_DataDeduplication" = [ "Win32_Storage" ];
+          "Win32_Storage_DistributedFileSystem" = [ "Win32_Storage" ];
+          "Win32_Storage_EnhancedStorage" = [ "Win32_Storage" ];
+          "Win32_Storage_FileHistory" = [ "Win32_Storage" ];
+          "Win32_Storage_FileServerResourceManager" = [ "Win32_Storage" ];
+          "Win32_Storage_FileSystem" = [ "Win32_Storage" ];
+          "Win32_Storage_Imapi" = [ "Win32_Storage" ];
+          "Win32_Storage_IndexServer" = [ "Win32_Storage" ];
+          "Win32_Storage_InstallableFileSystems" = [ "Win32_Storage" ];
+          "Win32_Storage_IscsiDisc" = [ "Win32_Storage" ];
+          "Win32_Storage_Jet" = [ "Win32_Storage" ];
+          "Win32_Storage_OfflineFiles" = [ "Win32_Storage" ];
+          "Win32_Storage_OperationRecorder" = [ "Win32_Storage" ];
+          "Win32_Storage_Packaging" = [ "Win32_Storage" ];
+          "Win32_Storage_Packaging_Appx" = [ "Win32_Storage_Packaging" ];
+          "Win32_Storage_Packaging_Opc" = [ "Win32_Storage_Packaging" ];
+          "Win32_Storage_ProjectedFileSystem" = [ "Win32_Storage" ];
+          "Win32_Storage_StructuredStorage" = [ "Win32_Storage" ];
+          "Win32_Storage_Vhd" = [ "Win32_Storage" ];
+          "Win32_Storage_VirtualDiskService" = [ "Win32_Storage" ];
+          "Win32_Storage_Vss" = [ "Win32_Storage" ];
+          "Win32_Storage_Xps" = [ "Win32_Storage" ];
+          "Win32_Storage_Xps_Printing" = [ "Win32_Storage_Xps" ];
+          "Win32_System" = [ "Win32" ];
+          "Win32_System_AddressBook" = [ "Win32_System" ];
+          "Win32_System_Antimalware" = [ "Win32_System" ];
+          "Win32_System_ApplicationInstallationAndServicing" = [ "Win32_System" ];
+          "Win32_System_ApplicationVerifier" = [ "Win32_System" ];
+          "Win32_System_AssessmentTool" = [ "Win32_System" ];
+          "Win32_System_Com" = [ "Win32_System" ];
+          "Win32_System_Com_CallObj" = [ "Win32_System_Com" ];
+          "Win32_System_Com_ChannelCredentials" = [ "Win32_System_Com" ];
+          "Win32_System_Com_Events" = [ "Win32_System_Com" ];
+          "Win32_System_Com_Marshal" = [ "Win32_System_Com" ];
+          "Win32_System_Com_StructuredStorage" = [ "Win32_System_Com" ];
+          "Win32_System_Com_UI" = [ "Win32_System_Com" ];
+          "Win32_System_Com_Urlmon" = [ "Win32_System_Com" ];
+          "Win32_System_ComponentServices" = [ "Win32_System" ];
+          "Win32_System_Console" = [ "Win32_System" ];
+          "Win32_System_Contacts" = [ "Win32_System" ];
+          "Win32_System_CorrelationVector" = [ "Win32_System" ];
+          "Win32_System_DataExchange" = [ "Win32_System" ];
+          "Win32_System_DeploymentServices" = [ "Win32_System" ];
+          "Win32_System_DesktopSharing" = [ "Win32_System" ];
+          "Win32_System_DeveloperLicensing" = [ "Win32_System" ];
+          "Win32_System_Diagnostics" = [ "Win32_System" ];
+          "Win32_System_Diagnostics_Ceip" = [ "Win32_System_Diagnostics" ];
+          "Win32_System_Diagnostics_Debug" = [ "Win32_System_Diagnostics" ];
+          "Win32_System_Diagnostics_Etw" = [ "Win32_System_Diagnostics" ];
+          "Win32_System_Diagnostics_ProcessSnapshotting" = [ "Win32_System_Diagnostics" ];
+          "Win32_System_Diagnostics_ToolHelp" = [ "Win32_System_Diagnostics" ];
+          "Win32_System_DistributedTransactionCoordinator" = [ "Win32_System" ];
+          "Win32_System_Environment" = [ "Win32_System" ];
+          "Win32_System_ErrorReporting" = [ "Win32_System" ];
+          "Win32_System_EventCollector" = [ "Win32_System" ];
+          "Win32_System_EventLog" = [ "Win32_System" ];
+          "Win32_System_EventNotificationService" = [ "Win32_System" ];
+          "Win32_System_GroupPolicy" = [ "Win32_System" ];
+          "Win32_System_HostCompute" = [ "Win32_System" ];
+          "Win32_System_HostComputeNetwork" = [ "Win32_System" ];
+          "Win32_System_HostComputeSystem" = [ "Win32_System" ];
+          "Win32_System_Hypervisor" = [ "Win32_System" ];
+          "Win32_System_IO" = [ "Win32_System" ];
+          "Win32_System_Iis" = [ "Win32_System" ];
+          "Win32_System_Ioctl" = [ "Win32_System" ];
+          "Win32_System_JobObjects" = [ "Win32_System" ];
+          "Win32_System_Js" = [ "Win32_System" ];
+          "Win32_System_Kernel" = [ "Win32_System" ];
+          "Win32_System_LibraryLoader" = [ "Win32_System" ];
+          "Win32_System_Mailslots" = [ "Win32_System" ];
+          "Win32_System_Mapi" = [ "Win32_System" ];
+          "Win32_System_Memory" = [ "Win32_System" ];
+          "Win32_System_Memory_NonVolatile" = [ "Win32_System_Memory" ];
+          "Win32_System_MessageQueuing" = [ "Win32_System" ];
+          "Win32_System_MixedReality" = [ "Win32_System" ];
+          "Win32_System_Mmc" = [ "Win32_System" ];
+          "Win32_System_Ole" = [ "Win32_System" ];
+          "Win32_System_ParentalControls" = [ "Win32_System" ];
+          "Win32_System_PasswordManagement" = [ "Win32_System" ];
+          "Win32_System_Performance" = [ "Win32_System" ];
+          "Win32_System_Performance_HardwareCounterProfiling" = [ "Win32_System_Performance" ];
+          "Win32_System_Pipes" = [ "Win32_System" ];
+          "Win32_System_Power" = [ "Win32_System" ];
+          "Win32_System_ProcessStatus" = [ "Win32_System" ];
+          "Win32_System_RealTimeCommunications" = [ "Win32_System" ];
+          "Win32_System_Recovery" = [ "Win32_System" ];
+          "Win32_System_Registry" = [ "Win32_System" ];
+          "Win32_System_RemoteAssistance" = [ "Win32_System" ];
+          "Win32_System_RemoteDesktop" = [ "Win32_System" ];
+          "Win32_System_RemoteManagement" = [ "Win32_System" ];
+          "Win32_System_RestartManager" = [ "Win32_System" ];
+          "Win32_System_Restore" = [ "Win32_System" ];
+          "Win32_System_Rpc" = [ "Win32_System" ];
+          "Win32_System_Search" = [ "Win32_System" ];
+          "Win32_System_Search_Common" = [ "Win32_System_Search" ];
+          "Win32_System_SecurityCenter" = [ "Win32_System" ];
+          "Win32_System_ServerBackup" = [ "Win32_System" ];
+          "Win32_System_Services" = [ "Win32_System" ];
+          "Win32_System_SettingsManagementInfrastructure" = [ "Win32_System" ];
+          "Win32_System_SetupAndMigration" = [ "Win32_System" ];
+          "Win32_System_Shutdown" = [ "Win32_System" ];
+          "Win32_System_StationsAndDesktops" = [ "Win32_System" ];
+          "Win32_System_SubsystemForLinux" = [ "Win32_System" ];
+          "Win32_System_SystemInformation" = [ "Win32_System" ];
+          "Win32_System_SystemServices" = [ "Win32_System" ];
+          "Win32_System_TaskScheduler" = [ "Win32_System" ];
+          "Win32_System_Threading" = [ "Win32_System" ];
+          "Win32_System_Time" = [ "Win32_System" ];
+          "Win32_System_TpmBaseServices" = [ "Win32_System" ];
+          "Win32_System_UpdateAgent" = [ "Win32_System" ];
+          "Win32_System_UpdateAssessment" = [ "Win32_System" ];
+          "Win32_System_UserAccessLogging" = [ "Win32_System" ];
+          "Win32_System_VirtualDosMachines" = [ "Win32_System" ];
+          "Win32_System_WindowsProgramming" = [ "Win32_System" ];
+          "Win32_System_WindowsSync" = [ "Win32_System" ];
+          "Win32_System_Wmi" = [ "Win32_System" ];
+          "Win32_UI" = [ "Win32" ];
+          "Win32_UI_Accessibility" = [ "Win32_UI" ];
+          "Win32_UI_Animation" = [ "Win32_UI" ];
+          "Win32_UI_ColorSystem" = [ "Win32_UI" ];
+          "Win32_UI_Controls" = [ "Win32_UI" ];
+          "Win32_UI_Controls_Dialogs" = [ "Win32_UI_Controls" ];
+          "Win32_UI_Controls_RichEdit" = [ "Win32_UI_Controls" ];
+          "Win32_UI_HiDpi" = [ "Win32_UI" ];
+          "Win32_UI_Input" = [ "Win32_UI" ];
+          "Win32_UI_Input_Ime" = [ "Win32_UI_Input" ];
+          "Win32_UI_Input_Ink" = [ "Win32_UI_Input" ];
+          "Win32_UI_Input_KeyboardAndMouse" = [ "Win32_UI_Input" ];
+          "Win32_UI_Input_Pointer" = [ "Win32_UI_Input" ];
+          "Win32_UI_Input_Radial" = [ "Win32_UI_Input" ];
+          "Win32_UI_Input_Touch" = [ "Win32_UI_Input" ];
+          "Win32_UI_Input_XboxController" = [ "Win32_UI_Input" ];
+          "Win32_UI_InteractionContext" = [ "Win32_UI" ];
+          "Win32_UI_LegacyWindowsEnvironmentFeatures" = [ "Win32_UI" ];
+          "Win32_UI_Magnification" = [ "Win32_UI" ];
+          "Win32_UI_Notifications" = [ "Win32_UI" ];
+          "Win32_UI_Ribbon" = [ "Win32_UI" ];
+          "Win32_UI_Shell" = [ "Win32_UI" ];
+          "Win32_UI_Shell_Common" = [ "Win32_UI_Shell" ];
+          "Win32_UI_Shell_PropertiesSystem" = [ "Win32_UI_Shell" ];
+          "Win32_UI_TabletPC" = [ "Win32_UI" ];
+          "Win32_UI_TextServices" = [ "Win32_UI" ];
+          "Win32_UI_WindowsAndMessaging" = [ "Win32_UI" ];
+          "Win32_UI_Wpf" = [ "Win32_UI" ];
+        };
+        resolvedDefaultFeatures = [ "Win32" "Win32_Foundation" "Win32_Storage" "Win32_Storage_FileSystem" "Win32_System" "Win32_System_Console" "Win32_UI" "Win32_UI_Input" "Win32_UI_Input_KeyboardAndMouse" "default" ];
+      };
+      "windows-sys 0.48.0" = rec {
+        crateName = "windows-sys";
+        version = "0.48.0";
+        edition = "2018";
+        sha256 = "1aan23v5gs7gya1lc46hqn9mdh8yph3fhxmhxlw36pn6pqc28zb7";
+        authors = [
+          "Microsoft"
+        ];
+        dependencies = [
+          {
+            name = "windows-targets";
+            packageId = "windows-targets 0.48.5";
+          }
+        ];
+        features = {
+          "Wdk_System" = [ "Wdk" ];
+          "Wdk_System_OfflineRegistry" = [ "Wdk_System" ];
+          "Win32_Data" = [ "Win32" ];
+          "Win32_Data_HtmlHelp" = [ "Win32_Data" ];
+          "Win32_Data_RightsManagement" = [ "Win32_Data" ];
+          "Win32_Data_Xml" = [ "Win32_Data" ];
+          "Win32_Data_Xml_MsXml" = [ "Win32_Data_Xml" ];
+          "Win32_Data_Xml_XmlLite" = [ "Win32_Data_Xml" ];
+          "Win32_Devices" = [ "Win32" ];
+          "Win32_Devices_AllJoyn" = [ "Win32_Devices" ];
+          "Win32_Devices_BiometricFramework" = [ "Win32_Devices" ];
+          "Win32_Devices_Bluetooth" = [ "Win32_Devices" ];
+          "Win32_Devices_Communication" = [ "Win32_Devices" ];
+          "Win32_Devices_DeviceAccess" = [ "Win32_Devices" ];
+          "Win32_Devices_DeviceAndDriverInstallation" = [ "Win32_Devices" ];
+          "Win32_Devices_DeviceQuery" = [ "Win32_Devices" ];
+          "Win32_Devices_Display" = [ "Win32_Devices" ];
+          "Win32_Devices_Enumeration" = [ "Win32_Devices" ];
+          "Win32_Devices_Enumeration_Pnp" = [ "Win32_Devices_Enumeration" ];
+          "Win32_Devices_Fax" = [ "Win32_Devices" ];
+          "Win32_Devices_FunctionDiscovery" = [ "Win32_Devices" ];
+          "Win32_Devices_Geolocation" = [ "Win32_Devices" ];
+          "Win32_Devices_HumanInterfaceDevice" = [ "Win32_Devices" ];
+          "Win32_Devices_ImageAcquisition" = [ "Win32_Devices" ];
+          "Win32_Devices_PortableDevices" = [ "Win32_Devices" ];
+          "Win32_Devices_Properties" = [ "Win32_Devices" ];
+          "Win32_Devices_Pwm" = [ "Win32_Devices" ];
+          "Win32_Devices_Sensors" = [ "Win32_Devices" ];
+          "Win32_Devices_SerialCommunication" = [ "Win32_Devices" ];
+          "Win32_Devices_Tapi" = [ "Win32_Devices" ];
+          "Win32_Devices_Usb" = [ "Win32_Devices" ];
+          "Win32_Devices_WebServicesOnDevices" = [ "Win32_Devices" ];
+          "Win32_Foundation" = [ "Win32" ];
+          "Win32_Gaming" = [ "Win32" ];
+          "Win32_Globalization" = [ "Win32" ];
+          "Win32_Graphics" = [ "Win32" ];
+          "Win32_Graphics_Dwm" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Gdi" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Hlsl" = [ "Win32_Graphics" ];
+          "Win32_Graphics_OpenGL" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Printing" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Printing_PrintTicket" = [ "Win32_Graphics_Printing" ];
+          "Win32_Management" = [ "Win32" ];
+          "Win32_Management_MobileDeviceManagementRegistration" = [ "Win32_Management" ];
+          "Win32_Media" = [ "Win32" ];
+          "Win32_Media_Audio" = [ "Win32_Media" ];
+          "Win32_Media_Audio_Apo" = [ "Win32_Media_Audio" ];
+          "Win32_Media_Audio_DirectMusic" = [ "Win32_Media_Audio" ];
+          "Win32_Media_Audio_Endpoints" = [ "Win32_Media_Audio" ];
+          "Win32_Media_Audio_XAudio2" = [ "Win32_Media_Audio" ];
+          "Win32_Media_DeviceManager" = [ "Win32_Media" ];
+          "Win32_Media_DxMediaObjects" = [ "Win32_Media" ];
+          "Win32_Media_KernelStreaming" = [ "Win32_Media" ];
+          "Win32_Media_LibrarySharingServices" = [ "Win32_Media" ];
+          "Win32_Media_MediaPlayer" = [ "Win32_Media" ];
+          "Win32_Media_Multimedia" = [ "Win32_Media" ];
+          "Win32_Media_Speech" = [ "Win32_Media" ];
+          "Win32_Media_Streaming" = [ "Win32_Media" ];
+          "Win32_Media_WindowsMediaFormat" = [ "Win32_Media" ];
+          "Win32_NetworkManagement" = [ "Win32" ];
+          "Win32_NetworkManagement_Dhcp" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_Dns" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_InternetConnectionWizard" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_IpHelper" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_MobileBroadband" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_Multicast" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_Ndis" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_NetBios" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_NetManagement" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_NetShell" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_NetworkDiagnosticsFramework" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_NetworkPolicyServer" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_P2P" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_QoS" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_Rras" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_Snmp" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WNet" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WebDav" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WiFi" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WindowsConnectNow" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WindowsConnectionManager" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WindowsFilteringPlatform" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WindowsFirewall" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WindowsNetworkVirtualization" = [ "Win32_NetworkManagement" ];
+          "Win32_Networking" = [ "Win32" ];
+          "Win32_Networking_ActiveDirectory" = [ "Win32_Networking" ];
+          "Win32_Networking_BackgroundIntelligentTransferService" = [ "Win32_Networking" ];
+          "Win32_Networking_Clustering" = [ "Win32_Networking" ];
+          "Win32_Networking_HttpServer" = [ "Win32_Networking" ];
+          "Win32_Networking_Ldap" = [ "Win32_Networking" ];
+          "Win32_Networking_NetworkListManager" = [ "Win32_Networking" ];
+          "Win32_Networking_RemoteDifferentialCompression" = [ "Win32_Networking" ];
+          "Win32_Networking_WebSocket" = [ "Win32_Networking" ];
+          "Win32_Networking_WinHttp" = [ "Win32_Networking" ];
+          "Win32_Networking_WinInet" = [ "Win32_Networking" ];
+          "Win32_Networking_WinSock" = [ "Win32_Networking" ];
+          "Win32_Networking_WindowsWebServices" = [ "Win32_Networking" ];
+          "Win32_Security" = [ "Win32" ];
+          "Win32_Security_AppLocker" = [ "Win32_Security" ];
+          "Win32_Security_Authentication" = [ "Win32_Security" ];
+          "Win32_Security_Authentication_Identity" = [ "Win32_Security_Authentication" ];
+          "Win32_Security_Authentication_Identity_Provider" = [ "Win32_Security_Authentication_Identity" ];
+          "Win32_Security_Authorization" = [ "Win32_Security" ];
+          "Win32_Security_Authorization_UI" = [ "Win32_Security_Authorization" ];
+          "Win32_Security_ConfigurationSnapin" = [ "Win32_Security" ];
+          "Win32_Security_Credentials" = [ "Win32_Security" ];
+          "Win32_Security_Cryptography" = [ "Win32_Security" ];
+          "Win32_Security_Cryptography_Catalog" = [ "Win32_Security_Cryptography" ];
+          "Win32_Security_Cryptography_Certificates" = [ "Win32_Security_Cryptography" ];
+          "Win32_Security_Cryptography_Sip" = [ "Win32_Security_Cryptography" ];
+          "Win32_Security_Cryptography_UI" = [ "Win32_Security_Cryptography" ];
+          "Win32_Security_DiagnosticDataQuery" = [ "Win32_Security" ];
+          "Win32_Security_DirectoryServices" = [ "Win32_Security" ];
+          "Win32_Security_EnterpriseData" = [ "Win32_Security" ];
+          "Win32_Security_ExtensibleAuthenticationProtocol" = [ "Win32_Security" ];
+          "Win32_Security_Isolation" = [ "Win32_Security" ];
+          "Win32_Security_LicenseProtection" = [ "Win32_Security" ];
+          "Win32_Security_NetworkAccessProtection" = [ "Win32_Security" ];
+          "Win32_Security_Tpm" = [ "Win32_Security" ];
+          "Win32_Security_WinTrust" = [ "Win32_Security" ];
+          "Win32_Security_WinWlx" = [ "Win32_Security" ];
+          "Win32_Storage" = [ "Win32" ];
+          "Win32_Storage_Cabinets" = [ "Win32_Storage" ];
+          "Win32_Storage_CloudFilters" = [ "Win32_Storage" ];
+          "Win32_Storage_Compression" = [ "Win32_Storage" ];
+          "Win32_Storage_DataDeduplication" = [ "Win32_Storage" ];
+          "Win32_Storage_DistributedFileSystem" = [ "Win32_Storage" ];
+          "Win32_Storage_EnhancedStorage" = [ "Win32_Storage" ];
+          "Win32_Storage_FileHistory" = [ "Win32_Storage" ];
+          "Win32_Storage_FileServerResourceManager" = [ "Win32_Storage" ];
+          "Win32_Storage_FileSystem" = [ "Win32_Storage" ];
+          "Win32_Storage_Imapi" = [ "Win32_Storage" ];
+          "Win32_Storage_IndexServer" = [ "Win32_Storage" ];
+          "Win32_Storage_InstallableFileSystems" = [ "Win32_Storage" ];
+          "Win32_Storage_IscsiDisc" = [ "Win32_Storage" ];
+          "Win32_Storage_Jet" = [ "Win32_Storage" ];
+          "Win32_Storage_OfflineFiles" = [ "Win32_Storage" ];
+          "Win32_Storage_OperationRecorder" = [ "Win32_Storage" ];
+          "Win32_Storage_Packaging" = [ "Win32_Storage" ];
+          "Win32_Storage_Packaging_Appx" = [ "Win32_Storage_Packaging" ];
+          "Win32_Storage_Packaging_Opc" = [ "Win32_Storage_Packaging" ];
+          "Win32_Storage_ProjectedFileSystem" = [ "Win32_Storage" ];
+          "Win32_Storage_StructuredStorage" = [ "Win32_Storage" ];
+          "Win32_Storage_Vhd" = [ "Win32_Storage" ];
+          "Win32_Storage_VirtualDiskService" = [ "Win32_Storage" ];
+          "Win32_Storage_Vss" = [ "Win32_Storage" ];
+          "Win32_Storage_Xps" = [ "Win32_Storage" ];
+          "Win32_Storage_Xps_Printing" = [ "Win32_Storage_Xps" ];
+          "Win32_System" = [ "Win32" ];
+          "Win32_System_AddressBook" = [ "Win32_System" ];
+          "Win32_System_Antimalware" = [ "Win32_System" ];
+          "Win32_System_ApplicationInstallationAndServicing" = [ "Win32_System" ];
+          "Win32_System_ApplicationVerifier" = [ "Win32_System" ];
+          "Win32_System_AssessmentTool" = [ "Win32_System" ];
+          "Win32_System_ClrHosting" = [ "Win32_System" ];
+          "Win32_System_Com" = [ "Win32_System" ];
+          "Win32_System_Com_CallObj" = [ "Win32_System_Com" ];
+          "Win32_System_Com_ChannelCredentials" = [ "Win32_System_Com" ];
+          "Win32_System_Com_Events" = [ "Win32_System_Com" ];
+          "Win32_System_Com_Marshal" = [ "Win32_System_Com" ];
+          "Win32_System_Com_StructuredStorage" = [ "Win32_System_Com" ];
+          "Win32_System_Com_UI" = [ "Win32_System_Com" ];
+          "Win32_System_Com_Urlmon" = [ "Win32_System_Com" ];
+          "Win32_System_ComponentServices" = [ "Win32_System" ];
+          "Win32_System_Console" = [ "Win32_System" ];
+          "Win32_System_Contacts" = [ "Win32_System" ];
+          "Win32_System_CorrelationVector" = [ "Win32_System" ];
+          "Win32_System_DataExchange" = [ "Win32_System" ];
+          "Win32_System_DeploymentServices" = [ "Win32_System" ];
+          "Win32_System_DesktopSharing" = [ "Win32_System" ];
+          "Win32_System_DeveloperLicensing" = [ "Win32_System" ];
+          "Win32_System_Diagnostics" = [ "Win32_System" ];
+          "Win32_System_Diagnostics_Ceip" = [ "Win32_System_Diagnostics" ];
+          "Win32_System_Diagnostics_ClrProfiling" = [ "Win32_System_Diagnostics" ];
+          "Win32_System_Diagnostics_Debug" = [ "Win32_System_Diagnostics" ];
+          "Win32_System_Diagnostics_Debug_ActiveScript" = [ "Win32_System_Diagnostics_Debug" ];
+          "Win32_System_Diagnostics_Debug_Extensions" = [ "Win32_System_Diagnostics_Debug" ];
+          "Win32_System_Diagnostics_Etw" = [ "Win32_System_Diagnostics" ];
+          "Win32_System_Diagnostics_ProcessSnapshotting" = [ "Win32_System_Diagnostics" ];
+          "Win32_System_Diagnostics_ToolHelp" = [ "Win32_System_Diagnostics" ];
+          "Win32_System_DistributedTransactionCoordinator" = [ "Win32_System" ];
+          "Win32_System_Environment" = [ "Win32_System" ];
+          "Win32_System_ErrorReporting" = [ "Win32_System" ];
+          "Win32_System_EventCollector" = [ "Win32_System" ];
+          "Win32_System_EventLog" = [ "Win32_System" ];
+          "Win32_System_EventNotificationService" = [ "Win32_System" ];
+          "Win32_System_GroupPolicy" = [ "Win32_System" ];
+          "Win32_System_HostCompute" = [ "Win32_System" ];
+          "Win32_System_HostComputeNetwork" = [ "Win32_System" ];
+          "Win32_System_HostComputeSystem" = [ "Win32_System" ];
+          "Win32_System_Hypervisor" = [ "Win32_System" ];
+          "Win32_System_IO" = [ "Win32_System" ];
+          "Win32_System_Iis" = [ "Win32_System" ];
+          "Win32_System_Ioctl" = [ "Win32_System" ];
+          "Win32_System_JobObjects" = [ "Win32_System" ];
+          "Win32_System_Js" = [ "Win32_System" ];
+          "Win32_System_Kernel" = [ "Win32_System" ];
+          "Win32_System_LibraryLoader" = [ "Win32_System" ];
+          "Win32_System_Mailslots" = [ "Win32_System" ];
+          "Win32_System_Mapi" = [ "Win32_System" ];
+          "Win32_System_Memory" = [ "Win32_System" ];
+          "Win32_System_Memory_NonVolatile" = [ "Win32_System_Memory" ];
+          "Win32_System_MessageQueuing" = [ "Win32_System" ];
+          "Win32_System_MixedReality" = [ "Win32_System" ];
+          "Win32_System_Mmc" = [ "Win32_System" ];
+          "Win32_System_Ole" = [ "Win32_System" ];
+          "Win32_System_ParentalControls" = [ "Win32_System" ];
+          "Win32_System_PasswordManagement" = [ "Win32_System" ];
+          "Win32_System_Performance" = [ "Win32_System" ];
+          "Win32_System_Performance_HardwareCounterProfiling" = [ "Win32_System_Performance" ];
+          "Win32_System_Pipes" = [ "Win32_System" ];
+          "Win32_System_Power" = [ "Win32_System" ];
+          "Win32_System_ProcessStatus" = [ "Win32_System" ];
+          "Win32_System_RealTimeCommunications" = [ "Win32_System" ];
+          "Win32_System_Recovery" = [ "Win32_System" ];
+          "Win32_System_Registry" = [ "Win32_System" ];
+          "Win32_System_RemoteAssistance" = [ "Win32_System" ];
+          "Win32_System_RemoteDesktop" = [ "Win32_System" ];
+          "Win32_System_RemoteManagement" = [ "Win32_System" ];
+          "Win32_System_RestartManager" = [ "Win32_System" ];
+          "Win32_System_Restore" = [ "Win32_System" ];
+          "Win32_System_Rpc" = [ "Win32_System" ];
+          "Win32_System_Search" = [ "Win32_System" ];
+          "Win32_System_Search_Common" = [ "Win32_System_Search" ];
+          "Win32_System_SecurityCenter" = [ "Win32_System" ];
+          "Win32_System_ServerBackup" = [ "Win32_System" ];
+          "Win32_System_Services" = [ "Win32_System" ];
+          "Win32_System_SettingsManagementInfrastructure" = [ "Win32_System" ];
+          "Win32_System_SetupAndMigration" = [ "Win32_System" ];
+          "Win32_System_Shutdown" = [ "Win32_System" ];
+          "Win32_System_StationsAndDesktops" = [ "Win32_System" ];
+          "Win32_System_SubsystemForLinux" = [ "Win32_System" ];
+          "Win32_System_SystemInformation" = [ "Win32_System" ];
+          "Win32_System_SystemServices" = [ "Win32_System" ];
+          "Win32_System_TaskScheduler" = [ "Win32_System" ];
+          "Win32_System_Threading" = [ "Win32_System" ];
+          "Win32_System_Time" = [ "Win32_System" ];
+          "Win32_System_TpmBaseServices" = [ "Win32_System" ];
+          "Win32_System_UpdateAgent" = [ "Win32_System" ];
+          "Win32_System_UpdateAssessment" = [ "Win32_System" ];
+          "Win32_System_UserAccessLogging" = [ "Win32_System" ];
+          "Win32_System_VirtualDosMachines" = [ "Win32_System" ];
+          "Win32_System_WindowsProgramming" = [ "Win32_System" ];
+          "Win32_System_WindowsSync" = [ "Win32_System" ];
+          "Win32_System_Wmi" = [ "Win32_System" ];
+          "Win32_UI" = [ "Win32" ];
+          "Win32_UI_Accessibility" = [ "Win32_UI" ];
+          "Win32_UI_Animation" = [ "Win32_UI" ];
+          "Win32_UI_ColorSystem" = [ "Win32_UI" ];
+          "Win32_UI_Controls" = [ "Win32_UI" ];
+          "Win32_UI_Controls_Dialogs" = [ "Win32_UI_Controls" ];
+          "Win32_UI_Controls_RichEdit" = [ "Win32_UI_Controls" ];
+          "Win32_UI_HiDpi" = [ "Win32_UI" ];
+          "Win32_UI_Input" = [ "Win32_UI" ];
+          "Win32_UI_Input_Ime" = [ "Win32_UI_Input" ];
+          "Win32_UI_Input_Ink" = [ "Win32_UI_Input" ];
+          "Win32_UI_Input_KeyboardAndMouse" = [ "Win32_UI_Input" ];
+          "Win32_UI_Input_Pointer" = [ "Win32_UI_Input" ];
+          "Win32_UI_Input_Radial" = [ "Win32_UI_Input" ];
+          "Win32_UI_Input_Touch" = [ "Win32_UI_Input" ];
+          "Win32_UI_Input_XboxController" = [ "Win32_UI_Input" ];
+          "Win32_UI_InteractionContext" = [ "Win32_UI" ];
+          "Win32_UI_LegacyWindowsEnvironmentFeatures" = [ "Win32_UI" ];
+          "Win32_UI_Magnification" = [ "Win32_UI" ];
+          "Win32_UI_Notifications" = [ "Win32_UI" ];
+          "Win32_UI_Ribbon" = [ "Win32_UI" ];
+          "Win32_UI_Shell" = [ "Win32_UI" ];
+          "Win32_UI_Shell_Common" = [ "Win32_UI_Shell" ];
+          "Win32_UI_Shell_PropertiesSystem" = [ "Win32_UI_Shell" ];
+          "Win32_UI_TabletPC" = [ "Win32_UI" ];
+          "Win32_UI_TextServices" = [ "Win32_UI" ];
+          "Win32_UI_WindowsAndMessaging" = [ "Win32_UI" ];
+          "Win32_UI_Wpf" = [ "Win32_UI" ];
+          "Win32_Web" = [ "Win32" ];
+          "Win32_Web_InternetExplorer" = [ "Win32_Web" ];
+        };
+        resolvedDefaultFeatures = [ "Win32" "Win32_Foundation" "Win32_NetworkManagement" "Win32_NetworkManagement_IpHelper" "Win32_Networking" "Win32_Networking_WinSock" "Win32_Security" "Win32_Security_Authentication" "Win32_Security_Authentication_Identity" "Win32_Security_Credentials" "Win32_Security_Cryptography" "Win32_Storage" "Win32_Storage_FileSystem" "Win32_System" "Win32_System_Console" "Win32_System_Diagnostics" "Win32_System_Diagnostics_Debug" "Win32_System_IO" "Win32_System_Memory" "Win32_System_Pipes" "Win32_System_SystemServices" "Win32_System_Threading" "Win32_System_WindowsProgramming" "Win32_UI" "Win32_UI_Shell" "default" ];
+      };
+      "windows-sys 0.52.0" = rec {
+        crateName = "windows-sys";
+        version = "0.52.0";
+        edition = "2021";
+        sha256 = "0gd3v4ji88490zgb6b5mq5zgbvwv7zx1ibn8v3x83rwcdbryaar8";
+        authors = [
+          "Microsoft"
+        ];
+        dependencies = [
+          {
+            name = "windows-targets";
+            packageId = "windows-targets 0.52.0";
+          }
+        ];
+        features = {
+          "Wdk_Foundation" = [ "Wdk" ];
+          "Wdk_Graphics" = [ "Wdk" ];
+          "Wdk_Graphics_Direct3D" = [ "Wdk_Graphics" ];
+          "Wdk_Storage" = [ "Wdk" ];
+          "Wdk_Storage_FileSystem" = [ "Wdk_Storage" ];
+          "Wdk_Storage_FileSystem_Minifilters" = [ "Wdk_Storage_FileSystem" ];
+          "Wdk_System" = [ "Wdk" ];
+          "Wdk_System_IO" = [ "Wdk_System" ];
+          "Wdk_System_OfflineRegistry" = [ "Wdk_System" ];
+          "Wdk_System_Registry" = [ "Wdk_System" ];
+          "Wdk_System_SystemInformation" = [ "Wdk_System" ];
+          "Wdk_System_SystemServices" = [ "Wdk_System" ];
+          "Wdk_System_Threading" = [ "Wdk_System" ];
+          "Win32_Data" = [ "Win32" ];
+          "Win32_Data_HtmlHelp" = [ "Win32_Data" ];
+          "Win32_Data_RightsManagement" = [ "Win32_Data" ];
+          "Win32_Devices" = [ "Win32" ];
+          "Win32_Devices_AllJoyn" = [ "Win32_Devices" ];
+          "Win32_Devices_BiometricFramework" = [ "Win32_Devices" ];
+          "Win32_Devices_Bluetooth" = [ "Win32_Devices" ];
+          "Win32_Devices_Communication" = [ "Win32_Devices" ];
+          "Win32_Devices_DeviceAndDriverInstallation" = [ "Win32_Devices" ];
+          "Win32_Devices_DeviceQuery" = [ "Win32_Devices" ];
+          "Win32_Devices_Display" = [ "Win32_Devices" ];
+          "Win32_Devices_Enumeration" = [ "Win32_Devices" ];
+          "Win32_Devices_Enumeration_Pnp" = [ "Win32_Devices_Enumeration" ];
+          "Win32_Devices_Fax" = [ "Win32_Devices" ];
+          "Win32_Devices_HumanInterfaceDevice" = [ "Win32_Devices" ];
+          "Win32_Devices_PortableDevices" = [ "Win32_Devices" ];
+          "Win32_Devices_Properties" = [ "Win32_Devices" ];
+          "Win32_Devices_Pwm" = [ "Win32_Devices" ];
+          "Win32_Devices_Sensors" = [ "Win32_Devices" ];
+          "Win32_Devices_SerialCommunication" = [ "Win32_Devices" ];
+          "Win32_Devices_Tapi" = [ "Win32_Devices" ];
+          "Win32_Devices_Usb" = [ "Win32_Devices" ];
+          "Win32_Devices_WebServicesOnDevices" = [ "Win32_Devices" ];
+          "Win32_Foundation" = [ "Win32" ];
+          "Win32_Gaming" = [ "Win32" ];
+          "Win32_Globalization" = [ "Win32" ];
+          "Win32_Graphics" = [ "Win32" ];
+          "Win32_Graphics_Dwm" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Gdi" = [ "Win32_Graphics" ];
+          "Win32_Graphics_GdiPlus" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Hlsl" = [ "Win32_Graphics" ];
+          "Win32_Graphics_OpenGL" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Printing" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Printing_PrintTicket" = [ "Win32_Graphics_Printing" ];
+          "Win32_Management" = [ "Win32" ];
+          "Win32_Management_MobileDeviceManagementRegistration" = [ "Win32_Management" ];
+          "Win32_Media" = [ "Win32" ];
+          "Win32_Media_Audio" = [ "Win32_Media" ];
+          "Win32_Media_DxMediaObjects" = [ "Win32_Media" ];
+          "Win32_Media_KernelStreaming" = [ "Win32_Media" ];
+          "Win32_Media_Multimedia" = [ "Win32_Media" ];
+          "Win32_Media_Streaming" = [ "Win32_Media" ];
+          "Win32_Media_WindowsMediaFormat" = [ "Win32_Media" ];
+          "Win32_NetworkManagement" = [ "Win32" ];
+          "Win32_NetworkManagement_Dhcp" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_Dns" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_InternetConnectionWizard" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_IpHelper" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_Multicast" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_Ndis" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_NetBios" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_NetManagement" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_NetShell" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_NetworkDiagnosticsFramework" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_P2P" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_QoS" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_Rras" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_Snmp" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WNet" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WebDav" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WiFi" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WindowsConnectionManager" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WindowsFilteringPlatform" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WindowsFirewall" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WindowsNetworkVirtualization" = [ "Win32_NetworkManagement" ];
+          "Win32_Networking" = [ "Win32" ];
+          "Win32_Networking_ActiveDirectory" = [ "Win32_Networking" ];
+          "Win32_Networking_Clustering" = [ "Win32_Networking" ];
+          "Win32_Networking_HttpServer" = [ "Win32_Networking" ];
+          "Win32_Networking_Ldap" = [ "Win32_Networking" ];
+          "Win32_Networking_WebSocket" = [ "Win32_Networking" ];
+          "Win32_Networking_WinHttp" = [ "Win32_Networking" ];
+          "Win32_Networking_WinInet" = [ "Win32_Networking" ];
+          "Win32_Networking_WinSock" = [ "Win32_Networking" ];
+          "Win32_Networking_WindowsWebServices" = [ "Win32_Networking" ];
+          "Win32_Security" = [ "Win32" ];
+          "Win32_Security_AppLocker" = [ "Win32_Security" ];
+          "Win32_Security_Authentication" = [ "Win32_Security" ];
+          "Win32_Security_Authentication_Identity" = [ "Win32_Security_Authentication" ];
+          "Win32_Security_Authorization" = [ "Win32_Security" ];
+          "Win32_Security_Credentials" = [ "Win32_Security" ];
+          "Win32_Security_Cryptography" = [ "Win32_Security" ];
+          "Win32_Security_Cryptography_Catalog" = [ "Win32_Security_Cryptography" ];
+          "Win32_Security_Cryptography_Certificates" = [ "Win32_Security_Cryptography" ];
+          "Win32_Security_Cryptography_Sip" = [ "Win32_Security_Cryptography" ];
+          "Win32_Security_Cryptography_UI" = [ "Win32_Security_Cryptography" ];
+          "Win32_Security_DiagnosticDataQuery" = [ "Win32_Security" ];
+          "Win32_Security_DirectoryServices" = [ "Win32_Security" ];
+          "Win32_Security_EnterpriseData" = [ "Win32_Security" ];
+          "Win32_Security_ExtensibleAuthenticationProtocol" = [ "Win32_Security" ];
+          "Win32_Security_Isolation" = [ "Win32_Security" ];
+          "Win32_Security_LicenseProtection" = [ "Win32_Security" ];
+          "Win32_Security_NetworkAccessProtection" = [ "Win32_Security" ];
+          "Win32_Security_WinTrust" = [ "Win32_Security" ];
+          "Win32_Security_WinWlx" = [ "Win32_Security" ];
+          "Win32_Storage" = [ "Win32" ];
+          "Win32_Storage_Cabinets" = [ "Win32_Storage" ];
+          "Win32_Storage_CloudFilters" = [ "Win32_Storage" ];
+          "Win32_Storage_Compression" = [ "Win32_Storage" ];
+          "Win32_Storage_DistributedFileSystem" = [ "Win32_Storage" ];
+          "Win32_Storage_FileHistory" = [ "Win32_Storage" ];
+          "Win32_Storage_FileSystem" = [ "Win32_Storage" ];
+          "Win32_Storage_Imapi" = [ "Win32_Storage" ];
+          "Win32_Storage_IndexServer" = [ "Win32_Storage" ];
+          "Win32_Storage_InstallableFileSystems" = [ "Win32_Storage" ];
+          "Win32_Storage_IscsiDisc" = [ "Win32_Storage" ];
+          "Win32_Storage_Jet" = [ "Win32_Storage" ];
+          "Win32_Storage_Nvme" = [ "Win32_Storage" ];
+          "Win32_Storage_OfflineFiles" = [ "Win32_Storage" ];
+          "Win32_Storage_OperationRecorder" = [ "Win32_Storage" ];
+          "Win32_Storage_Packaging" = [ "Win32_Storage" ];
+          "Win32_Storage_Packaging_Appx" = [ "Win32_Storage_Packaging" ];
+          "Win32_Storage_ProjectedFileSystem" = [ "Win32_Storage" ];
+          "Win32_Storage_StructuredStorage" = [ "Win32_Storage" ];
+          "Win32_Storage_Vhd" = [ "Win32_Storage" ];
+          "Win32_Storage_Xps" = [ "Win32_Storage" ];
+          "Win32_System" = [ "Win32" ];
+          "Win32_System_AddressBook" = [ "Win32_System" ];
+          "Win32_System_Antimalware" = [ "Win32_System" ];
+          "Win32_System_ApplicationInstallationAndServicing" = [ "Win32_System" ];
+          "Win32_System_ApplicationVerifier" = [ "Win32_System" ];
+          "Win32_System_ClrHosting" = [ "Win32_System" ];
+          "Win32_System_Com" = [ "Win32_System" ];
+          "Win32_System_Com_Marshal" = [ "Win32_System_Com" ];
+          "Win32_System_Com_StructuredStorage" = [ "Win32_System_Com" ];
+          "Win32_System_Com_Urlmon" = [ "Win32_System_Com" ];
+          "Win32_System_ComponentServices" = [ "Win32_System" ];
+          "Win32_System_Console" = [ "Win32_System" ];
+          "Win32_System_CorrelationVector" = [ "Win32_System" ];
+          "Win32_System_DataExchange" = [ "Win32_System" ];
+          "Win32_System_DeploymentServices" = [ "Win32_System" ];
+          "Win32_System_DeveloperLicensing" = [ "Win32_System" ];
+          "Win32_System_Diagnostics" = [ "Win32_System" ];
+          "Win32_System_Diagnostics_Ceip" = [ "Win32_System_Diagnostics" ];
+          "Win32_System_Diagnostics_Debug" = [ "Win32_System_Diagnostics" ];
+          "Win32_System_Diagnostics_Debug_Extensions" = [ "Win32_System_Diagnostics_Debug" ];
+          "Win32_System_Diagnostics_Etw" = [ "Win32_System_Diagnostics" ];
+          "Win32_System_Diagnostics_ProcessSnapshotting" = [ "Win32_System_Diagnostics" ];
+          "Win32_System_Diagnostics_ToolHelp" = [ "Win32_System_Diagnostics" ];
+          "Win32_System_DistributedTransactionCoordinator" = [ "Win32_System" ];
+          "Win32_System_Environment" = [ "Win32_System" ];
+          "Win32_System_ErrorReporting" = [ "Win32_System" ];
+          "Win32_System_EventCollector" = [ "Win32_System" ];
+          "Win32_System_EventLog" = [ "Win32_System" ];
+          "Win32_System_EventNotificationService" = [ "Win32_System" ];
+          "Win32_System_GroupPolicy" = [ "Win32_System" ];
+          "Win32_System_HostCompute" = [ "Win32_System" ];
+          "Win32_System_HostComputeNetwork" = [ "Win32_System" ];
+          "Win32_System_HostComputeSystem" = [ "Win32_System" ];
+          "Win32_System_Hypervisor" = [ "Win32_System" ];
+          "Win32_System_IO" = [ "Win32_System" ];
+          "Win32_System_Iis" = [ "Win32_System" ];
+          "Win32_System_Ioctl" = [ "Win32_System" ];
+          "Win32_System_JobObjects" = [ "Win32_System" ];
+          "Win32_System_Js" = [ "Win32_System" ];
+          "Win32_System_Kernel" = [ "Win32_System" ];
+          "Win32_System_LibraryLoader" = [ "Win32_System" ];
+          "Win32_System_Mailslots" = [ "Win32_System" ];
+          "Win32_System_Mapi" = [ "Win32_System" ];
+          "Win32_System_Memory" = [ "Win32_System" ];
+          "Win32_System_Memory_NonVolatile" = [ "Win32_System_Memory" ];
+          "Win32_System_MessageQueuing" = [ "Win32_System" ];
+          "Win32_System_MixedReality" = [ "Win32_System" ];
+          "Win32_System_Ole" = [ "Win32_System" ];
+          "Win32_System_PasswordManagement" = [ "Win32_System" ];
+          "Win32_System_Performance" = [ "Win32_System" ];
+          "Win32_System_Performance_HardwareCounterProfiling" = [ "Win32_System_Performance" ];
+          "Win32_System_Pipes" = [ "Win32_System" ];
+          "Win32_System_Power" = [ "Win32_System" ];
+          "Win32_System_ProcessStatus" = [ "Win32_System" ];
+          "Win32_System_Recovery" = [ "Win32_System" ];
+          "Win32_System_Registry" = [ "Win32_System" ];
+          "Win32_System_RemoteDesktop" = [ "Win32_System" ];
+          "Win32_System_RemoteManagement" = [ "Win32_System" ];
+          "Win32_System_RestartManager" = [ "Win32_System" ];
+          "Win32_System_Restore" = [ "Win32_System" ];
+          "Win32_System_Rpc" = [ "Win32_System" ];
+          "Win32_System_Search" = [ "Win32_System" ];
+          "Win32_System_Search_Common" = [ "Win32_System_Search" ];
+          "Win32_System_SecurityCenter" = [ "Win32_System" ];
+          "Win32_System_Services" = [ "Win32_System" ];
+          "Win32_System_SetupAndMigration" = [ "Win32_System" ];
+          "Win32_System_Shutdown" = [ "Win32_System" ];
+          "Win32_System_StationsAndDesktops" = [ "Win32_System" ];
+          "Win32_System_SubsystemForLinux" = [ "Win32_System" ];
+          "Win32_System_SystemInformation" = [ "Win32_System" ];
+          "Win32_System_SystemServices" = [ "Win32_System" ];
+          "Win32_System_Threading" = [ "Win32_System" ];
+          "Win32_System_Time" = [ "Win32_System" ];
+          "Win32_System_TpmBaseServices" = [ "Win32_System" ];
+          "Win32_System_UserAccessLogging" = [ "Win32_System" ];
+          "Win32_System_Variant" = [ "Win32_System" ];
+          "Win32_System_VirtualDosMachines" = [ "Win32_System" ];
+          "Win32_System_WindowsProgramming" = [ "Win32_System" ];
+          "Win32_System_Wmi" = [ "Win32_System" ];
+          "Win32_UI" = [ "Win32" ];
+          "Win32_UI_Accessibility" = [ "Win32_UI" ];
+          "Win32_UI_ColorSystem" = [ "Win32_UI" ];
+          "Win32_UI_Controls" = [ "Win32_UI" ];
+          "Win32_UI_Controls_Dialogs" = [ "Win32_UI_Controls" ];
+          "Win32_UI_HiDpi" = [ "Win32_UI" ];
+          "Win32_UI_Input" = [ "Win32_UI" ];
+          "Win32_UI_Input_Ime" = [ "Win32_UI_Input" ];
+          "Win32_UI_Input_KeyboardAndMouse" = [ "Win32_UI_Input" ];
+          "Win32_UI_Input_Pointer" = [ "Win32_UI_Input" ];
+          "Win32_UI_Input_Touch" = [ "Win32_UI_Input" ];
+          "Win32_UI_Input_XboxController" = [ "Win32_UI_Input" ];
+          "Win32_UI_InteractionContext" = [ "Win32_UI" ];
+          "Win32_UI_Magnification" = [ "Win32_UI" ];
+          "Win32_UI_Shell" = [ "Win32_UI" ];
+          "Win32_UI_Shell_PropertiesSystem" = [ "Win32_UI_Shell" ];
+          "Win32_UI_TabletPC" = [ "Win32_UI" ];
+          "Win32_UI_TextServices" = [ "Win32_UI" ];
+          "Win32_UI_WindowsAndMessaging" = [ "Win32_UI" ];
+          "Win32_Web" = [ "Win32" ];
+          "Win32_Web_InternetExplorer" = [ "Win32_Web" ];
+        };
+        resolvedDefaultFeatures = [ "Win32" "Win32_Foundation" "Win32_System" "Win32_System_Console" "default" ];
+      };
+      "windows-targets 0.42.2" = rec {
+        crateName = "windows-targets";
+        version = "0.42.2";
+        edition = "2018";
+        sha256 = "0wfhnib2fisxlx8c507dbmh97kgij4r6kcxdi0f9nk6l1k080lcf";
+        authors = [
+          "Microsoft"
+        ];
+        dependencies = [
+          {
+            name = "windows_aarch64_gnullvm";
+            packageId = "windows_aarch64_gnullvm 0.42.2";
+            target = { target, features }: (pkgs.rust.lib.toRustTarget stdenv.hostPlatform == "aarch64-pc-windows-gnullvm");
+          }
+          {
+            name = "windows_aarch64_msvc";
+            packageId = "windows_aarch64_msvc 0.42.2";
+            target = { target, features }: (pkgs.rust.lib.toRustTarget stdenv.hostPlatform == "aarch64-pc-windows-msvc");
+          }
+          {
+            name = "windows_aarch64_msvc";
+            packageId = "windows_aarch64_msvc 0.42.2";
+            target = { target, features }: (pkgs.rust.lib.toRustTarget stdenv.hostPlatform == "aarch64-uwp-windows-msvc");
+          }
+          {
+            name = "windows_i686_gnu";
+            packageId = "windows_i686_gnu 0.42.2";
+            target = { target, features }: (pkgs.rust.lib.toRustTarget stdenv.hostPlatform == "i686-pc-windows-gnu");
+          }
+          {
+            name = "windows_i686_gnu";
+            packageId = "windows_i686_gnu 0.42.2";
+            target = { target, features }: (pkgs.rust.lib.toRustTarget stdenv.hostPlatform == "i686-uwp-windows-gnu");
+          }
+          {
+            name = "windows_i686_msvc";
+            packageId = "windows_i686_msvc 0.42.2";
+            target = { target, features }: (pkgs.rust.lib.toRustTarget stdenv.hostPlatform == "i686-pc-windows-msvc");
+          }
+          {
+            name = "windows_i686_msvc";
+            packageId = "windows_i686_msvc 0.42.2";
+            target = { target, features }: (pkgs.rust.lib.toRustTarget stdenv.hostPlatform == "i686-uwp-windows-msvc");
+          }
+          {
+            name = "windows_x86_64_gnu";
+            packageId = "windows_x86_64_gnu 0.42.2";
+            target = { target, features }: (pkgs.rust.lib.toRustTarget stdenv.hostPlatform == "x86_64-pc-windows-gnu");
+          }
+          {
+            name = "windows_x86_64_gnu";
+            packageId = "windows_x86_64_gnu 0.42.2";
+            target = { target, features }: (pkgs.rust.lib.toRustTarget stdenv.hostPlatform == "x86_64-uwp-windows-gnu");
+          }
+          {
+            name = "windows_x86_64_gnullvm";
+            packageId = "windows_x86_64_gnullvm 0.42.2";
+            target = { target, features }: (pkgs.rust.lib.toRustTarget stdenv.hostPlatform == "x86_64-pc-windows-gnullvm");
+          }
+          {
+            name = "windows_x86_64_msvc";
+            packageId = "windows_x86_64_msvc 0.42.2";
+            target = { target, features }: (pkgs.rust.lib.toRustTarget stdenv.hostPlatform == "x86_64-pc-windows-msvc");
+          }
+          {
+            name = "windows_x86_64_msvc";
+            packageId = "windows_x86_64_msvc 0.42.2";
+            target = { target, features }: (pkgs.rust.lib.toRustTarget stdenv.hostPlatform == "x86_64-uwp-windows-msvc");
+          }
+        ];
+
+      };
+      "windows-targets 0.48.5" = rec {
+        crateName = "windows-targets";
+        version = "0.48.5";
+        edition = "2018";
+        sha256 = "034ljxqshifs1lan89xwpcy1hp0lhdh4b5n0d2z4fwjx2piacbws";
+        authors = [
+          "Microsoft"
+        ];
+        dependencies = [
+          {
+            name = "windows_aarch64_gnullvm";
+            packageId = "windows_aarch64_gnullvm 0.48.5";
+            target = { target, features }: (pkgs.rust.lib.toRustTarget stdenv.hostPlatform == "aarch64-pc-windows-gnullvm");
+          }
+          {
+            name = "windows_aarch64_msvc";
+            packageId = "windows_aarch64_msvc 0.48.5";
+            target = { target, features }: (("aarch64" == target."arch" or null) && ("msvc" == target."env" or null) && (!(target."windows_raw_dylib" or false)));
+          }
+          {
+            name = "windows_i686_gnu";
+            packageId = "windows_i686_gnu 0.48.5";
+            target = { target, features }: (("x86" == target."arch" or null) && ("gnu" == target."env" or null) && (!(target."windows_raw_dylib" or false)));
+          }
+          {
+            name = "windows_i686_msvc";
+            packageId = "windows_i686_msvc 0.48.5";
+            target = { target, features }: (("x86" == target."arch" or null) && ("msvc" == target."env" or null) && (!(target."windows_raw_dylib" or false)));
+          }
+          {
+            name = "windows_x86_64_gnu";
+            packageId = "windows_x86_64_gnu 0.48.5";
+            target = { target, features }: (("x86_64" == target."arch" or null) && ("gnu" == target."env" or null) && (!("llvm" == target."abi" or null)) && (!(target."windows_raw_dylib" or false)));
+          }
+          {
+            name = "windows_x86_64_gnullvm";
+            packageId = "windows_x86_64_gnullvm 0.48.5";
+            target = { target, features }: (pkgs.rust.lib.toRustTarget stdenv.hostPlatform == "x86_64-pc-windows-gnullvm");
+          }
+          {
+            name = "windows_x86_64_msvc";
+            packageId = "windows_x86_64_msvc 0.48.5";
+            target = { target, features }: (("x86_64" == target."arch" or null) && ("msvc" == target."env" or null) && (!(target."windows_raw_dylib" or false)));
+          }
+        ];
+
+      };
+      "windows-targets 0.52.0" = rec {
+        crateName = "windows-targets";
+        version = "0.52.0";
+        edition = "2021";
+        sha256 = "1kg7a27ynzw8zz3krdgy6w5gbqcji27j1sz4p7xk2j5j8082064a";
+        authors = [
+          "Microsoft"
+        ];
+        dependencies = [
+          {
+            name = "windows_aarch64_gnullvm";
+            packageId = "windows_aarch64_gnullvm 0.52.0";
+            target = { target, features }: (pkgs.rust.lib.toRustTarget stdenv.hostPlatform == "aarch64-pc-windows-gnullvm");
+          }
+          {
+            name = "windows_aarch64_msvc";
+            packageId = "windows_aarch64_msvc 0.52.0";
+            target = { target, features }: (("aarch64" == target."arch" or null) && ("msvc" == target."env" or null) && (!(target."windows_raw_dylib" or false)));
+          }
+          {
+            name = "windows_i686_gnu";
+            packageId = "windows_i686_gnu 0.52.0";
+            target = { target, features }: (("x86" == target."arch" or null) && ("gnu" == target."env" or null) && (!(target."windows_raw_dylib" or false)));
+          }
+          {
+            name = "windows_i686_msvc";
+            packageId = "windows_i686_msvc 0.52.0";
+            target = { target, features }: (("x86" == target."arch" or null) && ("msvc" == target."env" or null) && (!(target."windows_raw_dylib" or false)));
+          }
+          {
+            name = "windows_x86_64_gnu";
+            packageId = "windows_x86_64_gnu 0.52.0";
+            target = { target, features }: (("x86_64" == target."arch" or null) && ("gnu" == target."env" or null) && (!("llvm" == target."abi" or null)) && (!(target."windows_raw_dylib" or false)));
+          }
+          {
+            name = "windows_x86_64_gnullvm";
+            packageId = "windows_x86_64_gnullvm 0.52.0";
+            target = { target, features }: (pkgs.rust.lib.toRustTarget stdenv.hostPlatform == "x86_64-pc-windows-gnullvm");
+          }
+          {
+            name = "windows_x86_64_msvc";
+            packageId = "windows_x86_64_msvc 0.52.0";
+            target = { target, features }: (("x86_64" == target."arch" or null) && ("msvc" == target."env" or null) && (!(target."windows_raw_dylib" or false)));
+          }
+        ];
+
+      };
+      "windows_aarch64_gnullvm 0.42.2" = rec {
+        crateName = "windows_aarch64_gnullvm";
+        version = "0.42.2";
+        edition = "2018";
+        sha256 = "1y4q0qmvl0lvp7syxvfykafvmwal5hrjb4fmv04bqs0bawc52yjr";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "windows_aarch64_gnullvm 0.48.5" = rec {
+        crateName = "windows_aarch64_gnullvm";
+        version = "0.48.5";
+        edition = "2018";
+        sha256 = "1n05v7qblg1ci3i567inc7xrkmywczxrs1z3lj3rkkxw18py6f1b";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "windows_aarch64_gnullvm 0.52.0" = rec {
+        crateName = "windows_aarch64_gnullvm";
+        version = "0.52.0";
+        edition = "2021";
+        sha256 = "1shmn1kbdc0bpphcxz0vlph96bxz0h1jlmh93s9agf2dbpin8xyb";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "windows_aarch64_msvc 0.42.2" = rec {
+        crateName = "windows_aarch64_msvc";
+        version = "0.42.2";
+        edition = "2018";
+        sha256 = "0hsdikjl5sa1fva5qskpwlxzpc5q9l909fpl1w6yy1hglrj8i3p0";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "windows_aarch64_msvc 0.48.5" = rec {
+        crateName = "windows_aarch64_msvc";
+        version = "0.48.5";
+        edition = "2018";
+        sha256 = "1g5l4ry968p73g6bg6jgyvy9lb8fyhcs54067yzxpcpkf44k2dfw";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "windows_aarch64_msvc 0.52.0" = rec {
+        crateName = "windows_aarch64_msvc";
+        version = "0.52.0";
+        edition = "2021";
+        sha256 = "1vvmy1ypvzdvxn9yf0b8ygfl85gl2gpcyvsvqppsmlpisil07amv";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "windows_i686_gnu 0.42.2" = rec {
+        crateName = "windows_i686_gnu";
+        version = "0.42.2";
+        edition = "2018";
+        sha256 = "0kx866dfrby88lqs9v1vgmrkk1z6af9lhaghh5maj7d4imyr47f6";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "windows_i686_gnu 0.48.5" = rec {
+        crateName = "windows_i686_gnu";
+        version = "0.48.5";
+        edition = "2018";
+        sha256 = "0gklnglwd9ilqx7ac3cn8hbhkraqisd0n83jxzf9837nvvkiand7";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "windows_i686_gnu 0.52.0" = rec {
+        crateName = "windows_i686_gnu";
+        version = "0.52.0";
+        edition = "2021";
+        sha256 = "04zkglz4p3pjsns5gbz85v4s5aw102raz4spj4b0lmm33z5kg1m2";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "windows_i686_msvc 0.42.2" = rec {
+        crateName = "windows_i686_msvc";
+        version = "0.42.2";
+        edition = "2018";
+        sha256 = "0q0h9m2aq1pygc199pa5jgc952qhcnf0zn688454i7v4xjv41n24";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "windows_i686_msvc 0.48.5" = rec {
+        crateName = "windows_i686_msvc";
+        version = "0.48.5";
+        edition = "2018";
+        sha256 = "01m4rik437dl9rdf0ndnm2syh10hizvq0dajdkv2fjqcywrw4mcg";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "windows_i686_msvc 0.52.0" = rec {
+        crateName = "windows_i686_msvc";
+        version = "0.52.0";
+        edition = "2021";
+        sha256 = "16kvmbvx0vr0zbgnaz6nsks9ycvfh5xp05bjrhq65kj623iyirgz";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "windows_x86_64_gnu 0.42.2" = rec {
+        crateName = "windows_x86_64_gnu";
+        version = "0.42.2";
+        edition = "2018";
+        sha256 = "0dnbf2xnp3xrvy8v9mgs3var4zq9v9yh9kv79035rdgyp2w15scd";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "windows_x86_64_gnu 0.48.5" = rec {
+        crateName = "windows_x86_64_gnu";
+        version = "0.48.5";
+        edition = "2018";
+        sha256 = "13kiqqcvz2vnyxzydjh73hwgigsdr2z1xpzx313kxll34nyhmm2k";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "windows_x86_64_gnu 0.52.0" = rec {
+        crateName = "windows_x86_64_gnu";
+        version = "0.52.0";
+        edition = "2021";
+        sha256 = "1zdy4qn178sil5sdm63lm7f0kkcjg6gvdwmcprd2yjmwn8ns6vrx";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "windows_x86_64_gnullvm 0.42.2" = rec {
+        crateName = "windows_x86_64_gnullvm";
+        version = "0.42.2";
+        edition = "2018";
+        sha256 = "18wl9r8qbsl475j39zvawlidp1bsbinliwfymr43fibdld31pm16";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "windows_x86_64_gnullvm 0.48.5" = rec {
+        crateName = "windows_x86_64_gnullvm";
+        version = "0.48.5";
+        edition = "2018";
+        sha256 = "1k24810wfbgz8k48c2yknqjmiigmql6kk3knmddkv8k8g1v54yqb";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "windows_x86_64_gnullvm 0.52.0" = rec {
+        crateName = "windows_x86_64_gnullvm";
+        version = "0.52.0";
+        edition = "2021";
+        sha256 = "17lllq4l2k1lqgcnw1cccphxp9vs7inq99kjlm2lfl9zklg7wr8s";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "windows_x86_64_msvc 0.42.2" = rec {
+        crateName = "windows_x86_64_msvc";
+        version = "0.42.2";
+        edition = "2018";
+        sha256 = "1w5r0q0yzx827d10dpjza2ww0j8iajqhmb54s735hhaj66imvv4s";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "windows_x86_64_msvc 0.48.5" = rec {
+        crateName = "windows_x86_64_msvc";
+        version = "0.48.5";
+        edition = "2018";
+        sha256 = "0f4mdp895kkjh9zv8dxvn4pc10xr7839lf5pa9l0193i2pkgr57d";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "windows_x86_64_msvc 0.52.0" = rec {
+        crateName = "windows_x86_64_msvc";
+        version = "0.52.0";
+        edition = "2021";
+        sha256 = "012wfq37f18c09ij5m6rniw7xxn5fcvrxbqd0wd8vgnl3hfn9yfz";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "xml-rs" = rec {
+        crateName = "xml-rs";
+        version = "0.8.19";
+        edition = "2021";
+        crateBin = [ ];
+        sha256 = "0nnpvk3fv32hgh7vs9gbg2swmzxx5yz73f4b7rak7q39q2x9rjqg";
+        libName = "xml";
+        authors = [
+          "Vladimir Matveev <vmatveev@citrine.cc>"
+        ];
+
+      };
+      "xxhash-rust" = rec {
+        crateName = "xxhash-rust";
+        version = "0.8.7";
+        edition = "2018";
+        sha256 = "0yz037yrkn0qa0g0r6733ynd1xbw7zvx58v6qylhyi2kv9wb2a4q";
+        authors = [
+          "Douman <douman@gmx.se>"
+        ];
+        features = { };
+        resolvedDefaultFeatures = [ "xxh3" ];
+      };
+      "xz2" = rec {
+        crateName = "xz2";
+        version = "0.1.7";
+        edition = "2018";
+        sha256 = "1qk7nzpblizvayyq4xzi4b0zacmmbqr6vb9fc0v1avyp17f4931q";
+        authors = [
+          "Alex Crichton <alex@alexcrichton.com>"
+        ];
+        dependencies = [
+          {
+            name = "lzma-sys";
+            packageId = "lzma-sys";
+          }
+        ];
+        features = {
+          "futures" = [ "dep:futures" ];
+          "static" = [ "lzma-sys/static" ];
+          "tokio" = [ "tokio-io" "futures" ];
+          "tokio-io" = [ "dep:tokio-io" ];
+        };
+      };
+      "zerocopy" = rec {
+        crateName = "zerocopy";
+        version = "0.7.26";
+        edition = "2018";
+        sha256 = "184s51bzn8mpx3p9h6klrlppv9b7ja1b8y9998jr36jmj1a42zp9";
+        authors = [
+          "Joshua Liebow-Feeser <joshlf@google.com>"
+        ];
+        dependencies = [
+          {
+            name = "zerocopy-derive";
+            packageId = "zerocopy-derive";
+            optional = true;
+          }
+          {
+            name = "zerocopy-derive";
+            packageId = "zerocopy-derive";
+            target = { target, features }: false;
+          }
+        ];
+        devDependencies = [
+          {
+            name = "zerocopy-derive";
+            packageId = "zerocopy-derive";
+          }
+        ];
+        features = {
+          "__internal_use_only_features_that_work_on_stable" = [ "alloc" "derive" "simd" ];
+          "byteorder" = [ "dep:byteorder" ];
+          "default" = [ "byteorder" ];
+          "derive" = [ "zerocopy-derive" ];
+          "simd-nightly" = [ "simd" ];
+          "zerocopy-derive" = [ "dep:zerocopy-derive" ];
+        };
+        resolvedDefaultFeatures = [ "simd" ];
+      };
+      "zerocopy-derive" = rec {
+        crateName = "zerocopy-derive";
+        version = "0.7.26";
+        edition = "2018";
+        sha256 = "17v8yshfa23z5az2xhclpwrlzih26nj7imwbra12i5b6y764hznx";
+        procMacro = true;
+        authors = [
+          "Joshua Liebow-Feeser <joshlf@google.com>"
+        ];
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn 2.0.39";
+          }
+        ];
+
+      };
+      "zeroize" = rec {
+        crateName = "zeroize";
+        version = "1.7.0";
+        edition = "2021";
+        sha256 = "0bfvby7k9pdp6623p98yz2irqnamcyzpn7zh20nqmdn68b0lwnsj";
+        authors = [
+          "The RustCrypto Project Developers"
+        ];
+        features = {
+          "default" = [ "alloc" ];
+          "derive" = [ "zeroize_derive" ];
+          "serde" = [ "dep:serde" ];
+          "std" = [ "alloc" ];
+          "zeroize_derive" = [ "dep:zeroize_derive" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" ];
+      };
+      "zstd" = rec {
+        crateName = "zstd";
+        version = "0.13.0";
+        edition = "2018";
+        sha256 = "0401q54s9r35x2i7m1kwppgkj79g0pb6xz3xpby7qlkdb44k7yxz";
+        authors = [
+          "Alexandre Bury <alexandre.bury@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "zstd-safe";
+            packageId = "zstd-safe";
+            usesDefaultFeatures = false;
+            features = [ "std" ];
+          }
+        ];
+        features = {
+          "arrays" = [ "zstd-safe/arrays" ];
+          "bindgen" = [ "zstd-safe/bindgen" ];
+          "debug" = [ "zstd-safe/debug" ];
+          "default" = [ "legacy" "arrays" "zdict_builder" ];
+          "experimental" = [ "zstd-safe/experimental" ];
+          "fat-lto" = [ "zstd-safe/fat-lto" ];
+          "legacy" = [ "zstd-safe/legacy" ];
+          "no_asm" = [ "zstd-safe/no_asm" ];
+          "pkg-config" = [ "zstd-safe/pkg-config" ];
+          "thin" = [ "zstd-safe/thin" ];
+          "thin-lto" = [ "zstd-safe/thin-lto" ];
+          "zdict_builder" = [ "zstd-safe/zdict_builder" ];
+          "zstdmt" = [ "zstd-safe/zstdmt" ];
+        };
+        resolvedDefaultFeatures = [ "arrays" "default" "legacy" "zdict_builder" ];
+      };
+      "zstd-safe" = rec {
+        crateName = "zstd-safe";
+        version = "7.0.0";
+        edition = "2018";
+        sha256 = "0gpav2lcibrpmyslmjkcn3w0w64qif3jjljd2h8lr4p249s7qx23";
+        authors = [
+          "Alexandre Bury <alexandre.bury@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "zstd-sys";
+            packageId = "zstd-sys";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "bindgen" = [ "zstd-sys/bindgen" ];
+          "debug" = [ "zstd-sys/debug" ];
+          "default" = [ "legacy" "arrays" "zdict_builder" ];
+          "experimental" = [ "zstd-sys/experimental" ];
+          "fat-lto" = [ "zstd-sys/fat-lto" ];
+          "legacy" = [ "zstd-sys/legacy" ];
+          "no_asm" = [ "zstd-sys/no_asm" ];
+          "pkg-config" = [ "zstd-sys/pkg-config" ];
+          "std" = [ "zstd-sys/std" ];
+          "thin" = [ "zstd-sys/thin" ];
+          "thin-lto" = [ "zstd-sys/thin-lto" ];
+          "zdict_builder" = [ "zstd-sys/zdict_builder" ];
+          "zstdmt" = [ "zstd-sys/zstdmt" ];
+        };
+        resolvedDefaultFeatures = [ "arrays" "legacy" "std" "zdict_builder" ];
+      };
+      "zstd-sys" = rec {
+        crateName = "zstd-sys";
+        version = "2.0.9+zstd.1.5.5";
+        edition = "2018";
+        links = "zstd";
+        sha256 = "0mk6a2367swdi22zg03lcackpnvgq96d7120awd4i83lm2lfy5ly";
+        authors = [
+          "Alexandre Bury <alexandre.bury@gmail.com>"
+        ];
+        buildDependencies = [
+          {
+            name = "cc";
+            packageId = "cc";
+            features = [ "parallel" ];
+          }
+          {
+            name = "pkg-config";
+            packageId = "pkg-config";
+          }
+        ];
+        features = {
+          "bindgen" = [ "dep:bindgen" ];
+          "default" = [ "legacy" "zdict_builder" ];
+        };
+        resolvedDefaultFeatures = [ "legacy" "std" "zdict_builder" ];
+      };
+    };
+
+    #
+    # crate2nix/default.nix (excerpt start)
+    #
+
+    /* Target (platform) data for conditional dependencies.
+      This corresponds roughly to what buildRustCrate is setting.
+    */
+    makeDefaultTarget = platform: {
+      unix = platform.isUnix;
+      windows = platform.isWindows;
+      fuchsia = true;
+      test = false;
+
+      /* We are choosing an arbitrary rust version to grab `lib` from,
+      which is unfortunate, but `lib` has been version-agnostic the
+      whole time so this is good enough for now.
+      */
+      os = pkgs.rust.lib.toTargetOs platform;
+      arch = pkgs.rust.lib.toTargetArch platform;
+      family = pkgs.rust.lib.toTargetFamily platform;
+      vendor = pkgs.rust.lib.toTargetVendor platform;
+      env = "gnu";
+      endian =
+        if platform.parsed.cpu.significantByte.name == "littleEndian"
+        then "little" else "big";
+      pointer_width = toString platform.parsed.cpu.bits;
+      debug_assertions = false;
+    };
+
+    /* Filters common temp files and build files. */
+    # TODO(pkolloch): Substitute with gitignore filter
+    sourceFilter = name: type:
+      let
+        baseName = builtins.baseNameOf (builtins.toString name);
+      in
+        ! (
+          # Filter out git
+          baseName == ".gitignore"
+          || (type == "directory" && baseName == ".git")
+
+          # Filter out build results
+          || (
+            type == "directory" && (
+              baseName == "target"
+              || baseName == "_site"
+              || baseName == ".sass-cache"
+              || baseName == ".jekyll-metadata"
+              || baseName == "build-artifacts"
+            )
+          )
+
+          # Filter out nix-build result symlinks
+          || (
+            type == "symlink" && lib.hasPrefix "result" baseName
+          )
+
+          # Filter out IDE config
+          || (
+            type == "directory" && (
+              baseName == ".idea" || baseName == ".vscode"
+            )
+          ) || lib.hasSuffix ".iml" baseName
+
+          # Filter out nix build files
+          || baseName == "Cargo.nix"
+
+          # Filter out editor backup / swap files.
+          || lib.hasSuffix "~" baseName
+          || builtins.match "^\\.sw[a-z]$$" baseName != null
+          || builtins.match "^\\..*\\.sw[a-z]$$" baseName != null
+          || lib.hasSuffix ".tmp" baseName
+          || lib.hasSuffix ".bak" baseName
+          || baseName == "tests.nix"
+        );
+
+    /* Returns a crate which depends on successful test execution
+      of crate given as the second argument.
+
+      testCrateFlags: list of flags to pass to the test exectuable
+      testInputs: list of packages that should be available during test execution
+    */
+    crateWithTest = { crate, testCrate, testCrateFlags, testInputs, testPreRun, testPostRun }:
+      assert builtins.typeOf testCrateFlags == "list";
+      assert builtins.typeOf testInputs == "list";
+      assert builtins.typeOf testPreRun == "string";
+      assert builtins.typeOf testPostRun == "string";
+      let
+        # override the `crate` so that it will build and execute tests instead of
+        # building the actual lib and bin targets We just have to pass `--test`
+        # to rustc and it will do the right thing.  We execute the tests and copy
+        # their log and the test executables to $out for later inspection.
+        test =
+          let
+            drv = testCrate.override
+              (
+                _: {
+                  buildTests = true;
+                }
+              );
+            # If the user hasn't set any pre/post commands, we don't want to
+            # insert empty lines. This means that any existing users of crate2nix
+            # don't get a spurious rebuild unless they set these explicitly.
+            testCommand = pkgs.lib.concatStringsSep "\n"
+              (pkgs.lib.filter (s: s != "") [
+                testPreRun
+                "$f $testCrateFlags 2>&1 | tee -a $out"
+                testPostRun
+              ]);
+          in
+          pkgs.runCommand "run-tests-${testCrate.name}"
+            {
+              inherit testCrateFlags;
+              buildInputs = testInputs;
+            } ''
+            set -e
+
+            export RUST_BACKTRACE=1
+
+            # recreate a file hierarchy as when running tests with cargo
+
+            # the source for test data
+            # It's necessary to locate the source in $NIX_BUILD_TOP/source/
+            # instead of $NIX_BUILD_TOP/
+            # because we compiled those test binaries in the former and not the latter.
+            # So all paths will expect source tree to be there and not in the build top directly.
+            # For example: $NIX_BUILD_TOP := /build in general, if you ask yourself.
+            # TODO(raitobezarius): I believe there could be more edge cases if `crate.sourceRoot`
+            # do exist but it's very hard to reason about them, so let's wait until the first bug report.
+            mkdir -p source/
+            cd source/
+
+            ${pkgs.buildPackages.xorg.lndir}/bin/lndir ${crate.src}
+
+            # build outputs
+            testRoot=target/debug
+            mkdir -p $testRoot
+
+            # executables of the crate
+            # we copy to prevent std::env::current_exe() to resolve to a store location
+            for i in ${crate}/bin/*; do
+              cp "$i" "$testRoot"
+            done
+            chmod +w -R .
+
+            # test harness executables are suffixed with a hash, like cargo does
+            # this allows to prevent name collision with the main
+            # executables of the crate
+            hash=$(basename $out)
+            for file in ${drv}/tests/*; do
+              f=$testRoot/$(basename $file)-$hash
+              cp $file $f
+              ${testCommand}
+            done
+          '';
+      in
+      pkgs.runCommand "${crate.name}-linked"
+        {
+          inherit (crate) outputs crateName;
+          passthru = (crate.passthru or { }) // {
+            inherit test;
+          };
+        }
+        (lib.optionalString (stdenv.buildPlatform.canExecute stdenv.hostPlatform) ''
+          echo tested by ${test}
+        '' + ''
+          ${lib.concatMapStringsSep "\n" (output: "ln -s ${crate.${output}} ${"$"}${output}") crate.outputs}
+        '');
+
+    /* A restricted overridable version of builtRustCratesWithFeatures. */
+    buildRustCrateWithFeatures =
+      { packageId
+      , features ? rootFeatures
+      , crateOverrides ? defaultCrateOverrides
+      , buildRustCrateForPkgsFunc ? null
+      , runTests ? false
+      , testCrateFlags ? [ ]
+      , testInputs ? [ ]
+        # Any command to run immediatelly before a test is executed.
+      , testPreRun ? ""
+        # Any command run immediatelly after a test is executed.
+      , testPostRun ? ""
+      }:
+      lib.makeOverridable
+        (
+          { features
+          , crateOverrides
+          , runTests
+          , testCrateFlags
+          , testInputs
+          , testPreRun
+          , testPostRun
+          }:
+          let
+            buildRustCrateForPkgsFuncOverriden =
+              if buildRustCrateForPkgsFunc != null
+              then buildRustCrateForPkgsFunc
+              else
+                (
+                  if crateOverrides == pkgs.defaultCrateOverrides
+                  then buildRustCrateForPkgs
+                  else
+                    pkgs: (buildRustCrateForPkgs pkgs).override {
+                      defaultCrateOverrides = crateOverrides;
+                    }
+                );
+            builtRustCrates = builtRustCratesWithFeatures {
+              inherit packageId features;
+              buildRustCrateForPkgsFunc = buildRustCrateForPkgsFuncOverriden;
+              runTests = false;
+            };
+            builtTestRustCrates = builtRustCratesWithFeatures {
+              inherit packageId features;
+              buildRustCrateForPkgsFunc = buildRustCrateForPkgsFuncOverriden;
+              runTests = true;
+            };
+            drv = builtRustCrates.crates.${packageId};
+            testDrv = builtTestRustCrates.crates.${packageId};
+            derivation =
+              if runTests then
+                crateWithTest
+                  {
+                    crate = drv;
+                    testCrate = testDrv;
+                    inherit testCrateFlags testInputs testPreRun testPostRun;
+                  }
+              else drv;
+          in
+          derivation
+        )
+        { inherit features crateOverrides runTests testCrateFlags testInputs testPreRun testPostRun; };
+
+    /* Returns an attr set with packageId mapped to the result of buildRustCrateForPkgsFunc
+      for the corresponding crate.
+    */
+    builtRustCratesWithFeatures =
+      { packageId
+      , features
+      , crateConfigs ? crates
+      , buildRustCrateForPkgsFunc
+      , runTests
+      , makeTarget ? makeDefaultTarget
+      } @ args:
+        assert (builtins.isAttrs crateConfigs);
+        assert (builtins.isString packageId);
+        assert (builtins.isList features);
+        assert (builtins.isAttrs (makeTarget stdenv.hostPlatform));
+        assert (builtins.isBool runTests);
+        let
+          rootPackageId = packageId;
+          mergedFeatures = mergePackageFeatures
+            (
+              args // {
+                inherit rootPackageId;
+                target = makeTarget stdenv.hostPlatform // { test = runTests; };
+              }
+            );
+          # Memoize built packages so that reappearing packages are only built once.
+          builtByPackageIdByPkgs = mkBuiltByPackageIdByPkgs pkgs;
+          mkBuiltByPackageIdByPkgs = pkgs:
+            let
+              self = {
+                crates = lib.mapAttrs (packageId: value: buildByPackageIdForPkgsImpl self pkgs packageId) crateConfigs;
+                target = makeTarget pkgs.stdenv.hostPlatform;
+                build = mkBuiltByPackageIdByPkgs pkgs.buildPackages;
+              };
+            in
+            self;
+          buildByPackageIdForPkgsImpl = self: pkgs: packageId:
+            let
+              features = mergedFeatures."${packageId}" or [ ];
+              crateConfig' = crateConfigs."${packageId}";
+              crateConfig =
+                builtins.removeAttrs crateConfig' [ "resolvedDefaultFeatures" "devDependencies" ];
+              devDependencies =
+                lib.optionals
+                  (runTests && packageId == rootPackageId)
+                  (crateConfig'.devDependencies or [ ]);
+              dependencies =
+                dependencyDerivations {
+                  inherit features;
+                  inherit (self) target;
+                  buildByPackageId = depPackageId:
+                    # proc_macro crates must be compiled for the build architecture
+                    if crateConfigs.${depPackageId}.procMacro or false
+                    then self.build.crates.${depPackageId}
+                    else self.crates.${depPackageId};
+                  dependencies =
+                    (crateConfig.dependencies or [ ])
+                    ++ devDependencies;
+                };
+              buildDependencies =
+                dependencyDerivations {
+                  inherit features;
+                  inherit (self.build) target;
+                  buildByPackageId = depPackageId:
+                    self.build.crates.${depPackageId};
+                  dependencies = crateConfig.buildDependencies or [ ];
+                };
+              dependenciesWithRenames =
+                let
+                  buildDeps = filterEnabledDependencies {
+                    inherit features;
+                    inherit (self) target;
+                    dependencies = crateConfig.dependencies or [ ] ++ devDependencies;
+                  };
+                  hostDeps = filterEnabledDependencies {
+                    inherit features;
+                    inherit (self.build) target;
+                    dependencies = crateConfig.buildDependencies or [ ];
+                  };
+                in
+                lib.filter (d: d ? "rename") (hostDeps ++ buildDeps);
+              # Crate renames have the form:
+              #
+              # {
+              #    crate_name = [
+              #       { version = "1.2.3"; rename = "crate_name01"; }
+              #    ];
+              #    # ...
+              # }
+              crateRenames =
+                let
+                  grouped =
+                    lib.groupBy
+                      (dependency: dependency.name)
+                      dependenciesWithRenames;
+                  versionAndRename = dep:
+                    let
+                      package = crateConfigs."${dep.packageId}";
+                    in
+                    { inherit (dep) rename; inherit (package) version; };
+                in
+                lib.mapAttrs (name: builtins.map versionAndRename) grouped;
+            in
+            buildRustCrateForPkgsFunc pkgs
+              (
+                crateConfig // {
+                  # https://github.com/NixOS/nixpkgs/issues/218712
+                  dontStrip = stdenv.hostPlatform.isDarwin;
+                  src = crateConfig.src or (
+                    pkgs.fetchurl rec {
+                      name = "${crateConfig.crateName}-${crateConfig.version}.tar.gz";
+                      # https://www.pietroalbini.org/blog/downloading-crates-io/
+                      # Not rate-limited, CDN URL.
+                      url = "https://static.crates.io/crates/${crateConfig.crateName}/${crateConfig.crateName}-${crateConfig.version}.crate";
+                      sha256 =
+                        assert (lib.assertMsg (crateConfig ? sha256) "Missing sha256 for ${name}");
+                        crateConfig.sha256;
+                    }
+                  );
+                  extraRustcOpts = lib.lists.optional (targetFeatures != [ ]) "-C target-feature=${lib.concatMapStringsSep "," (x: "+${x}") targetFeatures}";
+                  inherit features dependencies buildDependencies crateRenames release;
+                }
+              );
+        in
+        builtByPackageIdByPkgs;
+
+    /* Returns the actual derivations for the given dependencies. */
+    dependencyDerivations =
+      { buildByPackageId
+      , features
+      , dependencies
+      , target
+      }:
+        assert (builtins.isList features);
+        assert (builtins.isList dependencies);
+        assert (builtins.isAttrs target);
+        let
+          enabledDependencies = filterEnabledDependencies {
+            inherit dependencies features target;
+          };
+          depDerivation = dependency: buildByPackageId dependency.packageId;
+        in
+        map depDerivation enabledDependencies;
+
+    /* Returns a sanitized version of val with all values substituted that cannot
+      be serialized as JSON.
+    */
+    sanitizeForJson = val:
+      if builtins.isAttrs val
+      then lib.mapAttrs (n: sanitizeForJson) val
+      else if builtins.isList val
+      then builtins.map sanitizeForJson val
+      else if builtins.isFunction val
+      then "function"
+      else val;
+
+    /* Returns various tools to debug a crate. */
+    debugCrate = { packageId, target ? makeDefaultTarget stdenv.hostPlatform }:
+      assert (builtins.isString packageId);
+      let
+        debug = rec {
+          # The built tree as passed to buildRustCrate.
+          buildTree = buildRustCrateWithFeatures {
+            buildRustCrateForPkgsFunc = _: lib.id;
+            inherit packageId;
+          };
+          sanitizedBuildTree = sanitizeForJson buildTree;
+          dependencyTree = sanitizeForJson
+            (
+              buildRustCrateWithFeatures {
+                buildRustCrateForPkgsFunc = _: crate: {
+                  "01_crateName" = crate.crateName or false;
+                  "02_features" = crate.features or [ ];
+                  "03_dependencies" = crate.dependencies or [ ];
+                };
+                inherit packageId;
+              }
+            );
+          mergedPackageFeatures = mergePackageFeatures {
+            features = rootFeatures;
+            inherit packageId target;
+          };
+          diffedDefaultPackageFeatures = diffDefaultPackageFeatures {
+            inherit packageId target;
+          };
+        };
+      in
+      { internal = debug; };
+
+    /* Returns differences between cargo default features and crate2nix default
+      features.
+
+      This is useful for verifying the feature resolution in crate2nix.
+    */
+    diffDefaultPackageFeatures =
+      { crateConfigs ? crates
+      , packageId
+      , target
+      }:
+        assert (builtins.isAttrs crateConfigs);
+        let
+          prefixValues = prefix: lib.mapAttrs (n: v: { "${prefix}" = v; });
+          mergedFeatures =
+            prefixValues
+              "crate2nix"
+              (mergePackageFeatures { inherit crateConfigs packageId target; features = [ "default" ]; });
+          configs = prefixValues "cargo" crateConfigs;
+          combined = lib.foldAttrs (a: b: a // b) { } [ mergedFeatures configs ];
+          onlyInCargo =
+            builtins.attrNames
+              (lib.filterAttrs (n: v: !(v ? "crate2nix") && (v ? "cargo")) combined);
+          onlyInCrate2Nix =
+            builtins.attrNames
+              (lib.filterAttrs (n: v: (v ? "crate2nix") && !(v ? "cargo")) combined);
+          differentFeatures = lib.filterAttrs
+            (
+              n: v:
+                (v ? "crate2nix")
+                && (v ? "cargo")
+                && (v.crate2nix.features or [ ]) != (v."cargo".resolved_default_features or [ ])
+            )
+            combined;
+        in
+        builtins.toJSON {
+          inherit onlyInCargo onlyInCrate2Nix differentFeatures;
+        };
+
+    /* Returns an attrset mapping packageId to the list of enabled features.
+
+      If multiple paths to a dependency enable different features, the
+      corresponding feature sets are merged. Features in rust are additive.
+    */
+    mergePackageFeatures =
+      { crateConfigs ? crates
+      , packageId
+      , rootPackageId ? packageId
+      , features ? rootFeatures
+      , dependencyPath ? [ crates.${packageId}.crateName ]
+      , featuresByPackageId ? { }
+      , target
+        # Adds devDependencies to the crate with rootPackageId.
+      , runTests ? false
+      , ...
+      } @ args:
+        assert (builtins.isAttrs crateConfigs);
+        assert (builtins.isString packageId);
+        assert (builtins.isString rootPackageId);
+        assert (builtins.isList features);
+        assert (builtins.isList dependencyPath);
+        assert (builtins.isAttrs featuresByPackageId);
+        assert (builtins.isAttrs target);
+        assert (builtins.isBool runTests);
+        let
+          crateConfig = crateConfigs."${packageId}" or (builtins.throw "Package not found: ${packageId}");
+          expandedFeatures = expandFeatures (crateConfig.features or { }) features;
+          enabledFeatures = enableFeatures (crateConfig.dependencies or [ ]) expandedFeatures;
+          depWithResolvedFeatures = dependency:
+            let
+              inherit (dependency) packageId;
+              features = dependencyFeatures enabledFeatures dependency;
+            in
+            { inherit packageId features; };
+          resolveDependencies = cache: path: dependencies:
+            assert (builtins.isAttrs cache);
+            assert (builtins.isList dependencies);
+            let
+              enabledDependencies = filterEnabledDependencies {
+                inherit dependencies target;
+                features = enabledFeatures;
+              };
+              directDependencies = map depWithResolvedFeatures enabledDependencies;
+              foldOverCache = op: lib.foldl op cache directDependencies;
+            in
+            foldOverCache
+              (
+                cache: { packageId, features }:
+                  let
+                    cacheFeatures = cache.${packageId} or [ ];
+                    combinedFeatures = sortedUnique (cacheFeatures ++ features);
+                  in
+                  if cache ? ${packageId} && cache.${packageId} == combinedFeatures
+                  then cache
+                  else
+                    mergePackageFeatures {
+                      features = combinedFeatures;
+                      featuresByPackageId = cache;
+                      inherit crateConfigs packageId target runTests rootPackageId;
+                    }
+              );
+          cacheWithSelf =
+            let
+              cacheFeatures = featuresByPackageId.${packageId} or [ ];
+              combinedFeatures = sortedUnique (cacheFeatures ++ enabledFeatures);
+            in
+            featuresByPackageId // {
+              "${packageId}" = combinedFeatures;
+            };
+          cacheWithDependencies =
+            resolveDependencies cacheWithSelf "dep"
+              (
+                crateConfig.dependencies or [ ]
+                ++ lib.optionals
+                  (runTests && packageId == rootPackageId)
+                  (crateConfig.devDependencies or [ ])
+              );
+          cacheWithAll =
+            resolveDependencies
+              cacheWithDependencies "build"
+              (crateConfig.buildDependencies or [ ]);
+        in
+        cacheWithAll;
+
+    /* Returns the enabled dependencies given the enabled features. */
+    filterEnabledDependencies = { dependencies, features, target }:
+      assert (builtins.isList dependencies);
+      assert (builtins.isList features);
+      assert (builtins.isAttrs target);
+
+      lib.filter
+        (
+          dep:
+          let
+            targetFunc = dep.target or (features: true);
+          in
+          targetFunc { inherit features target; }
+          && (
+            !(dep.optional or false)
+            || builtins.any (doesFeatureEnableDependency dep) features
+          )
+        )
+        dependencies;
+
+    /* Returns whether the given feature should enable the given dependency. */
+    doesFeatureEnableDependency = dependency: feature:
+      let
+        name = dependency.rename or dependency.name;
+        prefix = "${name}/";
+        len = builtins.stringLength prefix;
+        startsWithPrefix = builtins.substring 0 len feature == prefix;
+      in
+      feature == name || feature == "dep:" + name || startsWithPrefix;
+
+    /* Returns the expanded features for the given inputFeatures by applying the
+      rules in featureMap.
+
+      featureMap is an attribute set which maps feature names to lists of further
+      feature names to enable in case this feature is selected.
+    */
+    expandFeatures = featureMap: inputFeatures:
+      assert (builtins.isAttrs featureMap);
+      assert (builtins.isList inputFeatures);
+      let
+        expandFeaturesNoCycle = oldSeen: inputFeatures:
+          if inputFeatures != [ ]
+          then
+            let
+              # The feature we're currently expanding.
+              feature = builtins.head inputFeatures;
+              # All the features we've seen/expanded so far, including the one
+              # we're currently processing.
+              seen = oldSeen // { ${feature} = 1; };
+              # Expand the feature but be careful to not re-introduce a feature
+              # that we've already seen: this can easily cause a cycle, see issue
+              # #209.
+              enables = builtins.filter (f: !(seen ? "${f}")) (featureMap."${feature}" or [ ]);
+            in
+            [ feature ] ++ (expandFeaturesNoCycle seen (builtins.tail inputFeatures ++ enables))
+          # No more features left, nothing to expand to.
+          else [ ];
+        outFeatures = expandFeaturesNoCycle { } inputFeatures;
+      in
+      sortedUnique outFeatures;
+
+    /* This function adds optional dependencies as features if they are enabled
+      indirectly by dependency features. This function mimics Cargo's behavior
+      described in a note at:
+      https://doc.rust-lang.org/nightly/cargo/reference/features.html#dependency-features
+    */
+    enableFeatures = dependencies: features:
+      assert (builtins.isList features);
+      assert (builtins.isList dependencies);
+      let
+        additionalFeatures = lib.concatMap
+          (
+            dependency:
+              assert (builtins.isAttrs dependency);
+              let
+                enabled = builtins.any (doesFeatureEnableDependency dependency) features;
+              in
+              if (dependency.optional or false) && enabled
+              then [ (dependency.rename or dependency.name) ]
+              else [ ]
+          )
+          dependencies;
+      in
+      sortedUnique (features ++ additionalFeatures);
+
+    /*
+      Returns the actual features for the given dependency.
+
+      features: The features of the crate that refers this dependency.
+    */
+    dependencyFeatures = features: dependency:
+      assert (builtins.isList features);
+      assert (builtins.isAttrs dependency);
+      let
+        defaultOrNil =
+          if dependency.usesDefaultFeatures or true
+          then [ "default" ]
+          else [ ];
+        explicitFeatures = dependency.features or [ ];
+        additionalDependencyFeatures =
+          let
+            name = dependency.rename or dependency.name;
+            stripPrefixMatch = prefix: s:
+              if lib.hasPrefix prefix s
+              then lib.removePrefix prefix s
+              else null;
+            extractFeature = feature: lib.findFirst
+              (f: f != null)
+              null
+              (map (prefix: stripPrefixMatch prefix feature) [
+                (name + "/")
+                (name + "?/")
+              ]);
+            dependencyFeatures = lib.filter (f: f != null) (map extractFeature features);
+          in
+          dependencyFeatures;
+      in
+      defaultOrNil ++ explicitFeatures ++ additionalDependencyFeatures;
+
+    /* Sorts and removes duplicates from a list of strings. */
+    sortedUnique = features:
+      assert (builtins.isList features);
+      assert (builtins.all builtins.isString features);
+      let
+        outFeaturesSet = lib.foldl (set: feature: set // { "${feature}" = 1; }) { } features;
+        outFeaturesUnique = builtins.attrNames outFeaturesSet;
+      in
+      builtins.sort (a: b: a < b) outFeaturesUnique;
+
+    deprecationWarning = message: value:
+      if strictDeprecation
+      then builtins.throw "strictDeprecation enabled, aborting: ${message}"
+      else builtins.trace message value;
+
+    #
+    # crate2nix/default.nix (excerpt end)
+    #
+  };
+}
+
diff --git a/tvix/tools/crunch-v2/Cargo.toml b/tvix/tools/crunch-v2/Cargo.toml
new file mode 100644
index 0000000000..1e3f025250
--- /dev/null
+++ b/tvix/tools/crunch-v2/Cargo.toml
@@ -0,0 +1,39 @@
+[package]
+name = "crunch-v2"
+version = "0.1.0"
+edition = "2021"
+
+[workspace]
+members = ["."]
+
+[dependencies]
+anyhow = { version = "1.0.75", features = ["backtrace"] }
+lazy_static = "1.4.0"
+
+bstr = "1.8.0"
+bytes = "1.5.0"
+
+futures = "0.3.29"
+tokio = { version = "1.34.0", features = ["full"] }
+
+rusoto_core = { version = "0.48.0", default-features = false, features = ["hyper-rustls"] }
+rusoto_s3 = { version = "0.48.0", default-features = false, features = ["rustls"] }
+
+nix-compat = { version = "0.1.0", path = "../../nix-compat" }
+sled = "0.34.7"
+
+fastcdc = "3.1.0"
+blake3 = "1.5.0"
+sha2 = { version = "0.10.8", features = ["asm"] }
+digest = "0.10.7"
+
+bzip2 = "0.4.4"
+xz2 = "0.1.7"
+zstd = "0.13.0"
+prost = "0.12.2"
+polars = { version = "0.35.4", default-features = false, features = ["parquet", "lazy", "sql", "dtype-struct"] }
+indicatif = "0.17.7"
+clap = { version = "4.4.18", features = ["derive"] }
+
+[build-dependencies]
+prost-build = "0.12.2"
diff --git a/tvix/tools/crunch-v2/OWNERS b/tvix/tools/crunch-v2/OWNERS
new file mode 100644
index 0000000000..b9bc074a80
--- /dev/null
+++ b/tvix/tools/crunch-v2/OWNERS
@@ -0,0 +1 @@
+edef
diff --git a/tvix/tools/crunch-v2/build.rs b/tvix/tools/crunch-v2/build.rs
new file mode 100644
index 0000000000..25e6d0be21
--- /dev/null
+++ b/tvix/tools/crunch-v2/build.rs
@@ -0,0 +1,6 @@
+use std::io::Result;
+
+fn main() -> Result<()> {
+    prost_build::compile_protos(&["protos/flatstore.proto"], &["protos/"])?;
+    Ok(())
+}
diff --git a/tvix/tools/crunch-v2/default.nix b/tvix/tools/crunch-v2/default.nix
new file mode 100644
index 0000000000..689e86be65
--- /dev/null
+++ b/tvix/tools/crunch-v2/default.nix
@@ -0,0 +1,15 @@
+{ pkgs, ... }:
+
+let
+  crates = import ./Cargo.nix {
+    inherit pkgs;
+    nixpkgs = pkgs.path;
+
+    defaultCrateOverrides = pkgs.defaultCrateOverrides // {
+      crunch-v2 = prev: {
+        nativeBuildInputs = (prev.nativeBuildInputs or [ ]) ++ [ pkgs.buildPackages.protobuf ];
+      };
+    };
+  };
+in
+crates.rootCrate.build
diff --git a/tvix/tools/crunch-v2/protos/flatstore.proto b/tvix/tools/crunch-v2/protos/flatstore.proto
new file mode 100644
index 0000000000..2f2838fc75
--- /dev/null
+++ b/tvix/tools/crunch-v2/protos/flatstore.proto
@@ -0,0 +1,38 @@
+syntax = "proto3";
+
+package tvix.flatstore.v1;
+
+message Path {
+    bytes nar_hash = 1;
+
+    oneof node {
+        DirectoryNode directory = 2;
+        FileNode file = 3;
+        SymlinkNode symlink = 4;
+    }
+}
+
+message DirectoryNode {
+    bytes name = 1;
+    repeated DirectoryNode directories = 2;
+    repeated FileNode files = 3;
+    repeated SymlinkNode symlinks = 4;
+}
+
+message FileNode {
+    bytes name = 1;
+    bytes hash = 2;
+    repeated Chunk chunks = 3;
+    bool executable = 4;
+}
+
+message Chunk {
+    bytes hash = 1;
+    uint32 size = 2;
+    uint32 size_compressed = 3;
+}
+
+message SymlinkNode {
+    bytes name = 1;
+    bytes target = 2;
+}
diff --git a/tvix/tools/crunch-v2/src/bin/extract.rs b/tvix/tools/crunch-v2/src/bin/extract.rs
new file mode 100644
index 0000000000..416d201f4e
--- /dev/null
+++ b/tvix/tools/crunch-v2/src/bin/extract.rs
@@ -0,0 +1,155 @@
+//! This tool lossily converts a Sled database produced by crunch-v2 into a Parquet file for analysis.
+//! The resulting `crunch.parquet` has columns file_hash`, `nar_hash`, and `chunk`.
+//! The first two are SHA-256 hashes of the compressed file and the NAR it decompresses to.
+//! `chunk` is a struct array corresponding to [crunch_v2::proto::Chunk] messages.
+//! They are concatenated without any additional structure, so nothing but the chunk list is preserved.
+
+use anyhow::Result;
+use clap::Parser;
+use indicatif::{ProgressBar, ProgressStyle};
+use std::fs::File;
+use std::path::PathBuf;
+
+use crunch_v2::proto::{self, path::Node};
+use prost::Message;
+
+use polars::{
+    chunked_array::builder::AnonymousOwnedListBuilder,
+    prelude::{
+        df, BinaryChunkedBuilder, ChunkedBuilder, DataFrame, DataType, Field, ListBuilderTrait,
+        NamedFrom, ParquetWriter, PrimitiveChunkedBuilder, Series, UInt32Type,
+    },
+    series::IntoSeries,
+};
+
+#[derive(Parser)]
+struct Args {
+    /// Path to the sled database that's read from.
+    #[clap(default_value = "crunch.db")]
+    infile: PathBuf,
+
+    /// Path to the resulting parquet file that's written.
+    #[clap(default_value = "crunch.parquet")]
+    outfile: PathBuf,
+}
+
+fn main() -> Result<()> {
+    let args = Args::parse();
+
+    let w = ParquetWriter::new(File::create(args.outfile)?);
+
+    let db: sled::Db = sled::open(&args.infile).unwrap();
+    let files_tree: sled::Tree = db.open_tree("files").unwrap();
+
+    let progress =
+        ProgressBar::new(files_tree.len() as u64).with_style(ProgressStyle::with_template(
+            "{elapsed_precise}/{duration_precise} {wide_bar} {pos}/{len}",
+        )?);
+
+    let mut frame = FrameBuilder::new();
+    for entry in &files_tree {
+        let (file_hash, pb) = entry?;
+        frame.push(
+            file_hash[..].try_into().unwrap(),
+            proto::Path::decode(&pb[..])?,
+        );
+        progress.inc(1);
+    }
+
+    w.finish(&mut frame.finish())?;
+
+    Ok(())
+}
+
+struct FrameBuilder {
+    file_hash: BinaryChunkedBuilder,
+    nar_hash: BinaryChunkedBuilder,
+    chunk: AnonymousOwnedListBuilder,
+}
+
+impl FrameBuilder {
+    fn new() -> Self {
+        Self {
+            file_hash: BinaryChunkedBuilder::new("file_hash", 0, 0),
+            nar_hash: BinaryChunkedBuilder::new("nar_hash", 0, 0),
+            chunk: AnonymousOwnedListBuilder::new(
+                "chunk",
+                0,
+                Some(DataType::Struct(vec![
+                    Field::new("hash", DataType::Binary),
+                    Field::new("size", DataType::UInt32),
+                    Field::new("size_compressed", DataType::UInt32),
+                ])),
+            ),
+        }
+    }
+
+    fn push(&mut self, file_hash: [u8; 32], pb: proto::Path) {
+        self.file_hash.append_value(&file_hash[..]);
+        self.nar_hash.append_value(pb.nar_hash);
+        self.chunk
+            .append_series(&ChunkFrameBuilder::new(pb.node.unwrap()))
+            .unwrap();
+    }
+
+    fn finish(mut self) -> DataFrame {
+        df! {
+            "file_hash" => self.file_hash.finish().into_series(),
+            "nar_hash" => self.nar_hash.finish().into_series(),
+            "chunk" => self.chunk.finish().into_series()
+        }
+        .unwrap()
+    }
+}
+
+struct ChunkFrameBuilder {
+    hash: BinaryChunkedBuilder,
+    size: PrimitiveChunkedBuilder<UInt32Type>,
+    size_compressed: PrimitiveChunkedBuilder<UInt32Type>,
+}
+
+impl ChunkFrameBuilder {
+    fn new(node: proto::path::Node) -> Series {
+        let mut this = Self {
+            hash: BinaryChunkedBuilder::new("hash", 0, 0),
+            size: PrimitiveChunkedBuilder::new("size", 0),
+            size_compressed: PrimitiveChunkedBuilder::new("size_compressed", 0),
+        };
+
+        this.push(node);
+        this.finish()
+    }
+
+    fn push(&mut self, node: Node) {
+        match node {
+            Node::Directory(node) => {
+                for node in node.files {
+                    self.push(Node::File(node));
+                }
+
+                for node in node.directories {
+                    self.push(Node::Directory(node));
+                }
+            }
+            Node::File(node) => {
+                for chunk in node.chunks {
+                    self.hash.append_value(&chunk.hash);
+                    self.size.append_value(chunk.size);
+                    self.size_compressed.append_value(chunk.size_compressed);
+                }
+            }
+            Node::Symlink(_) => {}
+        }
+    }
+
+    fn finish(self) -> Series {
+        df! {
+            "hash" => self.hash.finish().into_series(),
+            "size" => self.size.finish().into_series(),
+            "size_compressed" => self.size_compressed.finish().into_series()
+        }
+        .unwrap()
+        .into_struct("chunk")
+        .into_series()
+    }
+}
diff --git a/tvix/tools/crunch-v2/src/lib.rs b/tvix/tools/crunch-v2/src/lib.rs
new file mode 100644
index 0000000000..09ea2e75d5
--- /dev/null
+++ b/tvix/tools/crunch-v2/src/lib.rs
@@ -0,0 +1,3 @@
+pub mod proto {
+    include!(concat!(env!("OUT_DIR"), "/tvix.flatstore.v1.rs"));
+}
diff --git a/tvix/tools/crunch-v2/src/main.rs b/tvix/tools/crunch-v2/src/main.rs
new file mode 100644
index 0000000000..a5d538f6be
--- /dev/null
+++ b/tvix/tools/crunch-v2/src/main.rs
@@ -0,0 +1,309 @@
+//! This is a tool for ingesting subsets of cache.nixos.org into its own flattened castore format.
+//! Currently, produced chunks are not preserved, and this purely serves as a way of measuring
+//! compression/deduplication ratios for various chunking and compression parameters.
+//!
+//! NARs to be ingested are read from `ingest.parquet`, and filtered by an SQL expression provided as a program argument.
+//! The `file_hash` column should contain SHA-256 hashes of the compressed data, corresponding to the `FileHash` narinfo field.
+//! The `compression` column should contain either `"bzip2"` or `"xz"`, corresponding to the `Compression` narinfo field.
+//! Additional columns are ignored, but can be used by the SQL filter expression.
+//!
+//! flatstore protobufs are written to a sled database named `crunch.db`, addressed by file hash.
+
+use crunch_v2::proto;
+
+mod remote;
+
+use anyhow::Result;
+use clap::Parser;
+use futures::{stream, StreamExt, TryStreamExt};
+use indicatif::{ProgressBar, ProgressStyle};
+use std::{
+    io::{self, BufRead, Read, Write},
+    path::PathBuf,
+    ptr,
+};
+
+use polars::{
+    prelude::{col, LazyFrame, ScanArgsParquet},
+    sql::sql_expr,
+};
+
+use fastcdc::v2020::{ChunkData, StreamCDC};
+use nix_compat::nar::reader as nar;
+
+use digest::Digest;
+use prost::Message;
+use sha2::Sha256;
+
+#[derive(Parser)]
+struct Args {
+    /// Path to an existing parquet file.
+    /// The `file_hash` column should contain SHA-256 hashes of the compressed
+    /// data, corresponding to the `FileHash` narinfo field.
+    /// The `compression` column should contain either `"bzip2"` or `"xz"`,
+    /// corresponding to the `Compression` narinfo field.
+    /// Additional columns are ignored, but can be used by the SQL filter expression.
+    #[clap(long, default_value = "ingest.parquet")]
+    infile: PathBuf,
+
+    /// Filter expression to filter elements in the parquet file for.
+    filter: String,
+
+    /// Average chunk size for FastCDC, in KiB.
+    /// min value is half, max value double of that number.
+    #[clap(long, default_value_t = 256)]
+    avg_chunk_size: u32,
+
+    /// Path to the sled database where results are written to (flatstore
+    /// protobufs, addressed by file hash).
+    #[clap(long, default_value = "crunch.db")]
+    outfile: PathBuf,
+}
+
+#[tokio::main]
+async fn main() -> Result<()> {
+    let args = Args::parse();
+
+    let filter = sql_expr(args.filter)?;
+    let avg_chunk_size = args.avg_chunk_size * 1024;
+
+    let df = LazyFrame::scan_parquet(&args.infile, ScanArgsParquet::default())?
+        .filter(filter)
+        .select([col("file_hash"), col("compression")])
+        .drop_nulls(None)
+        .collect()?;
+
+    let progress = ProgressBar::new(df.height() as u64).with_style(ProgressStyle::with_template(
+        "{elapsed_precise}/{duration_precise} {wide_bar} {pos}/{len}",
+    )?);
+
+    let file_hash = df
+        .column("file_hash")?
+        .binary()?
+        .into_iter()
+        .map(|h| -> [u8; 32] { h.unwrap().try_into().unwrap() });
+
+    let compression = df
+        .column("compression")?
+        .utf8()?
+        .into_iter()
+        .map(|c| c.unwrap());
+
+    let db: sled::Db = sled::open(args.outfile).unwrap();
+    let files_tree = db.open_tree("files").unwrap();
+
+    let res = stream::iter(file_hash.zip(compression))
+        .map(Ok)
+        .try_for_each_concurrent(Some(16), |(file_hash, compression)| {
+            let progress = progress.clone();
+            let files_tree = files_tree.clone();
+            async move {
+                if files_tree.contains_key(&file_hash)? {
+                    progress.inc(1);
+                    return Ok(());
+                }
+
+                let reader = remote::nar(file_hash, compression).await?;
+
+                tokio::task::spawn_blocking(move || {
+                    let mut reader = Sha256Reader::from(reader);
+
+                    let path =
+                        ingest(nar::open(&mut reader)?, vec![], avg_chunk_size).map(|node| {
+                            proto::Path {
+                                nar_hash: reader.finalize().as_slice().into(),
+                                node: Some(node),
+                            }
+                        })?;
+
+                    files_tree.insert(file_hash, path.encode_to_vec())?;
+                    progress.inc(1);
+
+                    Ok::<_, anyhow::Error>(())
+                })
+                .await?
+            }
+        })
+        .await;
+
+    let flush = files_tree.flush_async().await;
+
+    res?;
+    flush?;
+
+    Ok(())
+}
+
+fn ingest(node: nar::Node, name: Vec<u8>, avg_chunk_size: u32) -> Result<proto::path::Node> {
+    match node {
+        nar::Node::Symlink { target } => Ok(proto::path::Node::Symlink(proto::SymlinkNode {
+            name,
+            target,
+        })),
+
+        nar::Node::Directory(mut reader) => {
+            let mut directories = vec![];
+            let mut files = vec![];
+            let mut symlinks = vec![];
+
+            while let Some(node) = reader.next()? {
+                match ingest(node.node, node.name, avg_chunk_size)? {
+                    proto::path::Node::Directory(node) => {
+                        directories.push(node);
+                    }
+                    proto::path::Node::File(node) => {
+                        files.push(node);
+                    }
+                    proto::path::Node::Symlink(node) => {
+                        symlinks.push(node);
+                    }
+                }
+            }
+
+            Ok(proto::path::Node::Directory(proto::DirectoryNode {
+                name,
+                directories,
+                files,
+                symlinks,
+            }))
+        }
+
+        nar::Node::File { executable, reader } => {
+            let mut reader = B3Reader::from(reader);
+            let mut chunks = vec![];
+
+            for chunk in StreamCDC::new(
+                &mut reader,
+                avg_chunk_size / 2,
+                avg_chunk_size,
+                avg_chunk_size * 2,
+            ) {
+                let ChunkData {
+                    length: size, data, ..
+                } = chunk?;
+
+                let hash = blake3::hash(&data);
+                let size_compressed = zstd_size(&data, 9);
+
+                chunks.push(proto::Chunk {
+                    hash: hash.as_bytes().as_slice().into(),
+                    size: size.try_into().unwrap(),
+                    size_compressed: size_compressed.try_into().unwrap(),
+                });
+            }
+
+            Ok(proto::path::Node::File(proto::FileNode {
+                name,
+                hash: reader.finalize().as_bytes().as_slice().into(),
+                chunks,
+                executable,
+            }))
+        }
+    }
+}
+
+struct Sha256Reader<R> {
+    inner: R,
+    hasher: Sha256,
+    buf: *const [u8],
+}
+
+const ZERO_BUF: *const [u8] = ptr::slice_from_raw_parts(1 as *const u8, 0);
+
+unsafe impl<R: Send> Send for Sha256Reader<R> {}
+
+impl<R> From<R> for Sha256Reader<R> {
+    fn from(value: R) -> Self {
+        Self {
+            inner: value,
+            hasher: Sha256::new(),
+            buf: ZERO_BUF,
+        }
+    }
+}
+
+impl<R: Read> Read for Sha256Reader<R> {
+    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+        self.buf = ZERO_BUF;
+        let n = self.inner.read(buf)?;
+        self.hasher.update(&buf[..n]);
+        Ok(n)
+    }
+}
+
+impl<R: BufRead> BufRead for Sha256Reader<R> {
+    fn fill_buf(&mut self) -> io::Result<&[u8]> {
+        self.buf = ZERO_BUF;
+        let buf = self.inner.fill_buf()?;
+        self.buf = buf as *const [u8];
+        Ok(buf)
+    }
+
+    fn consume(&mut self, amt: usize) {
+        // UNSAFETY: This assumes that `R::consume` doesn't invalidate the buffer.
+        // That's not a sound assumption in general, though it is likely to hold.
+        // TODO(edef): refactor this codebase to write a fresh NAR for verification purposes
+        // we already buffer full chunks, so there's no pressing need to reuse the input buffers
+        unsafe {
+            let (head, buf) = (*self.buf).split_at(amt);
+            self.buf = buf as *const [u8];
+            self.hasher.update(head);
+            self.inner.consume(amt);
+        }
+    }
+}
+
+impl<R> Sha256Reader<R> {
+    fn finalize(self) -> [u8; 32] {
+        self.hasher.finalize().into()
+    }
+}
+
+struct B3Reader<R> {
+    inner: R,
+    hasher: blake3::Hasher,
+}
+
+impl<R> From<R> for B3Reader<R> {
+    fn from(value: R) -> Self {
+        Self {
+            inner: value,
+            hasher: blake3::Hasher::new(),
+        }
+    }
+}
+
+impl<R: Read> Read for B3Reader<R> {
+    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+        let n = self.inner.read(buf)?;
+        self.hasher.update(&buf[..n]);
+        Ok(n)
+    }
+}
+
+impl<R> B3Reader<R> {
+    fn finalize(self) -> blake3::Hash {
+        self.hasher.finalize()
+    }
+}
+
+fn zstd_size(data: &[u8], level: i32) -> u64 {
+    let mut w = zstd::Encoder::new(CountingWriter::default(), level).unwrap();
+    w.write_all(&data).unwrap();
+    let CountingWriter(size) = w.finish().unwrap();
+    size
+}
+
+#[derive(Default)]
+struct CountingWriter(u64);
+
+impl Write for CountingWriter {
+    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+        self.0 += buf.len() as u64;
+        Ok(buf.len())
+    }
+
+    fn flush(&mut self) -> io::Result<()> {
+        Ok(())
+    }
+}
diff --git a/tvix/tools/crunch-v2/src/remote.rs b/tvix/tools/crunch-v2/src/remote.rs
new file mode 100644
index 0000000000..93952ecd73
--- /dev/null
+++ b/tvix/tools/crunch-v2/src/remote.rs
@@ -0,0 +1,211 @@
+use std::{
+    cmp,
+    io::{self, BufRead, BufReader, Read},
+    pin::Pin,
+    task::{self, Poll},
+};
+
+use anyhow::{bail, Result};
+use bytes::{Buf, Bytes};
+use futures::{future::BoxFuture, Future, FutureExt, Stream, StreamExt};
+use lazy_static::lazy_static;
+use tokio::runtime::Handle;
+
+use nix_compat::nixbase32;
+
+use rusoto_core::{ByteStream, Region};
+use rusoto_s3::{GetObjectOutput, GetObjectRequest, S3Client, S3};
+
+use bzip2::read::BzDecoder;
+use xz2::read::XzDecoder;
+
+lazy_static! {
+    static ref S3_CLIENT: S3Client = S3Client::new(Region::UsEast1);
+}
+
+const BUCKET: &str = "nix-cache";
+
+pub async fn nar(
+    file_hash: [u8; 32],
+    compression: &str,
+) -> Result<Box<BufReader<dyn Read + Send>>> {
+    let (extension, decompress): (&'static str, fn(_) -> Box<_>) = match compression {
+        "bzip2" => ("bz2", decompress_bz2),
+        "xz" => ("xz", decompress_xz),
+        _ => bail!("unknown compression: {compression}"),
+    };
+
+    Ok(decompress(
+        FileStream::new(FileKey {
+            file_hash,
+            extension,
+        })
+        .await?
+        .into(),
+    ))
+}
+
+fn decompress_xz(reader: FileStreamReader) -> Box<BufReader<dyn Read + Send>> {
+    Box::new(BufReader::new(XzDecoder::new(reader)))
+}
+
+fn decompress_bz2(reader: FileStreamReader) -> Box<BufReader<dyn Read + Send>> {
+    Box::new(BufReader::new(BzDecoder::new(reader)))
+}
+
+struct FileStreamReader {
+    inner: FileStream,
+    buffer: Bytes,
+}
+
+impl From<FileStream> for FileStreamReader {
+    fn from(value: FileStream) -> Self {
+        FileStreamReader {
+            inner: value,
+            buffer: Bytes::new(),
+        }
+    }
+}
+
+impl Read for FileStreamReader {
+    fn read(&mut self, dst: &mut [u8]) -> io::Result<usize> {
+        let src = self.fill_buf()?;
+        let n = cmp::min(src.len(), dst.len());
+        dst[..n].copy_from_slice(&src[..n]);
+        self.consume(n);
+        Ok(n)
+    }
+}
+
+impl BufRead for FileStreamReader {
+    fn fill_buf(&mut self) -> io::Result<&[u8]> {
+        if !self.buffer.is_empty() {
+            return Ok(&self.buffer);
+        }
+
+        self.buffer = Handle::current()
+            .block_on(self.inner.next())
+            .transpose()?
+            .unwrap_or_default();
+
+        Ok(&self.buffer)
+    }
+
+    fn consume(&mut self, cnt: usize) {
+        self.buffer.advance(cnt);
+    }
+}
+
+struct FileKey {
+    file_hash: [u8; 32],
+    extension: &'static str,
+}
+
+impl FileKey {
+    fn get(
+        &self,
+        offset: u64,
+        e_tag: Option<&str>,
+    ) -> impl Future<Output = io::Result<GetObjectOutput>> + Send + 'static {
+        let input = GetObjectRequest {
+            bucket: BUCKET.to_string(),
+            key: format!(
+                "nar/{}.nar.{}",
+                nixbase32::encode(&self.file_hash),
+                self.extension
+            ),
+            if_match: e_tag.map(str::to_owned),
+            range: Some(format!("bytes {}-", offset + 1)),
+            ..Default::default()
+        };
+
+        async {
+            S3_CLIENT
+                .get_object(input)
+                .await
+                .map_err(|e| io::Error::new(io::ErrorKind::Other, e))
+        }
+    }
+}
+
+struct FileStream {
+    key: FileKey,
+    e_tag: String,
+    offset: u64,
+    length: u64,
+    inner: FileStreamState,
+}
+
+enum FileStreamState {
+    Response(BoxFuture<'static, io::Result<GetObjectOutput>>),
+    Body(ByteStream),
+    Eof,
+}
+
+impl FileStream {
+    pub async fn new(key: FileKey) -> io::Result<Self> {
+        let resp = key.get(0, None).await?;
+
+        Ok(FileStream {
+            key,
+            e_tag: resp.e_tag.unwrap(),
+            offset: 0,
+            length: resp.content_length.unwrap().try_into().unwrap(),
+            inner: FileStreamState::Body(resp.body.unwrap()),
+        })
+    }
+}
+
+macro_rules! poll {
+    ($expr:expr) => {
+        match $expr {
+            Poll::Pending => {
+                return Poll::Pending;
+            }
+            Poll::Ready(value) => value,
+        }
+    };
+}
+
+impl Stream for FileStream {
+    type Item = io::Result<Bytes>;
+
+    fn poll_next(self: Pin<&mut Self>, cx: &mut task::Context) -> Poll<Option<Self::Item>> {
+        let this = self.get_mut();
+
+        let chunk = loop {
+            match &mut this.inner {
+                FileStreamState::Response(resp) => match poll!(resp.poll_unpin(cx)) {
+                    Err(err) => {
+                        this.inner = FileStreamState::Eof;
+                        return Poll::Ready(Some(Err(err)));
+                    }
+                    Ok(resp) => {
+                        this.inner = FileStreamState::Body(resp.body.unwrap());
+                    }
+                },
+                FileStreamState::Body(body) => match poll!(body.poll_next_unpin(cx)) {
+                    None | Some(Err(_)) => {
+                        this.inner = FileStreamState::Response(
+                            this.key.get(this.offset, Some(&this.e_tag)).boxed(),
+                        );
+                    }
+                    Some(Ok(chunk)) => {
+                        break chunk;
+                    }
+                },
+                FileStreamState::Eof => {
+                    return Poll::Ready(None);
+                }
+            }
+        };
+
+        this.offset += chunk.len() as u64;
+
+        if this.offset >= this.length {
+            this.inner = FileStreamState::Eof;
+        }
+
+        Poll::Ready(Some(Ok(chunk)))
+    }
+}
diff --git a/tvix/tools/narinfo2parquet/Cargo.lock b/tvix/tools/narinfo2parquet/Cargo.lock
new file mode 100644
index 0000000000..e59f70732d
--- /dev/null
+++ b/tvix/tools/narinfo2parquet/Cargo.lock
@@ -0,0 +1,2144 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "addr2line"
+version = "0.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb"
+dependencies = [
+ "gimli",
+]
+
+[[package]]
+name = "adler"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+
+[[package]]
+name = "ahash"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a"
+dependencies = [
+ "cfg-if",
+ "getrandom",
+ "once_cell",
+ "version_check",
+ "zerocopy",
+]
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "alloc-no-stdlib"
+version = "2.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3"
+
+[[package]]
+name = "alloc-stdlib"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece"
+dependencies = [
+ "alloc-no-stdlib",
+]
+
+[[package]]
+name = "allocator-api2"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5"
+
+[[package]]
+name = "android-tzdata"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
+
+[[package]]
+name = "android_system_properties"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.75"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
+dependencies = [
+ "backtrace",
+]
+
+[[package]]
+name = "argminmax"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "202108b46429b765ef483f8a24d5c46f48c14acfdacc086dd4ab6dddf6bcdbd2"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "array-init-cursor"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf7d0a018de4f6aa429b9d33d69edf69072b1c5b1cb8d3e4a5f7ef898fc3eb76"
+
+[[package]]
+name = "arrow-format"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07884ea216994cdc32a2d5f8274a8bee979cfe90274b83f86f440866ee3132c7"
+dependencies = [
+ "planus",
+ "serde",
+]
+
+[[package]]
+name = "async-stream"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51"
+dependencies = [
+ "async-stream-impl",
+ "futures-core",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "async-stream-impl"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.39",
+]
+
+[[package]]
+name = "async-trait"
+version = "0.1.74"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.39",
+]
+
+[[package]]
+name = "atoi"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "atoi_simd"
+version = "0.15.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ae037714f313c1353189ead58ef9eec30a8e8dc101b2622d461418fd59e28a9"
+
+[[package]]
+name = "autocfg"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+
+[[package]]
+name = "backtrace"
+version = "0.3.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837"
+dependencies = [
+ "addr2line",
+ "cc",
+ "cfg-if",
+ "libc",
+ "miniz_oxide",
+ "object",
+ "rustc-demangle",
+]
+
+[[package]]
+name = "base64"
+version = "0.21.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9"
+
+[[package]]
+name = "base64ct"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "bitflags"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
+
+[[package]]
+name = "block-buffer"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "brotli"
+version = "3.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "516074a47ef4bce09577a3b379392300159ce5b1ba2e501ff1c819950066100f"
+dependencies = [
+ "alloc-no-stdlib",
+ "alloc-stdlib",
+ "brotli-decompressor",
+]
+
+[[package]]
+name = "brotli-decompressor"
+version = "2.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4e2e4afe60d7dd600fdd3de8d0f08c2b7ec039712e3b6137ff98b7004e82de4f"
+dependencies = [
+ "alloc-no-stdlib",
+ "alloc-stdlib",
+]
+
+[[package]]
+name = "bstr"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c79ad7fb2dd38f3dabd76b09c6a5a20c038fc0213ef1e9afd30eb777f120f019"
+dependencies = [
+ "memchr",
+ "regex-automata",
+ "serde",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"
+
+[[package]]
+name = "bytemuck"
+version = "1.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6"
+dependencies = [
+ "bytemuck_derive",
+]
+
+[[package]]
+name = "bytemuck_derive"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "965ab7eb5f8f97d2a083c799f3a1b994fc397b2fe2da5d1da1626ce15a39f2b1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.39",
+]
+
+[[package]]
+name = "bytes"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
+
+[[package]]
+name = "cc"
+version = "1.0.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
+dependencies = [
+ "jobserver",
+ "libc",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "chrono"
+version = "0.4.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38"
+dependencies = [
+ "android-tzdata",
+ "iana-time-zone",
+ "num-traits",
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "const-oid"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f"
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
+
+[[package]]
+name = "cpufeatures"
+version = "0.2.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crc32fast"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "crossbeam-channel"
+version = "0.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200"
+dependencies = [
+ "cfg-if",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-deque"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef"
+dependencies = [
+ "cfg-if",
+ "crossbeam-epoch",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.9.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7"
+dependencies = [
+ "autocfg",
+ "cfg-if",
+ "crossbeam-utils",
+ "memoffset",
+ "scopeguard",
+]
+
+[[package]]
+name = "crossbeam-queue"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add"
+dependencies = [
+ "cfg-if",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "crypto-common"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
+dependencies = [
+ "generic-array",
+ "typenum",
+]
+
+[[package]]
+name = "curve25519-dalek"
+version = "4.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e89b8c6a2e4b1f45971ad09761aafb85514a84744b67a95e32c3cc1352d1f65c"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "curve25519-dalek-derive",
+ "digest",
+ "fiat-crypto",
+ "platforms",
+ "rustc_version",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "curve25519-dalek-derive"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.39",
+]
+
+[[package]]
+name = "data-encoding"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"
+
+[[package]]
+name = "der"
+version = "0.7.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c"
+dependencies = [
+ "const-oid",
+ "zeroize",
+]
+
+[[package]]
+name = "digest"
+version = "0.10.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
+dependencies = [
+ "block-buffer",
+ "crypto-common",
+]
+
+[[package]]
+name = "dyn-clone"
+version = "1.0.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "545b22097d44f8a9581187cdf93de7a71e4722bf51200cfaba810865b49a495d"
+
+[[package]]
+name = "ed25519"
+version = "2.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53"
+dependencies = [
+ "pkcs8",
+ "signature",
+]
+
+[[package]]
+name = "ed25519-dalek"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f628eaec48bfd21b865dc2950cfa014450c01d2fa2b69a86c2fd5844ec523c0"
+dependencies = [
+ "curve25519-dalek",
+ "ed25519",
+ "serde",
+ "sha2",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "either"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
+
+[[package]]
+name = "enum_dispatch"
+version = "0.3.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f33313078bb8d4d05a2733a94ac4c2d8a0df9a2b84424ebf4f33bfc224a890e"
+dependencies = [
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.39",
+]
+
+[[package]]
+name = "equivalent"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
+
+[[package]]
+name = "errno"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c18ee0ed65a5f1f81cac6b1d213b69c35fa47d4252ad41f1486dbd8226fe36e"
+dependencies = [
+ "libc",
+ "windows-sys",
+]
+
+[[package]]
+name = "ethnum"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b90ca2580b73ab6a1f724b76ca11ab632df820fd6040c336200d2c1df7b3c82c"
+
+[[package]]
+name = "fallible-streaming-iterator"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
+
+[[package]]
+name = "fast-float"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95765f67b4b18863968b4a1bd5bb576f732b29a4a28c7cd84c09fa3e2875f33c"
+
+[[package]]
+name = "fastrand"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
+
+[[package]]
+name = "fiat-crypto"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "27573eac26f4dd11e2b1916c3fe1baa56407c83c71a773a8ba17ec0bca03b6b7"
+
+[[package]]
+name = "flate2"
+version = "1.0.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e"
+dependencies = [
+ "crc32fast",
+ "miniz_oxide",
+]
+
+[[package]]
+name = "foreign_vec"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee1b05cbd864bcaecbd3455d6d967862d446e4ebfc3c2e5e5b9841e53cba6673"
+
+[[package]]
+name = "futures"
+version = "0.3.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-io",
+ "futures-sink",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c"
+
+[[package]]
+name = "futures-executor"
+version = "0.3.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-io"
+version = "0.3.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa"
+
+[[package]]
+name = "futures-macro"
+version = "0.3.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.39",
+]
+
+[[package]]
+name = "futures-sink"
+version = "0.3.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817"
+
+[[package]]
+name = "futures-task"
+version = "0.3.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2"
+
+[[package]]
+name = "futures-util"
+version = "0.3.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-macro",
+ "futures-sink",
+ "futures-task",
+ "memchr",
+ "pin-project-lite",
+ "pin-utils",
+ "slab",
+]
+
+[[package]]
+name = "generic-array"
+version = "0.14.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "libc",
+ "wasi",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "gimli"
+version = "0.28.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0"
+
+[[package]]
+name = "glob"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
+
+[[package]]
+name = "hashbrown"
+version = "0.14.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156"
+dependencies = [
+ "ahash",
+ "allocator-api2",
+ "rayon",
+]
+
+[[package]]
+name = "heck"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
+
+[[package]]
+name = "hermit-abi"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
+
+[[package]]
+name = "home"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb"
+dependencies = [
+ "windows-sys",
+]
+
+[[package]]
+name = "iana-time-zone"
+version = "0.1.58"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "iana-time-zone-haiku",
+ "js-sys",
+ "wasm-bindgen",
+ "windows-core 0.51.1",
+]
+
+[[package]]
+name = "iana-time-zone-haiku"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "indexmap"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f"
+dependencies = [
+ "equivalent",
+ "hashbrown",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
+
+[[package]]
+name = "jemalloc-sys"
+version = "0.5.4+5.3.0-patched"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac6c1946e1cea1788cbfde01c993b52a10e2da07f4bac608228d1bed20bfebf2"
+dependencies = [
+ "cc",
+ "libc",
+]
+
+[[package]]
+name = "jemallocator"
+version = "0.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a0de374a9f8e63150e6f5e8a60cc14c668226d7a347d8aee1a45766e3c4dd3bc"
+dependencies = [
+ "jemalloc-sys",
+ "libc",
+]
+
+[[package]]
+name = "jobserver"
+version = "0.1.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "js-sys"
+version = "0.3.65"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "54c0c35952f67de54bb584e9fd912b3023117cbafc0a77d8f3dee1fb5f572fe8"
+dependencies = [
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.150"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c"
+
+[[package]]
+name = "libm"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058"
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.4.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f"
+
+[[package]]
+name = "log"
+version = "0.4.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
+
+[[package]]
+name = "lz4"
+version = "1.24.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e9e2dd86df36ce760a60f6ff6ad526f7ba1f14ba0356f8254fb6905e6494df1"
+dependencies = [
+ "libc",
+ "lz4-sys",
+]
+
+[[package]]
+name = "lz4-sys"
+version = "1.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57d27b317e207b10f69f5e75494119e391a96f48861ae870d1da6edac98ca900"
+dependencies = [
+ "cc",
+ "libc",
+]
+
+[[package]]
+name = "memchr"
+version = "2.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
+
+[[package]]
+name = "memmap2"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f49388d20533534cd19360ad3d6a7dadc885944aa802ba3995040c5ec11288c6"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "memoffset"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "minimal-lexical"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
+
+[[package]]
+name = "miniz_oxide"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
+dependencies = [
+ "adler",
+]
+
+[[package]]
+name = "mio"
+version = "0.8.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0"
+dependencies = [
+ "libc",
+ "wasi",
+ "windows-sys",
+]
+
+[[package]]
+name = "multiversion"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2c7b9d7fe61760ce5ea19532ead98541f6b4c495d87247aff9826445cf6872a"
+dependencies = [
+ "multiversion-macros",
+ "target-features",
+]
+
+[[package]]
+name = "multiversion-macros"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26a83d8500ed06d68877e9de1dde76c1dbb83885dcdbda4ef44ccbc3fbda2ac8"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+ "target-features",
+]
+
+[[package]]
+name = "narinfo2parquet"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "jemallocator",
+ "nix-compat",
+ "polars",
+ "tempfile-fast",
+ "zstd",
+]
+
+[[package]]
+name = "nix-compat"
+version = "0.1.0"
+dependencies = [
+ "bitflags 2.4.1",
+ "bstr",
+ "data-encoding",
+ "ed25519",
+ "ed25519-dalek",
+ "glob",
+ "nom",
+ "serde",
+ "serde_json",
+ "sha2",
+ "thiserror",
+]
+
+[[package]]
+name = "nom"
+version = "7.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
+dependencies = [
+ "memchr",
+ "minimal-lexical",
+]
+
+[[package]]
+name = "now"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d89e9874397a1f0a52fc1f197a8effd9735223cb2390e9dcc83ac6cd02923d0"
+dependencies = [
+ "chrono",
+]
+
+[[package]]
+name = "ntapi"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c"
+dependencies = [
+ "autocfg",
+ "libm",
+]
+
+[[package]]
+name = "num_cpus"
+version = "1.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
+dependencies = [
+ "hermit-abi",
+ "libc",
+]
+
+[[package]]
+name = "object"
+version = "0.32.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
+
+[[package]]
+name = "parquet-format-safe"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1131c54b167dd4e4799ce762e1ab01549ebb94d5bdd13e6ec1b467491c378e1f"
+dependencies = [
+ "async-trait",
+ "futures",
+]
+
+[[package]]
+name = "percent-encoding"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
+[[package]]
+name = "pkcs8"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
+dependencies = [
+ "der",
+ "spki",
+]
+
+[[package]]
+name = "pkg-config"
+version = "0.3.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
+
+[[package]]
+name = "planus"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc1691dd09e82f428ce8d6310bd6d5da2557c82ff17694d2a32cad7242aea89f"
+dependencies = [
+ "array-init-cursor",
+]
+
+[[package]]
+name = "platforms"
+version = "3.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "14e6ab3f592e6fb464fc9712d8d6e6912de6473954635fd76a589d832cffcbb0"
+
+[[package]]
+name = "polars"
+version = "0.36.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "938048fcda6a8e2ace6eb168bee1b415a92423ce51e418b853bf08fc40349b6b"
+dependencies = [
+ "getrandom",
+ "polars-core",
+ "polars-io",
+ "polars-lazy",
+ "polars-ops",
+ "polars-sql",
+ "version_check",
+]
+
+[[package]]
+name = "polars-arrow"
+version = "0.36.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce68a02f698ff7787c261aea1b4c040a8fe183a8fb200e2436d7f35d95a1b86f"
+dependencies = [
+ "ahash",
+ "arrow-format",
+ "atoi_simd",
+ "bytemuck",
+ "chrono",
+ "dyn-clone",
+ "either",
+ "ethnum",
+ "fast-float",
+ "foreign_vec",
+ "futures",
+ "getrandom",
+ "hashbrown",
+ "itoa",
+ "lz4",
+ "multiversion",
+ "num-traits",
+ "polars-error",
+ "polars-utils",
+ "ryu",
+ "simdutf8",
+ "streaming-iterator",
+ "strength_reduce",
+ "version_check",
+ "zstd",
+]
+
+[[package]]
+name = "polars-compute"
+version = "0.36.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b14fbc5f141b29b656a4cec4802632e5bff10bf801c6809c6bbfbd4078a044dd"
+dependencies = [
+ "bytemuck",
+ "num-traits",
+ "polars-arrow",
+ "polars-utils",
+ "version_check",
+]
+
+[[package]]
+name = "polars-core"
+version = "0.36.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0f5efe734b6cbe5f97ea769be8360df5324fade396f1f3f5ad7fe9360ca4a23"
+dependencies = [
+ "ahash",
+ "bitflags 2.4.1",
+ "bytemuck",
+ "chrono",
+ "either",
+ "hashbrown",
+ "indexmap",
+ "num-traits",
+ "once_cell",
+ "polars-arrow",
+ "polars-compute",
+ "polars-error",
+ "polars-row",
+ "polars-utils",
+ "rand",
+ "rand_distr",
+ "rayon",
+ "regex",
+ "smartstring",
+ "thiserror",
+ "version_check",
+ "xxhash-rust",
+]
+
+[[package]]
+name = "polars-error"
+version = "0.36.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6396de788f99ebfc9968e7b6f523e23000506cde4ba6dfc62ae4ce949002a886"
+dependencies = [
+ "arrow-format",
+ "regex",
+ "simdutf8",
+ "thiserror",
+]
+
+[[package]]
+name = "polars-io"
+version = "0.36.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d0458efe8946f4718fd352f230c0db5a37926bd0d2bd25af79dc24746abaaea"
+dependencies = [
+ "ahash",
+ "async-trait",
+ "bytes",
+ "futures",
+ "home",
+ "memchr",
+ "memmap2",
+ "num-traits",
+ "once_cell",
+ "percent-encoding",
+ "polars-arrow",
+ "polars-core",
+ "polars-error",
+ "polars-parquet",
+ "polars-utils",
+ "rayon",
+ "regex",
+ "smartstring",
+ "tokio",
+ "tokio-util",
+]
+
+[[package]]
+name = "polars-lazy"
+version = "0.36.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d7105b40905bb38e8fc4a7fd736594b7491baa12fad3ac492969ca221a1b5d5"
+dependencies = [
+ "ahash",
+ "bitflags 2.4.1",
+ "glob",
+ "once_cell",
+ "polars-arrow",
+ "polars-core",
+ "polars-io",
+ "polars-ops",
+ "polars-pipe",
+ "polars-plan",
+ "polars-time",
+ "polars-utils",
+ "rayon",
+ "smartstring",
+ "version_check",
+]
+
+[[package]]
+name = "polars-ops"
+version = "0.36.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e09afc456ab11e75e5dcb43e00a01c71f3a46a2781e450054acb6bb096ca78e"
+dependencies = [
+ "ahash",
+ "argminmax",
+ "bytemuck",
+ "either",
+ "hashbrown",
+ "indexmap",
+ "memchr",
+ "num-traits",
+ "polars-arrow",
+ "polars-compute",
+ "polars-core",
+ "polars-error",
+ "polars-utils",
+ "rayon",
+ "regex",
+ "smartstring",
+ "version_check",
+]
+
+[[package]]
+name = "polars-parquet"
+version = "0.36.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ba24d67b1f64ab85143033dd46fa090b13c0f74acdf91b0780c16aecf005e3d"
+dependencies = [
+ "ahash",
+ "async-stream",
+ "base64",
+ "brotli",
+ "ethnum",
+ "flate2",
+ "futures",
+ "lz4",
+ "num-traits",
+ "parquet-format-safe",
+ "polars-arrow",
+ "polars-error",
+ "polars-utils",
+ "seq-macro",
+ "simdutf8",
+ "snap",
+ "streaming-decompression",
+ "zstd",
+]
+
+[[package]]
+name = "polars-pipe"
+version = "0.36.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b7ead073cc3917027d77b59861a9f071db47125de9314f8907db1a0a3e4100"
+dependencies = [
+ "crossbeam-channel",
+ "crossbeam-queue",
+ "enum_dispatch",
+ "hashbrown",
+ "num-traits",
+ "polars-arrow",
+ "polars-compute",
+ "polars-core",
+ "polars-io",
+ "polars-ops",
+ "polars-plan",
+ "polars-row",
+ "polars-utils",
+ "rayon",
+ "smartstring",
+ "version_check",
+]
+
+[[package]]
+name = "polars-plan"
+version = "0.36.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "384a175624d050c31c473ee11df9d7af5d729ae626375e522158cfb3d150acd0"
+dependencies = [
+ "ahash",
+ "bytemuck",
+ "once_cell",
+ "percent-encoding",
+ "polars-arrow",
+ "polars-core",
+ "polars-io",
+ "polars-ops",
+ "polars-parquet",
+ "polars-time",
+ "polars-utils",
+ "rayon",
+ "regex",
+ "smartstring",
+ "strum_macros",
+ "version_check",
+]
+
+[[package]]
+name = "polars-row"
+version = "0.36.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32322f7acbb83db3e9c7697dc821be73d06238da89c817dcc8bc1549a5e9c72f"
+dependencies = [
+ "polars-arrow",
+ "polars-error",
+ "polars-utils",
+]
+
+[[package]]
+name = "polars-sql"
+version = "0.36.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f0b4c6ddffdfd0453e84bc3918572c633014d661d166654399cf93752aa95b5"
+dependencies = [
+ "polars-arrow",
+ "polars-core",
+ "polars-error",
+ "polars-lazy",
+ "polars-plan",
+ "rand",
+ "serde",
+ "serde_json",
+ "sqlparser",
+]
+
+[[package]]
+name = "polars-time"
+version = "0.36.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dee2649fc96bd1b6584e0e4a4b3ca7d22ed3d117a990e63ad438ecb26f7544d0"
+dependencies = [
+ "atoi",
+ "chrono",
+ "now",
+ "once_cell",
+ "polars-arrow",
+ "polars-core",
+ "polars-error",
+ "polars-ops",
+ "polars-utils",
+ "regex",
+ "smartstring",
+]
+
+[[package]]
+name = "polars-utils"
+version = "0.36.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b174ca4a77ad47d7b91a0460aaae65bbf874c8bfbaaa5308675dadef3976bbda"
+dependencies = [
+ "ahash",
+ "bytemuck",
+ "hashbrown",
+ "indexmap",
+ "num-traits",
+ "once_cell",
+ "polars-error",
+ "rayon",
+ "smartstring",
+ "sysinfo",
+ "version_check",
+]
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.33"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "libc",
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "rand_distr"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31"
+dependencies = [
+ "num-traits",
+ "rand",
+]
+
+[[package]]
+name = "rayon"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1"
+dependencies = [
+ "either",
+ "rayon-core",
+]
+
+[[package]]
+name = "rayon-core"
+version = "1.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed"
+dependencies = [
+ "crossbeam-deque",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
+dependencies = [
+ "bitflags 1.3.2",
+]
+
+[[package]]
+name = "regex"
+version = "1.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
+
+[[package]]
+name = "rustc-demangle"
+version = "0.1.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
+
+[[package]]
+name = "rustc_version"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
+dependencies = [
+ "semver",
+]
+
+[[package]]
+name = "rustix"
+version = "0.38.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3"
+dependencies = [
+ "bitflags 2.4.1",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys",
+]
+
+[[package]]
+name = "rustversion"
+version = "1.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4"
+
+[[package]]
+name = "ryu"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
+
+[[package]]
+name = "scopeguard"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+
+[[package]]
+name = "semver"
+version = "1.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090"
+
+[[package]]
+name = "seq-macro"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a3f0bf26fd526d2a95683cd0f87bf103b8539e2ca1ef48ce002d67aad59aa0b4"
+
+[[package]]
+name = "serde"
+version = "1.0.192"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.192"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.39",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.108"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "sha2"
+version = "0.10.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
+[[package]]
+name = "signature"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
+dependencies = [
+ "rand_core",
+]
+
+[[package]]
+name = "simdutf8"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a"
+
+[[package]]
+name = "slab"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "smartstring"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29"
+dependencies = [
+ "autocfg",
+ "static_assertions",
+ "version_check",
+]
+
+[[package]]
+name = "snap"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e9f0ab6ef7eb7353d9119c170a436d1bf248eea575ac42d19d12f4e34130831"
+
+[[package]]
+name = "socket2"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9"
+dependencies = [
+ "libc",
+ "windows-sys",
+]
+
+[[package]]
+name = "spki"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a"
+dependencies = [
+ "base64ct",
+ "der",
+]
+
+[[package]]
+name = "sqlparser"
+version = "0.39.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "743b4dc2cbde11890ccb254a8fc9d537fa41b36da00de2a1c5e9848c9bc42bd7"
+dependencies = [
+ "log",
+]
+
+[[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
+[[package]]
+name = "streaming-decompression"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf6cc3b19bfb128a8ad11026086e31d3ce9ad23f8ea37354b31383a187c44cf3"
+dependencies = [
+ "fallible-streaming-iterator",
+]
+
+[[package]]
+name = "streaming-iterator"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b2231b7c3057d5e4ad0156fb3dc807d900806020c5ffa3ee6ff2c8c76fb8520"
+
+[[package]]
+name = "strength_reduce"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82"
+
+[[package]]
+name = "strum_macros"
+version = "0.25.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "rustversion",
+ "syn 2.0.39",
+]
+
+[[package]]
+name = "subtle"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
+
+[[package]]
+name = "syn"
+version = "1.0.109"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.39"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "sysinfo"
+version = "0.30.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fb4f3438c8f6389c864e61221cbc97e9bca98b4daf39a5beb7bea660f528bb2"
+dependencies = [
+ "cfg-if",
+ "core-foundation-sys",
+ "libc",
+ "ntapi",
+ "once_cell",
+ "windows",
+]
+
+[[package]]
+name = "target-features"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfb5fa503293557c5158bd215fdc225695e567a77e453f5d4452a50a193969bd"
+
+[[package]]
+name = "tempfile"
+version = "3.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5"
+dependencies = [
+ "cfg-if",
+ "fastrand",
+ "redox_syscall",
+ "rustix",
+ "windows-sys",
+]
+
+[[package]]
+name = "tempfile-fast"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a74be8531b1a9d607004a32b8f50dd8093b09ec6b0a6af004e33051068e87af6"
+dependencies = [
+ "libc",
+ "rand",
+ "tempfile",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.50"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.50"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.39",
+]
+
+[[package]]
+name = "tokio"
+version = "1.33.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4f38200e3ef7995e5ef13baec2f432a6da0aa9ac495b2c0e8f3b7eec2c92d653"
+dependencies = [
+ "backtrace",
+ "bytes",
+ "libc",
+ "mio",
+ "num_cpus",
+ "pin-project-lite",
+ "socket2",
+ "windows-sys",
+]
+
+[[package]]
+name = "tokio-util"
+version = "0.7.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "futures-sink",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "typenum"
+version = "1.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+
+[[package]]
+name = "version_check"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.88"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce"
+dependencies = [
+ "cfg-if",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.88"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e397f4664c0e4e428e8313a469aaa58310d302159845980fd23b0f22a847f217"
+dependencies = [
+ "bumpalo",
+ "log",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.39",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.88"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5961017b3b08ad5f3fe39f1e79877f8ee7c23c5e5fd5eb80de95abc41f1f16b2"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.88"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.39",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.88"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b"
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "windows"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be"
+dependencies = [
+ "windows-core 0.52.0",
+ "windows-targets 0.52.0",
+]
+
+[[package]]
+name = "windows-core"
+version = "0.51.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64"
+dependencies = [
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "windows-core"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
+dependencies = [
+ "windows-targets 0.52.0",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+dependencies = [
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
+dependencies = [
+ "windows_aarch64_gnullvm 0.48.5",
+ "windows_aarch64_msvc 0.48.5",
+ "windows_i686_gnu 0.48.5",
+ "windows_i686_msvc 0.48.5",
+ "windows_x86_64_gnu 0.48.5",
+ "windows_x86_64_gnullvm 0.48.5",
+ "windows_x86_64_msvc 0.48.5",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
+dependencies = [
+ "windows_aarch64_gnullvm 0.52.0",
+ "windows_aarch64_msvc 0.52.0",
+ "windows_i686_gnu 0.52.0",
+ "windows_i686_msvc 0.52.0",
+ "windows_x86_64_gnu 0.52.0",
+ "windows_x86_64_gnullvm 0.52.0",
+ "windows_x86_64_msvc 0.52.0",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
+
+[[package]]
+name = "xxhash-rust"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9828b178da53440fa9c766a3d2f73f7cf5d0ac1fe3980c1e5018d899fd19e07b"
+
+[[package]]
+name = "zerocopy"
+version = "0.7.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8cd369a67c0edfef15010f980c3cbe45d7f651deac2cd67ce097cd801de16557"
+dependencies = [
+ "zerocopy-derive",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.7.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2f140bda219a26ccc0cdb03dba58af72590c53b22642577d88a927bc5c87d6b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.39",
+]
+
+[[package]]
+name = "zeroize"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d"
+
+[[package]]
+name = "zstd"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bffb3309596d527cfcba7dfc6ed6052f1d39dfbd7c867aa2e865e4a449c10110"
+dependencies = [
+ "zstd-safe",
+]
+
+[[package]]
+name = "zstd-safe"
+version = "7.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43747c7422e2924c11144d5229878b98180ef8b06cca4ab5af37afc8a8d8ea3e"
+dependencies = [
+ "zstd-sys",
+]
+
+[[package]]
+name = "zstd-sys"
+version = "2.0.9+zstd.1.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656"
+dependencies = [
+ "cc",
+ "pkg-config",
+]
diff --git a/tvix/tools/narinfo2parquet/Cargo.nix b/tvix/tools/narinfo2parquet/Cargo.nix
new file mode 100644
index 0000000000..27a5d684b6
--- /dev/null
+++ b/tvix/tools/narinfo2parquet/Cargo.nix
@@ -0,0 +1,8528 @@
+# This file was @generated by crate2nix 0.13.0 with the command:
+#   "generate" "--all-features"
+# See https://github.com/kolloch/crate2nix for more info.
+
+{ nixpkgs ? <nixpkgs>
+, pkgs ? import nixpkgs { config = { }; }
+, lib ? pkgs.lib
+, stdenv ? pkgs.stdenv
+, buildRustCrateForPkgs ? pkgs: pkgs.buildRustCrate
+  # This is used as the `crateOverrides` argument for `buildRustCrate`.
+, defaultCrateOverrides ? pkgs.defaultCrateOverrides
+  # The features to enable for the root_crate or the workspace_members.
+, rootFeatures ? [ "default" ]
+  # If true, throw errors instead of issueing deprecation warnings.
+, strictDeprecation ? false
+  # Used for conditional compilation based on CPU feature detection.
+, targetFeatures ? [ ]
+  # Whether to perform release builds: longer compile times, faster binaries.
+, release ? true
+  # Additional crate2nix configuration if it exists.
+, crateConfig ? if builtins.pathExists ./crate-config.nix
+  then pkgs.callPackage ./crate-config.nix { }
+  else { }
+}:
+
+rec {
+  #
+  # "public" attributes that we attempt to keep stable with new versions of crate2nix.
+  #
+
+  rootCrate = rec {
+    packageId = "narinfo2parquet";
+
+    # Use this attribute to refer to the derivation building your root crate package.
+    # You can override the features with rootCrate.build.override { features = [ "default" "feature1" ... ]; }.
+    build = internal.buildRustCrateWithFeatures {
+      inherit packageId;
+    };
+
+    # Debug support which might change between releases.
+    # File a bug if you depend on any for non-debug work!
+    debug = internal.debugCrate { inherit packageId; };
+  };
+  # Refer your crate build derivation by name here.
+  # You can override the features with
+  # workspaceMembers."${crateName}".build.override { features = [ "default" "feature1" ... ]; }.
+  workspaceMembers = {
+    "narinfo2parquet" = rec {
+      packageId = "narinfo2parquet";
+      build = internal.buildRustCrateWithFeatures {
+        packageId = "narinfo2parquet";
+      };
+
+      # Debug support which might change between releases.
+      # File a bug if you depend on any for non-debug work!
+      debug = internal.debugCrate { inherit packageId; };
+    };
+  };
+
+  # A derivation that joins the outputs of all workspace members together.
+  allWorkspaceMembers = pkgs.symlinkJoin {
+    name = "all-workspace-members";
+    paths =
+      let members = builtins.attrValues workspaceMembers;
+      in builtins.map (m: m.build) members;
+  };
+
+  #
+  # "internal" ("private") attributes that may change in every new version of crate2nix.
+  #
+
+  internal = rec {
+    # Build and dependency information for crates.
+    # Many of the fields are passed one-to-one to buildRustCrate.
+    #
+    # Noteworthy:
+    # * `dependencies`/`buildDependencies`: similar to the corresponding fields for buildRustCrate.
+    #   but with additional information which is used during dependency/feature resolution.
+    # * `resolvedDependencies`: the selected default features reported by cargo - only included for debugging.
+    # * `devDependencies` as of now not used by `buildRustCrate` but used to
+    #   inject test dependencies into the build
+
+    crates = {
+      "addr2line" = rec {
+        crateName = "addr2line";
+        version = "0.21.0";
+        edition = "2018";
+        sha256 = "1jx0k3iwyqr8klqbzk6kjvr496yd94aspis10vwsj5wy7gib4c4a";
+        dependencies = [
+          {
+            name = "gimli";
+            packageId = "gimli";
+            usesDefaultFeatures = false;
+            features = [ "read" ];
+          }
+        ];
+        features = {
+          "alloc" = [ "dep:alloc" ];
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "core" = [ "dep:core" ];
+          "cpp_demangle" = [ "dep:cpp_demangle" ];
+          "default" = [ "rustc-demangle" "cpp_demangle" "std-object" "fallible-iterator" "smallvec" "memmap2" ];
+          "fallible-iterator" = [ "dep:fallible-iterator" ];
+          "memmap2" = [ "dep:memmap2" ];
+          "object" = [ "dep:object" ];
+          "rustc-demangle" = [ "dep:rustc-demangle" ];
+          "rustc-dep-of-std" = [ "core" "alloc" "compiler_builtins" "gimli/rustc-dep-of-std" ];
+          "smallvec" = [ "dep:smallvec" ];
+          "std" = [ "gimli/std" ];
+          "std-object" = [ "std" "object" "object/std" "object/compression" "gimli/endian-reader" ];
+        };
+      };
+      "adler" = rec {
+        crateName = "adler";
+        version = "1.0.2";
+        edition = "2015";
+        sha256 = "1zim79cvzd5yrkzl3nyfx0avijwgk9fqv3yrscdy1cc79ih02qpj";
+        authors = [
+          "Jonas Schievink <jonasschievink@gmail.com>"
+        ];
+        features = {
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "core" = [ "dep:core" ];
+          "default" = [ "std" ];
+          "rustc-dep-of-std" = [ "core" "compiler_builtins" ];
+        };
+      };
+      "ahash" = rec {
+        crateName = "ahash";
+        version = "0.8.6";
+        edition = "2018";
+        sha256 = "0yn9i8nc6mmv28ig9w3dga571q09vg9f1f650mi5z8phx42r6hli";
+        authors = [
+          "Tom Kaitchuck <Tom.Kaitchuck@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "getrandom";
+            packageId = "getrandom";
+            optional = true;
+          }
+          {
+            name = "once_cell";
+            packageId = "once_cell";
+            usesDefaultFeatures = false;
+            target = { target, features }: (!(("arm" == target."arch" or null) && ("none" == target."os" or null)));
+            features = [ "unstable" "alloc" ];
+          }
+          {
+            name = "zerocopy";
+            packageId = "zerocopy";
+            usesDefaultFeatures = false;
+            features = [ "simd" ];
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "version_check";
+            packageId = "version_check";
+          }
+        ];
+        features = {
+          "atomic-polyfill" = [ "dep:atomic-polyfill" "once_cell/atomic-polyfill" ];
+          "compile-time-rng" = [ "const-random" ];
+          "const-random" = [ "dep:const-random" ];
+          "default" = [ "std" "runtime-rng" ];
+          "getrandom" = [ "dep:getrandom" ];
+          "runtime-rng" = [ "getrandom" ];
+          "serde" = [ "dep:serde" ];
+        };
+        resolvedDefaultFeatures = [ "default" "getrandom" "runtime-rng" "std" ];
+      };
+      "aho-corasick" = rec {
+        crateName = "aho-corasick";
+        version = "1.1.2";
+        edition = "2021";
+        sha256 = "1w510wnixvlgimkx1zjbvlxh6xps2vjgfqgwf5a6adlbjp5rv5mj";
+        libName = "aho_corasick";
+        authors = [
+          "Andrew Gallant <jamslam@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "memchr";
+            packageId = "memchr";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "default" = [ "std" "perf-literal" ];
+          "logging" = [ "dep:log" ];
+          "perf-literal" = [ "dep:memchr" ];
+          "std" = [ "memchr?/std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "perf-literal" "std" ];
+      };
+      "alloc-no-stdlib" = rec {
+        crateName = "alloc-no-stdlib";
+        version = "2.0.4";
+        edition = "2015";
+        crateBin = [ ];
+        sha256 = "1cy6r2sfv5y5cigv86vms7n5nlwhx1rbyxwcraqnmm1rxiib2yyc";
+        authors = [
+          "Daniel Reiter Horn <danielrh@dropbox.com>"
+        ];
+        features = { };
+      };
+      "alloc-stdlib" = rec {
+        crateName = "alloc-stdlib";
+        version = "0.2.2";
+        edition = "2015";
+        crateBin = [ ];
+        sha256 = "1kkfbld20ab4165p29v172h8g0wvq8i06z8vnng14whw0isq5ywl";
+        authors = [
+          "Daniel Reiter Horn <danielrh@dropbox.com>"
+        ];
+        dependencies = [
+          {
+            name = "alloc-no-stdlib";
+            packageId = "alloc-no-stdlib";
+          }
+        ];
+        features = { };
+      };
+      "allocator-api2" = rec {
+        crateName = "allocator-api2";
+        version = "0.2.16";
+        edition = "2018";
+        sha256 = "1iayppgq4wqbfbfcqmsbwgamj0s65012sskfvyx07pxavk3gyhh9";
+        authors = [
+          "Zakarum <zaq.dev@icloud.com>"
+        ];
+        features = {
+          "default" = [ "std" ];
+          "serde" = [ "dep:serde" ];
+          "std" = [ "alloc" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" ];
+      };
+      "android-tzdata" = rec {
+        crateName = "android-tzdata";
+        version = "0.1.1";
+        edition = "2018";
+        sha256 = "1w7ynjxrfs97xg3qlcdns4kgfpwcdv824g611fq32cag4cdr96g9";
+        authors = [
+          "RumovZ"
+        ];
+
+      };
+      "android_system_properties" = rec {
+        crateName = "android_system_properties";
+        version = "0.1.5";
+        edition = "2018";
+        sha256 = "04b3wrz12837j7mdczqd95b732gw5q7q66cv4yn4646lvccp57l1";
+        authors = [
+          "Nicolas Silva <nical@fastmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+          }
+        ];
+
+      };
+      "anyhow" = rec {
+        crateName = "anyhow";
+        version = "1.0.75";
+        edition = "2018";
+        sha256 = "1rmcjkim91c5mw7h9wn8nv0k6x118yz0xg0z1q18svgn42mqqrm4";
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "backtrace";
+            packageId = "backtrace";
+            optional = true;
+          }
+        ];
+        features = {
+          "backtrace" = [ "dep:backtrace" ];
+          "default" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "backtrace" "default" "std" ];
+      };
+      "argminmax" = rec {
+        crateName = "argminmax";
+        version = "0.6.1";
+        edition = "2021";
+        sha256 = "1lnvpkvdsvdbsinhik6srx5c2j3gqkaj92iz93pnbdr9cjs0h890";
+        authors = [
+          "Jeroen Van Der Donckt"
+        ];
+        dependencies = [
+          {
+            name = "num-traits";
+            packageId = "num-traits";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "arrow" = [ "dep:arrow" ];
+          "arrow2" = [ "dep:arrow2" ];
+          "default" = [ "nightly_simd" "float" ];
+          "half" = [ "dep:half" ];
+          "ndarray" = [ "dep:ndarray" ];
+        };
+        resolvedDefaultFeatures = [ "float" ];
+      };
+      "array-init-cursor" = rec {
+        crateName = "array-init-cursor";
+        version = "0.2.0";
+        edition = "2021";
+        sha256 = "0xpbqf7qkvzplpjd7f0wbcf2n1v9vygdccwxkd1amxp4il0hlzdz";
+
+      };
+      "arrow-format" = rec {
+        crateName = "arrow-format";
+        version = "0.8.1";
+        edition = "2018";
+        sha256 = "1irj67p6c224dzw86jr7j3z9r5zfid52gy6ml8rdqk4r2si4x207";
+        authors = [
+          "Jorge C. Leitao <jorgecarleitao@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "planus";
+            packageId = "planus";
+            optional = true;
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+            optional = true;
+            usesDefaultFeatures = false;
+            features = [ "derive" "std" ];
+          }
+        ];
+        features = {
+          "flight-data" = [ "prost" "prost-derive" ];
+          "flight-service" = [ "flight-data" "tonic" ];
+          "full" = [ "ipc" "flight-data" "flight-service" ];
+          "ipc" = [ "planus" "serde" ];
+          "planus" = [ "dep:planus" ];
+          "prost" = [ "dep:prost" ];
+          "prost-derive" = [ "dep:prost-derive" ];
+          "serde" = [ "dep:serde" ];
+          "tonic" = [ "dep:tonic" ];
+        };
+        resolvedDefaultFeatures = [ "default" "ipc" "planus" "serde" ];
+      };
+      "async-stream" = rec {
+        crateName = "async-stream";
+        version = "0.3.5";
+        edition = "2018";
+        sha256 = "0l8sjq1rylkb1ak0pdyjn83b3k6x36j22myngl4sqqgg7whdsmnd";
+        authors = [
+          "Carl Lerche <me@carllerche.com>"
+        ];
+        dependencies = [
+          {
+            name = "async-stream-impl";
+            packageId = "async-stream-impl";
+          }
+          {
+            name = "futures-core";
+            packageId = "futures-core";
+          }
+          {
+            name = "pin-project-lite";
+            packageId = "pin-project-lite";
+          }
+        ];
+
+      };
+      "async-stream-impl" = rec {
+        crateName = "async-stream-impl";
+        version = "0.3.5";
+        edition = "2018";
+        sha256 = "14q179j4y8p2z1d0ic6aqgy9fhwz8p9cai1ia8kpw4bw7q12mrhn";
+        procMacro = true;
+        authors = [
+          "Carl Lerche <me@carllerche.com>"
+        ];
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn 2.0.39";
+            features = [ "full" "visit-mut" ];
+          }
+        ];
+
+      };
+      "async-trait" = rec {
+        crateName = "async-trait";
+        version = "0.1.74";
+        edition = "2021";
+        sha256 = "1ydhbsqjqqa6bxbv0kgys2wq2vi3jpwjy57dk162ajwppgqkfrd6";
+        procMacro = true;
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn 2.0.39";
+            features = [ "full" "visit-mut" ];
+          }
+        ];
+
+      };
+      "atoi" = rec {
+        crateName = "atoi";
+        version = "2.0.0";
+        edition = "2021";
+        sha256 = "0a05h42fggmy7h0ajjv6m7z72l924i7igbx13hk9d8pyign9k3gj";
+        authors = [
+          "Markus Klein"
+        ];
+        dependencies = [
+          {
+            name = "num-traits";
+            packageId = "num-traits";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "std" = [ "num-traits/std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "atoi_simd" = rec {
+        crateName = "atoi_simd";
+        version = "0.15.6";
+        edition = "2018";
+        sha256 = "1a98kvaqyhb1shi2c6qhvklahc7ckvpmibcy319i6g1i9xqkgq4s";
+        authors = [
+          "Dmitry Rodionov <gh@rdmtr.com>"
+        ];
+        features = {
+          "default" = [ "std" ];
+          "std" = [ "arrayvec/std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "autocfg" = rec {
+        crateName = "autocfg";
+        version = "1.1.0";
+        edition = "2015";
+        sha256 = "1ylp3cb47ylzabimazvbz9ms6ap784zhb6syaz6c1jqpmcmq0s6l";
+        authors = [
+          "Josh Stone <cuviper@gmail.com>"
+        ];
+
+      };
+      "backtrace" = rec {
+        crateName = "backtrace";
+        version = "0.3.69";
+        edition = "2018";
+        sha256 = "0dsq23dhw4pfndkx2nsa1ml2g31idm7ss7ljxp8d57avygivg290";
+        authors = [
+          "The Rust Project Developers"
+        ];
+        dependencies = [
+          {
+            name = "addr2line";
+            packageId = "addr2line";
+            usesDefaultFeatures = false;
+            target = { target, features }: (!((target."windows" or false) && ("msvc" == target."env" or null) && (!("uwp" == target."vendor" or null))));
+          }
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            usesDefaultFeatures = false;
+            target = { target, features }: (!((target."windows" or false) && ("msvc" == target."env" or null) && (!("uwp" == target."vendor" or null))));
+          }
+          {
+            name = "miniz_oxide";
+            packageId = "miniz_oxide";
+            usesDefaultFeatures = false;
+            target = { target, features }: (!((target."windows" or false) && ("msvc" == target."env" or null) && (!("uwp" == target."vendor" or null))));
+          }
+          {
+            name = "object";
+            packageId = "object";
+            usesDefaultFeatures = false;
+            target = { target, features }: (!((target."windows" or false) && ("msvc" == target."env" or null) && (!("uwp" == target."vendor" or null))));
+            features = [ "read_core" "elf" "macho" "pe" "unaligned" "archive" ];
+          }
+          {
+            name = "rustc-demangle";
+            packageId = "rustc-demangle";
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "cc";
+            packageId = "cc";
+          }
+        ];
+        features = {
+          "cpp_demangle" = [ "dep:cpp_demangle" ];
+          "default" = [ "std" ];
+          "rustc-serialize" = [ "dep:rustc-serialize" ];
+          "serde" = [ "dep:serde" ];
+          "serialize-rustc" = [ "rustc-serialize" ];
+          "serialize-serde" = [ "serde" ];
+          "verify-winapi" = [ "winapi/dbghelp" "winapi/handleapi" "winapi/libloaderapi" "winapi/memoryapi" "winapi/minwindef" "winapi/processthreadsapi" "winapi/synchapi" "winapi/tlhelp32" "winapi/winbase" "winapi/winnt" ];
+          "winapi" = [ "dep:winapi" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "base64" = rec {
+        crateName = "base64";
+        version = "0.21.5";
+        edition = "2018";
+        sha256 = "1y8x2xs9nszj5ix7gg4ycn5a6wy7ca74zxwqri3bdqzdjha6lqrm";
+        authors = [
+          "Alice Maz <alice@alicemaz.com>"
+          "Marshall Pierce <marshall@mpierce.org>"
+        ];
+        features = {
+          "default" = [ "std" ];
+          "std" = [ "alloc" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "std" ];
+      };
+      "base64ct" = rec {
+        crateName = "base64ct";
+        version = "1.6.0";
+        edition = "2021";
+        sha256 = "0nvdba4jb8aikv60az40x2w1y96sjdq8z3yp09rwzmkhiwv1lg4c";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        features = {
+          "std" = [ "alloc" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" ];
+      };
+      "bitflags 1.3.2" = rec {
+        crateName = "bitflags";
+        version = "1.3.2";
+        edition = "2018";
+        sha256 = "12ki6w8gn1ldq7yz9y680llwk5gmrhrzszaa17g1sbrw2r2qvwxy";
+        authors = [
+          "The Rust Project Developers"
+        ];
+        features = {
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "core" = [ "dep:core" ];
+          "rustc-dep-of-std" = [ "core" "compiler_builtins" ];
+        };
+        resolvedDefaultFeatures = [ "default" ];
+      };
+      "bitflags 2.4.1" = rec {
+        crateName = "bitflags";
+        version = "2.4.1";
+        edition = "2021";
+        sha256 = "01ryy3kd671b0ll4bhdvhsz67vwz1lz53fz504injrd7wpv64xrj";
+        authors = [
+          "The Rust Project Developers"
+        ];
+        features = {
+          "arbitrary" = [ "dep:arbitrary" ];
+          "bytemuck" = [ "dep:bytemuck" ];
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "core" = [ "dep:core" ];
+          "rustc-dep-of-std" = [ "core" "compiler_builtins" ];
+          "serde" = [ "dep:serde" ];
+        };
+        resolvedDefaultFeatures = [ "std" ];
+      };
+      "block-buffer" = rec {
+        crateName = "block-buffer";
+        version = "0.10.4";
+        edition = "2018";
+        sha256 = "0w9sa2ypmrsqqvc20nhwr75wbb5cjr4kkyhpjm1z1lv2kdicfy1h";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        dependencies = [
+          {
+            name = "generic-array";
+            packageId = "generic-array";
+          }
+        ];
+
+      };
+      "brotli" = rec {
+        crateName = "brotli";
+        version = "3.4.0";
+        edition = "2015";
+        crateBin = [ ];
+        sha256 = "03qhcq09a6f8y4gm0bmsn7jrq5804cwpkcx3fyay1g7lgsj78q2i";
+        authors = [
+          "Daniel Reiter Horn <danielrh@dropbox.com>"
+          "The Brotli Authors"
+        ];
+        dependencies = [
+          {
+            name = "alloc-no-stdlib";
+            packageId = "alloc-no-stdlib";
+          }
+          {
+            name = "alloc-stdlib";
+            packageId = "alloc-stdlib";
+            optional = true;
+          }
+          {
+            name = "brotli-decompressor";
+            packageId = "brotli-decompressor";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "alloc-stdlib" = [ "dep:alloc-stdlib" ];
+          "benchmark" = [ "brotli-decompressor/benchmark" ];
+          "default" = [ "std" "ffi-api" ];
+          "disable-timer" = [ "brotli-decompressor/disable-timer" ];
+          "seccomp" = [ "brotli-decompressor/seccomp" ];
+          "sha2" = [ "dep:sha2" ];
+          "std" = [ "alloc-stdlib" "brotli-decompressor/std" ];
+          "validation" = [ "sha2" ];
+        };
+        resolvedDefaultFeatures = [ "alloc-stdlib" "default" "ffi-api" "std" ];
+      };
+      "brotli-decompressor" = rec {
+        crateName = "brotli-decompressor";
+        version = "2.5.1";
+        edition = "2015";
+        crateBin = [ ];
+        sha256 = "0kyyh9701dwqzwvn2frff4ww0zibikqd1s1xvl7n1pfpc3z4lbjf";
+        authors = [
+          "Daniel Reiter Horn <danielrh@dropbox.com>"
+          "The Brotli Authors"
+        ];
+        dependencies = [
+          {
+            name = "alloc-no-stdlib";
+            packageId = "alloc-no-stdlib";
+          }
+          {
+            name = "alloc-stdlib";
+            packageId = "alloc-stdlib";
+            optional = true;
+          }
+        ];
+        features = {
+          "alloc-stdlib" = [ "dep:alloc-stdlib" ];
+          "default" = [ "std" ];
+          "std" = [ "alloc-stdlib" ];
+          "unsafe" = [ "alloc-no-stdlib/unsafe" "alloc-stdlib/unsafe" ];
+        };
+        resolvedDefaultFeatures = [ "alloc-stdlib" "std" ];
+      };
+      "bstr" = rec {
+        crateName = "bstr";
+        version = "1.7.0";
+        edition = "2021";
+        sha256 = "06gh43qpgdqfsfpykw9y4708y0qclajwc2bbsymkv3yk5pxxg6n7";
+        authors = [
+          "Andrew Gallant <jamslam@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "memchr";
+            packageId = "memchr";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "regex-automata";
+            packageId = "regex-automata";
+            optional = true;
+            usesDefaultFeatures = false;
+            features = [ "dfa-search" ];
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "alloc" = [ "memchr/alloc" "serde?/alloc" ];
+          "default" = [ "std" "unicode" ];
+          "serde" = [ "dep:serde" ];
+          "std" = [ "alloc" "memchr/std" "serde?/std" ];
+          "unicode" = [ "dep:regex-automata" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "serde" "std" "unicode" ];
+      };
+      "bumpalo" = rec {
+        crateName = "bumpalo";
+        version = "3.14.0";
+        edition = "2021";
+        sha256 = "1v4arnv9kwk54v5d0qqpv4vyw2sgr660nk0w3apzixi1cm3yfc3z";
+        authors = [
+          "Nick Fitzgerald <fitzgen@gmail.com>"
+        ];
+        features = {
+          "allocator-api2" = [ "dep:allocator-api2" ];
+        };
+        resolvedDefaultFeatures = [ "default" ];
+      };
+      "bytemuck" = rec {
+        crateName = "bytemuck";
+        version = "1.14.0";
+        edition = "2018";
+        sha256 = "1ik1ma5n3bg700skkzhx50zjk7kj7mbsphi773if17l04pn2hk9p";
+        authors = [
+          "Lokathor <zefria@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "bytemuck_derive";
+            packageId = "bytemuck_derive";
+            optional = true;
+          }
+        ];
+        features = {
+          "bytemuck_derive" = [ "dep:bytemuck_derive" ];
+          "derive" = [ "bytemuck_derive" ];
+          "extern_crate_std" = [ "extern_crate_alloc" ];
+        };
+        resolvedDefaultFeatures = [ "bytemuck_derive" "derive" "extern_crate_alloc" ];
+      };
+      "bytemuck_derive" = rec {
+        crateName = "bytemuck_derive";
+        version = "1.5.0";
+        edition = "2018";
+        sha256 = "1cgj75df2v32l4fmvnp25xxkkz4lp6hz76f7hfhd55wgbzmvfnln";
+        procMacro = true;
+        authors = [
+          "Lokathor <zefria@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn 2.0.39";
+          }
+        ];
+
+      };
+      "bytes" = rec {
+        crateName = "bytes";
+        version = "1.5.0";
+        edition = "2018";
+        sha256 = "08w2i8ac912l8vlvkv3q51cd4gr09pwlg3sjsjffcizlrb0i5gd2";
+        authors = [
+          "Carl Lerche <me@carllerche.com>"
+          "Sean McArthur <sean@seanmonstar.com>"
+        ];
+        features = {
+          "default" = [ "std" ];
+          "serde" = [ "dep:serde" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "cc" = rec {
+        crateName = "cc";
+        version = "1.0.83";
+        edition = "2018";
+        crateBin = [ ];
+        sha256 = "1l643zidlb5iy1dskc5ggqs4wqa29a02f44piczqc8zcnsq4y5zi";
+        authors = [
+          "Alex Crichton <alex@alexcrichton.com>"
+        ];
+        dependencies = [
+          {
+            name = "jobserver";
+            packageId = "jobserver";
+            optional = true;
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            usesDefaultFeatures = false;
+            target = { target, features }: (target."unix" or false);
+          }
+        ];
+        features = {
+          "jobserver" = [ "dep:jobserver" ];
+          "parallel" = [ "jobserver" ];
+        };
+        resolvedDefaultFeatures = [ "jobserver" "parallel" ];
+      };
+      "cfg-if" = rec {
+        crateName = "cfg-if";
+        version = "1.0.0";
+        edition = "2018";
+        sha256 = "1za0vb97n4brpzpv8lsbnzmq5r8f2b0cpqqr0sy8h5bn751xxwds";
+        authors = [
+          "Alex Crichton <alex@alexcrichton.com>"
+        ];
+        features = {
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "core" = [ "dep:core" ];
+          "rustc-dep-of-std" = [ "core" "compiler_builtins" ];
+        };
+      };
+      "chrono" = rec {
+        crateName = "chrono";
+        version = "0.4.31";
+        edition = "2021";
+        sha256 = "0f6vg67pipm8cziad2yms6a639pssnvysk1m05dd9crymmdnhb3z";
+        dependencies = [
+          {
+            name = "android-tzdata";
+            packageId = "android-tzdata";
+            optional = true;
+            target = { target, features }: ("android" == target."os" or null);
+          }
+          {
+            name = "iana-time-zone";
+            packageId = "iana-time-zone";
+            optional = true;
+            target = { target, features }: (target."unix" or false);
+            features = [ "fallback" ];
+          }
+          {
+            name = "num-traits";
+            packageId = "num-traits";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "windows-targets";
+            packageId = "windows-targets 0.48.5";
+            optional = true;
+            target = { target, features }: (target."windows" or false);
+          }
+        ];
+        features = {
+          "android-tzdata" = [ "dep:android-tzdata" ];
+          "arbitrary" = [ "dep:arbitrary" ];
+          "clock" = [ "std" "winapi" "iana-time-zone" "android-tzdata" ];
+          "default" = [ "clock" "std" "oldtime" "wasmbind" ];
+          "iana-time-zone" = [ "dep:iana-time-zone" ];
+          "js-sys" = [ "dep:js-sys" ];
+          "pure-rust-locales" = [ "dep:pure-rust-locales" ];
+          "rkyv" = [ "dep:rkyv" ];
+          "rustc-serialize" = [ "dep:rustc-serialize" ];
+          "serde" = [ "dep:serde" ];
+          "unstable-locales" = [ "pure-rust-locales" "alloc" ];
+          "wasm-bindgen" = [ "dep:wasm-bindgen" ];
+          "wasmbind" = [ "wasm-bindgen" "js-sys" ];
+          "winapi" = [ "windows-targets" ];
+          "windows-targets" = [ "dep:windows-targets" ];
+        };
+        resolvedDefaultFeatures = [ "android-tzdata" "clock" "iana-time-zone" "std" "winapi" "windows-targets" ];
+      };
+      "const-oid" = rec {
+        crateName = "const-oid";
+        version = "0.9.5";
+        edition = "2021";
+        sha256 = "0vxb4d25mgk8y0phay7j078limx2553716ixsr1x5605k31j5h98";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        features = {
+          "arbitrary" = [ "dep:arbitrary" ];
+        };
+      };
+      "core-foundation-sys" = rec {
+        crateName = "core-foundation-sys";
+        version = "0.8.4";
+        edition = "2015";
+        sha256 = "1yhf471qj6snnm2mcswai47vsbc9w30y4abmdp4crb4av87sb5p4";
+        authors = [
+          "The Servo Project Developers"
+        ];
+        features = { };
+      };
+      "cpufeatures" = rec {
+        crateName = "cpufeatures";
+        version = "0.2.11";
+        edition = "2018";
+        sha256 = "1l0gzsyy576n017g9bf0vkv5hhg9cpz1h1libxyfdlzcgbh0yhnf";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (pkgs.rust.lib.toRustTarget stdenv.hostPlatform == "aarch64-linux-android");
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (("aarch64" == target."arch" or null) && ("linux" == target."os" or null));
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (("aarch64" == target."arch" or null) && ("apple" == target."vendor" or null));
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (("loongarch64" == target."arch" or null) && ("linux" == target."os" or null));
+          }
+        ];
+
+      };
+      "crc32fast" = rec {
+        crateName = "crc32fast";
+        version = "1.3.2";
+        edition = "2015";
+        sha256 = "03c8f29yx293yf43xar946xbls1g60c207m9drf8ilqhr25vsh5m";
+        authors = [
+          "Sam Rijs <srijs@airpost.net>"
+          "Alex Crichton <alex@alexcrichton.com>"
+        ];
+        dependencies = [
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "crossbeam-channel" = rec {
+        crateName = "crossbeam-channel";
+        version = "0.5.8";
+        edition = "2018";
+        sha256 = "004jz4wxp9k26z657i7rsh9s7586dklx2c5aqf1n3w1dgzvjng53";
+        dependencies = [
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "crossbeam-utils";
+            packageId = "crossbeam-utils";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "crossbeam-utils" = [ "dep:crossbeam-utils" ];
+          "default" = [ "std" ];
+          "std" = [ "crossbeam-utils/std" ];
+        };
+        resolvedDefaultFeatures = [ "crossbeam-utils" "default" "std" ];
+      };
+      "crossbeam-deque" = rec {
+        crateName = "crossbeam-deque";
+        version = "0.8.3";
+        edition = "2018";
+        sha256 = "1vqczbcild7nczh5z116w8w46z991kpjyw7qxkf24c14apwdcvyf";
+        dependencies = [
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "crossbeam-epoch";
+            packageId = "crossbeam-epoch";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "crossbeam-utils";
+            packageId = "crossbeam-utils";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "crossbeam-epoch" = [ "dep:crossbeam-epoch" ];
+          "crossbeam-utils" = [ "dep:crossbeam-utils" ];
+          "default" = [ "std" ];
+          "std" = [ "crossbeam-epoch/std" "crossbeam-utils/std" ];
+        };
+        resolvedDefaultFeatures = [ "crossbeam-epoch" "crossbeam-utils" "default" "std" ];
+      };
+      "crossbeam-epoch" = rec {
+        crateName = "crossbeam-epoch";
+        version = "0.9.15";
+        edition = "2018";
+        sha256 = "1ixwc3cq816wb8rlh3ix4jnybqbyyq4l61nwlx0mfm3ck0s148df";
+        dependencies = [
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "crossbeam-utils";
+            packageId = "crossbeam-utils";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "memoffset";
+            packageId = "memoffset";
+          }
+          {
+            name = "scopeguard";
+            packageId = "scopeguard";
+            usesDefaultFeatures = false;
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "autocfg";
+            packageId = "autocfg";
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "loom" = [ "loom-crate" "crossbeam-utils/loom" ];
+          "loom-crate" = [ "dep:loom-crate" ];
+          "nightly" = [ "crossbeam-utils/nightly" ];
+          "std" = [ "alloc" "crossbeam-utils/std" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "std" ];
+      };
+      "crossbeam-queue" = rec {
+        crateName = "crossbeam-queue";
+        version = "0.3.8";
+        edition = "2018";
+        sha256 = "1p9s6n4ckwdgxkb7a8ay9zjzmgc8ppfbxix2vr07rwskibmb7kyi";
+        dependencies = [
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "crossbeam-utils";
+            packageId = "crossbeam-utils";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "nightly" = [ "crossbeam-utils/nightly" ];
+          "std" = [ "alloc" "crossbeam-utils/std" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "std" ];
+      };
+      "crossbeam-utils" = rec {
+        crateName = "crossbeam-utils";
+        version = "0.8.16";
+        edition = "2018";
+        sha256 = "153j0gikblz7n7qdvdi8pslhi008s1yp9cmny6vw07ad7pbb48js";
+        dependencies = [
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "loom" = [ "dep:loom" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "crypto-common" = rec {
+        crateName = "crypto-common";
+        version = "0.1.6";
+        edition = "2018";
+        sha256 = "1cvby95a6xg7kxdz5ln3rl9xh66nz66w46mm3g56ri1z5x815yqv";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        dependencies = [
+          {
+            name = "generic-array";
+            packageId = "generic-array";
+            features = [ "more_lengths" ];
+          }
+          {
+            name = "typenum";
+            packageId = "typenum";
+          }
+        ];
+        features = {
+          "getrandom" = [ "rand_core/getrandom" ];
+          "rand_core" = [ "dep:rand_core" ];
+        };
+        resolvedDefaultFeatures = [ "std" ];
+      };
+      "curve25519-dalek" = rec {
+        crateName = "curve25519-dalek";
+        version = "4.1.1";
+        edition = "2021";
+        sha256 = "0p7ns5917k6369gajrsbfj24llc5zfm635yh3abla7sb5rm8r6z8";
+        authors = [
+          "Isis Lovecruft <isis@patternsinthevoid.net>"
+          "Henry de Valence <hdevalence@hdevalence.ca>"
+        ];
+        dependencies = [
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "cpufeatures";
+            packageId = "cpufeatures";
+            target = { target, features }: ("x86_64" == target."arch" or null);
+          }
+          {
+            name = "curve25519-dalek-derive";
+            packageId = "curve25519-dalek-derive";
+            target = { target, features }: ((!("fiat" == target."curve25519_dalek_backend" or null)) && (!("serial" == target."curve25519_dalek_backend" or null)) && ("x86_64" == target."arch" or null));
+          }
+          {
+            name = "digest";
+            packageId = "digest";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "fiat-crypto";
+            packageId = "fiat-crypto";
+            usesDefaultFeatures = false;
+            target = { target, features }: ("fiat" == target."curve25519_dalek_backend" or null);
+          }
+          {
+            name = "subtle";
+            packageId = "subtle";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "zeroize";
+            packageId = "zeroize";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "platforms";
+            packageId = "platforms";
+          }
+          {
+            name = "rustc_version";
+            packageId = "rustc_version";
+          }
+        ];
+        features = {
+          "alloc" = [ "zeroize?/alloc" ];
+          "default" = [ "alloc" "precomputed-tables" "zeroize" ];
+          "digest" = [ "dep:digest" ];
+          "ff" = [ "dep:ff" ];
+          "group" = [ "dep:group" "rand_core" ];
+          "group-bits" = [ "group" "ff/bits" ];
+          "rand_core" = [ "dep:rand_core" ];
+          "serde" = [ "dep:serde" ];
+          "zeroize" = [ "dep:zeroize" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "digest" "precomputed-tables" "zeroize" ];
+      };
+      "curve25519-dalek-derive" = rec {
+        crateName = "curve25519-dalek-derive";
+        version = "0.1.1";
+        edition = "2021";
+        sha256 = "1cry71xxrr0mcy5my3fb502cwfxy6822k4pm19cwrilrg7hq4s7l";
+        procMacro = true;
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn 2.0.39";
+            features = [ "full" ];
+          }
+        ];
+
+      };
+      "data-encoding" = rec {
+        crateName = "data-encoding";
+        version = "2.4.0";
+        edition = "2018";
+        sha256 = "023k3dk8422jgbj7k72g63x51h1mhv91dhw1j4h205vzh6fnrrn2";
+        authors = [
+          "Julien Cretin <git@ia0.eu>"
+        ];
+        features = {
+          "default" = [ "std" ];
+          "std" = [ "alloc" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "std" ];
+      };
+      "der" = rec {
+        crateName = "der";
+        version = "0.7.8";
+        edition = "2021";
+        sha256 = "070bwiyr80800h31c5zd96ckkgagfjgnrrdmz3dzg2lccsd3dypz";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        dependencies = [
+          {
+            name = "const-oid";
+            packageId = "const-oid";
+            optional = true;
+          }
+          {
+            name = "zeroize";
+            packageId = "zeroize";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "alloc" = [ "zeroize?/alloc" ];
+          "arbitrary" = [ "dep:arbitrary" "const-oid?/arbitrary" "std" ];
+          "bytes" = [ "dep:bytes" "alloc" ];
+          "derive" = [ "dep:der_derive" ];
+          "flagset" = [ "dep:flagset" ];
+          "oid" = [ "dep:const-oid" ];
+          "pem" = [ "dep:pem-rfc7468" "alloc" "zeroize" ];
+          "std" = [ "alloc" ];
+          "time" = [ "dep:time" ];
+          "zeroize" = [ "dep:zeroize" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "oid" "std" "zeroize" ];
+      };
+      "digest" = rec {
+        crateName = "digest";
+        version = "0.10.7";
+        edition = "2018";
+        sha256 = "14p2n6ih29x81akj097lvz7wi9b6b9hvls0lwrv7b6xwyy0s5ncy";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        dependencies = [
+          {
+            name = "block-buffer";
+            packageId = "block-buffer";
+            optional = true;
+          }
+          {
+            name = "crypto-common";
+            packageId = "crypto-common";
+          }
+        ];
+        features = {
+          "blobby" = [ "dep:blobby" ];
+          "block-buffer" = [ "dep:block-buffer" ];
+          "const-oid" = [ "dep:const-oid" ];
+          "core-api" = [ "block-buffer" ];
+          "default" = [ "core-api" ];
+          "dev" = [ "blobby" ];
+          "mac" = [ "subtle" ];
+          "oid" = [ "const-oid" ];
+          "rand_core" = [ "crypto-common/rand_core" ];
+          "std" = [ "alloc" "crypto-common/std" ];
+          "subtle" = [ "dep:subtle" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "block-buffer" "core-api" "default" "std" ];
+      };
+      "dyn-clone" = rec {
+        crateName = "dyn-clone";
+        version = "1.0.16";
+        edition = "2018";
+        sha256 = "0pa9kas6a241pbx0q82ipwi4f7m7wwyzkkc725caky24gl4j4nsl";
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+
+      };
+      "ed25519" = rec {
+        crateName = "ed25519";
+        version = "2.2.3";
+        edition = "2021";
+        sha256 = "0lydzdf26zbn82g7xfczcac9d7mzm3qgx934ijjrd5hjpjx32m8i";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        dependencies = [
+          {
+            name = "pkcs8";
+            packageId = "pkcs8";
+            optional = true;
+          }
+          {
+            name = "signature";
+            packageId = "signature";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "alloc" = [ "pkcs8?/alloc" ];
+          "default" = [ "std" ];
+          "pem" = [ "alloc" "pkcs8/pem" ];
+          "pkcs8" = [ "dep:pkcs8" ];
+          "serde" = [ "dep:serde" ];
+          "serde_bytes" = [ "serde" "dep:serde_bytes" ];
+          "std" = [ "pkcs8?/std" "signature/std" ];
+          "zeroize" = [ "dep:zeroize" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "std" ];
+      };
+      "ed25519-dalek" = rec {
+        crateName = "ed25519-dalek";
+        version = "2.1.0";
+        edition = "2021";
+        sha256 = "1h13qm789m9gdjl6jazss80hqi8ll37m0afwcnw23zcbqjp8wqhz";
+        authors = [
+          "isis lovecruft <isis@patternsinthevoid.net>"
+          "Tony Arcieri <bascule@gmail.com>"
+          "Michael Rosenberg <michael@mrosenberg.pub>"
+        ];
+        dependencies = [
+          {
+            name = "curve25519-dalek";
+            packageId = "curve25519-dalek";
+            usesDefaultFeatures = false;
+            features = [ "digest" ];
+          }
+          {
+            name = "ed25519";
+            packageId = "ed25519";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "sha2";
+            packageId = "sha2";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "subtle";
+            packageId = "subtle";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "zeroize";
+            packageId = "zeroize";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+        ];
+        devDependencies = [
+          {
+            name = "curve25519-dalek";
+            packageId = "curve25519-dalek";
+            usesDefaultFeatures = false;
+            features = [ "digest" "rand_core" ];
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+            features = [ "derive" ];
+          }
+        ];
+        features = {
+          "alloc" = [ "curve25519-dalek/alloc" "ed25519/alloc" "serde?/alloc" "zeroize/alloc" ];
+          "asm" = [ "sha2/asm" ];
+          "batch" = [ "alloc" "merlin" "rand_core" ];
+          "default" = [ "fast" "std" "zeroize" ];
+          "digest" = [ "signature/digest" ];
+          "fast" = [ "curve25519-dalek/precomputed-tables" ];
+          "legacy_compatibility" = [ "curve25519-dalek/legacy_compatibility" ];
+          "merlin" = [ "dep:merlin" ];
+          "pem" = [ "alloc" "ed25519/pem" "pkcs8" ];
+          "pkcs8" = [ "ed25519/pkcs8" ];
+          "rand_core" = [ "dep:rand_core" ];
+          "serde" = [ "dep:serde" "ed25519/serde" ];
+          "signature" = [ "dep:signature" ];
+          "std" = [ "alloc" "ed25519/std" "serde?/std" "sha2/std" ];
+          "zeroize" = [ "dep:zeroize" "curve25519-dalek/zeroize" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "fast" "std" "zeroize" ];
+      };
+      "either" = rec {
+        crateName = "either";
+        version = "1.9.0";
+        edition = "2018";
+        sha256 = "01qy3anr7jal5lpc20791vxrw0nl6vksb5j7x56q2fycgcyy8sm2";
+        authors = [
+          "bluss"
+        ];
+        features = {
+          "default" = [ "use_std" ];
+          "serde" = [ "dep:serde" ];
+        };
+        resolvedDefaultFeatures = [ "default" "use_std" ];
+      };
+      "enum_dispatch" = rec {
+        crateName = "enum_dispatch";
+        version = "0.3.12";
+        edition = "2018";
+        sha256 = "03l998igqfzkykmj8i5qlbwhv2id9jn98fkkl82lv3dvg0q32cwg";
+        procMacro = true;
+        authors = [
+          "Anton Lazarev <https://antonok.com>"
+        ];
+        dependencies = [
+          {
+            name = "once_cell";
+            packageId = "once_cell";
+          }
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn 2.0.39";
+            features = [ "full" ];
+          }
+        ];
+
+      };
+      "equivalent" = rec {
+        crateName = "equivalent";
+        version = "1.0.1";
+        edition = "2015";
+        sha256 = "1malmx5f4lkfvqasz319lq6gb3ddg19yzf9s8cykfsgzdmyq0hsl";
+
+      };
+      "errno" = rec {
+        crateName = "errno";
+        version = "0.3.6";
+        edition = "2018";
+        sha256 = "0vp3dwidinw62hgx8ai5si3zldcwnq9x5cf6ra0iypsssq7fw63w";
+        authors = [
+          "Chris Wong <lambda.fairy@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+            usesDefaultFeatures = false;
+            target = { target, features }: ("hermit" == target."os" or null);
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            usesDefaultFeatures = false;
+            target = { target, features }: ("wasi" == target."os" or null);
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            usesDefaultFeatures = false;
+            target = { target, features }: (target."unix" or false);
+          }
+          {
+            name = "windows-sys";
+            packageId = "windows-sys";
+            target = { target, features }: (target."windows" or false);
+            features = [ "Win32_Foundation" "Win32_System_Diagnostics_Debug" ];
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "std" = [ "libc/std" ];
+        };
+        resolvedDefaultFeatures = [ "std" ];
+      };
+      "ethnum" = rec {
+        crateName = "ethnum";
+        version = "1.5.0";
+        edition = "2021";
+        sha256 = "0b68ngvisb0d40vc6h30zlhghbb3mc8wlxjbf8gnmavk1dca435r";
+        authors = [
+          "Nicholas Rodrigues Lordello <nlordell@gmail.com>"
+        ];
+        features = {
+          "ethnum-intrinsics" = [ "dep:ethnum-intrinsics" ];
+          "llvm-intrinsics" = [ "ethnum-intrinsics" ];
+          "serde" = [ "dep:serde" ];
+        };
+      };
+      "fallible-streaming-iterator" = rec {
+        crateName = "fallible-streaming-iterator";
+        version = "0.1.9";
+        edition = "2015";
+        sha256 = "0nj6j26p71bjy8h42x6jahx1hn0ng6mc2miwpgwnp8vnwqf4jq3k";
+        authors = [
+          "Steven Fackler <sfackler@gmail.com>"
+        ];
+        features = { };
+      };
+      "fast-float" = rec {
+        crateName = "fast-float";
+        version = "0.2.0";
+        edition = "2018";
+        sha256 = "0g7kfll3xyh99kc7r352lhljnwvgayxxa6saifb6725inikmyxlm";
+        authors = [
+          "Ivan Smirnov <i.s.smirnov@gmail.com>"
+        ];
+        features = {
+          "default" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "fastrand" = rec {
+        crateName = "fastrand";
+        version = "2.0.1";
+        edition = "2018";
+        sha256 = "19flpv5zbzpf0rk4x77z4zf25in0brg8l7m304d3yrf47qvwxjr5";
+        authors = [
+          "Stjepan Glavina <stjepang@gmail.com>"
+        ];
+        features = {
+          "default" = [ "std" ];
+          "getrandom" = [ "dep:getrandom" ];
+          "js" = [ "std" "getrandom" ];
+          "std" = [ "alloc" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "std" ];
+      };
+      "fiat-crypto" = rec {
+        crateName = "fiat-crypto";
+        version = "0.2.5";
+        edition = "2018";
+        sha256 = "1dxn0g50pv0ppal779vi7k40fr55pbhkyv4in7i13pgl4sn3wmr7";
+        authors = [
+          "Fiat Crypto library authors <jgross@mit.edu>"
+        ];
+        features = {
+          "default" = [ "std" ];
+        };
+      };
+      "flate2" = rec {
+        crateName = "flate2";
+        version = "1.0.28";
+        edition = "2018";
+        sha256 = "03llhsh4gqdirnfxxb9g2w9n0721dyn4yjir3pz7z4vjaxb3yc26";
+        authors = [
+          "Alex Crichton <alex@alexcrichton.com>"
+          "Josh Triplett <josh@joshtriplett.org>"
+        ];
+        dependencies = [
+          {
+            name = "crc32fast";
+            packageId = "crc32fast";
+          }
+          {
+            name = "miniz_oxide";
+            packageId = "miniz_oxide";
+            optional = true;
+            usesDefaultFeatures = false;
+            features = [ "with-alloc" ];
+          }
+          {
+            name = "miniz_oxide";
+            packageId = "miniz_oxide";
+            usesDefaultFeatures = false;
+            target = { target, features }: (("wasm32" == target."arch" or null) && (!("emscripten" == target."os" or null)));
+            features = [ "with-alloc" ];
+          }
+        ];
+        features = {
+          "any_zlib" = [ "any_impl" ];
+          "cloudflare-zlib-sys" = [ "dep:cloudflare-zlib-sys" ];
+          "cloudflare_zlib" = [ "any_zlib" "cloudflare-zlib-sys" ];
+          "default" = [ "rust_backend" ];
+          "libz-ng-sys" = [ "dep:libz-ng-sys" ];
+          "libz-sys" = [ "dep:libz-sys" ];
+          "miniz-sys" = [ "rust_backend" ];
+          "miniz_oxide" = [ "dep:miniz_oxide" ];
+          "rust_backend" = [ "miniz_oxide" "any_impl" ];
+          "zlib" = [ "any_zlib" "libz-sys" ];
+          "zlib-default" = [ "any_zlib" "libz-sys/default" ];
+          "zlib-ng" = [ "any_zlib" "libz-ng-sys" ];
+          "zlib-ng-compat" = [ "zlib" "libz-sys/zlib-ng" ];
+        };
+        resolvedDefaultFeatures = [ "any_impl" "miniz_oxide" "rust_backend" ];
+      };
+      "foreign_vec" = rec {
+        crateName = "foreign_vec";
+        version = "0.1.0";
+        edition = "2021";
+        sha256 = "0wv6p8yfahcqbdg2wg7wxgj4dm32g2b6spa5sg5sxg34v35ha6zf";
+        authors = [
+          "Jorge C. Leitao <jorgecarleitao@gmail.com>"
+        ];
+
+      };
+      "futures" = rec {
+        crateName = "futures";
+        version = "0.3.29";
+        edition = "2018";
+        sha256 = "0dak2ilpcmyjrb1j54fzy9hlw6vd10vqljq9gd59pbrq9dqr00ns";
+        dependencies = [
+          {
+            name = "futures-channel";
+            packageId = "futures-channel";
+            usesDefaultFeatures = false;
+            features = [ "sink" ];
+          }
+          {
+            name = "futures-core";
+            packageId = "futures-core";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-executor";
+            packageId = "futures-executor";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-io";
+            packageId = "futures-io";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-sink";
+            packageId = "futures-sink";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-task";
+            packageId = "futures-task";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-util";
+            packageId = "futures-util";
+            usesDefaultFeatures = false;
+            features = [ "sink" ];
+          }
+        ];
+        features = {
+          "alloc" = [ "futures-core/alloc" "futures-task/alloc" "futures-sink/alloc" "futures-channel/alloc" "futures-util/alloc" ];
+          "async-await" = [ "futures-util/async-await" "futures-util/async-await-macro" ];
+          "bilock" = [ "futures-util/bilock" ];
+          "compat" = [ "std" "futures-util/compat" ];
+          "default" = [ "std" "async-await" "executor" ];
+          "executor" = [ "std" "futures-executor/std" ];
+          "futures-executor" = [ "dep:futures-executor" ];
+          "io-compat" = [ "compat" "futures-util/io-compat" ];
+          "std" = [ "alloc" "futures-core/std" "futures-task/std" "futures-io/std" "futures-sink/std" "futures-util/std" "futures-util/io" "futures-util/channel" ];
+          "thread-pool" = [ "executor" "futures-executor/thread-pool" ];
+          "unstable" = [ "futures-core/unstable" "futures-task/unstable" "futures-channel/unstable" "futures-io/unstable" "futures-util/unstable" ];
+          "write-all-vectored" = [ "futures-util/write-all-vectored" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "async-await" "default" "executor" "futures-executor" "std" ];
+      };
+      "futures-channel" = rec {
+        crateName = "futures-channel";
+        version = "0.3.29";
+        edition = "2018";
+        sha256 = "1jxsifvrbqzdadk0svbax71cba5d3qg3wgjq8i160mxmd1kdckgz";
+        dependencies = [
+          {
+            name = "futures-core";
+            packageId = "futures-core";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-sink";
+            packageId = "futures-sink";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "alloc" = [ "futures-core/alloc" ];
+          "default" = [ "std" ];
+          "futures-sink" = [ "dep:futures-sink" ];
+          "sink" = [ "futures-sink" ];
+          "std" = [ "alloc" "futures-core/std" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "futures-sink" "sink" "std" ];
+      };
+      "futures-core" = rec {
+        crateName = "futures-core";
+        version = "0.3.29";
+        edition = "2018";
+        sha256 = "1308bpj0g36nhx2y6bl4mm6f1gnh9xyvvw2q2wpdgnb6dv3247gb";
+        features = {
+          "default" = [ "std" ];
+          "portable-atomic" = [ "dep:portable-atomic" ];
+          "std" = [ "alloc" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "std" ];
+      };
+      "futures-executor" = rec {
+        crateName = "futures-executor";
+        version = "0.3.29";
+        edition = "2018";
+        sha256 = "1g4pjni0sw28djx6mlcfz584abm2lpifz86cmng0kkxh7mlvhkqg";
+        dependencies = [
+          {
+            name = "futures-core";
+            packageId = "futures-core";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-task";
+            packageId = "futures-task";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-util";
+            packageId = "futures-util";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "num_cpus" = [ "dep:num_cpus" ];
+          "std" = [ "futures-core/std" "futures-task/std" "futures-util/std" ];
+          "thread-pool" = [ "std" "num_cpus" ];
+        };
+        resolvedDefaultFeatures = [ "std" ];
+      };
+      "futures-io" = rec {
+        crateName = "futures-io";
+        version = "0.3.29";
+        edition = "2018";
+        sha256 = "1ajsljgny3zfxwahba9byjzclrgvm1ypakca8z854k2w7cb4mwwb";
+        features = {
+          "default" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "std" ];
+      };
+      "futures-macro" = rec {
+        crateName = "futures-macro";
+        version = "0.3.29";
+        edition = "2018";
+        sha256 = "1nwd18i8kvpkdfwm045hddjli0n96zi7pn6f99zi9c74j7ym7cak";
+        procMacro = true;
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn 2.0.39";
+            features = [ "full" ];
+          }
+        ];
+
+      };
+      "futures-sink" = rec {
+        crateName = "futures-sink";
+        version = "0.3.29";
+        edition = "2018";
+        sha256 = "05q8jykqddxzp8nwf00wjk5m5mqi546d7i8hsxma7hiqxrw36vg3";
+        features = {
+          "default" = [ "std" ];
+          "std" = [ "alloc" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "std" ];
+      };
+      "futures-task" = rec {
+        crateName = "futures-task";
+        version = "0.3.29";
+        edition = "2018";
+        sha256 = "1qmsss8rb5ppql4qvd4r70h9gpfcpd0bg2b3qilxrnhdkc397lgg";
+        features = {
+          "default" = [ "std" ];
+          "std" = [ "alloc" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "std" ];
+      };
+      "futures-util" = rec {
+        crateName = "futures-util";
+        version = "0.3.29";
+        edition = "2018";
+        sha256 = "0141rkqh0psj4h8x8lgsl1p29dhqr7z2wcixkcbs60z74kb2d5d1";
+        dependencies = [
+          {
+            name = "futures-channel";
+            packageId = "futures-channel";
+            optional = true;
+            usesDefaultFeatures = false;
+            features = [ "std" ];
+          }
+          {
+            name = "futures-core";
+            packageId = "futures-core";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-io";
+            packageId = "futures-io";
+            optional = true;
+            usesDefaultFeatures = false;
+            features = [ "std" ];
+          }
+          {
+            name = "futures-macro";
+            packageId = "futures-macro";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-sink";
+            packageId = "futures-sink";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-task";
+            packageId = "futures-task";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "memchr";
+            packageId = "memchr";
+            optional = true;
+          }
+          {
+            name = "pin-project-lite";
+            packageId = "pin-project-lite";
+          }
+          {
+            name = "pin-utils";
+            packageId = "pin-utils";
+          }
+          {
+            name = "slab";
+            packageId = "slab";
+            optional = true;
+          }
+        ];
+        features = {
+          "alloc" = [ "futures-core/alloc" "futures-task/alloc" ];
+          "async-await-macro" = [ "async-await" "futures-macro" ];
+          "channel" = [ "std" "futures-channel" ];
+          "compat" = [ "std" "futures_01" ];
+          "default" = [ "std" "async-await" "async-await-macro" ];
+          "futures-channel" = [ "dep:futures-channel" ];
+          "futures-io" = [ "dep:futures-io" ];
+          "futures-macro" = [ "dep:futures-macro" ];
+          "futures-sink" = [ "dep:futures-sink" ];
+          "futures_01" = [ "dep:futures_01" ];
+          "io" = [ "std" "futures-io" "memchr" ];
+          "io-compat" = [ "io" "compat" "tokio-io" ];
+          "memchr" = [ "dep:memchr" ];
+          "portable-atomic" = [ "futures-core/portable-atomic" ];
+          "sink" = [ "futures-sink" ];
+          "slab" = [ "dep:slab" ];
+          "std" = [ "alloc" "futures-core/std" "futures-task/std" "slab" ];
+          "tokio-io" = [ "dep:tokio-io" ];
+          "unstable" = [ "futures-core/unstable" "futures-task/unstable" ];
+          "write-all-vectored" = [ "io" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "async-await" "async-await-macro" "channel" "futures-channel" "futures-io" "futures-macro" "futures-sink" "io" "memchr" "sink" "slab" "std" ];
+      };
+      "generic-array" = rec {
+        crateName = "generic-array";
+        version = "0.14.7";
+        edition = "2015";
+        sha256 = "16lyyrzrljfq424c3n8kfwkqihlimmsg5nhshbbp48np3yjrqr45";
+        libName = "generic_array";
+        authors = [
+          "Bartłomiej Kamiński <fizyk20@gmail.com>"
+          "Aaron Trent <novacrazy@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "typenum";
+            packageId = "typenum";
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "version_check";
+            packageId = "version_check";
+          }
+        ];
+        features = {
+          "serde" = [ "dep:serde" ];
+          "zeroize" = [ "dep:zeroize" ];
+        };
+        resolvedDefaultFeatures = [ "more_lengths" ];
+      };
+      "getrandom" = rec {
+        crateName = "getrandom";
+        version = "0.2.11";
+        edition = "2018";
+        sha256 = "03q7120cc2kn7ry013i67zmjl2g9q73h1ks5z08hq5v9syz0d47y";
+        authors = [
+          "The Rand Project Developers"
+        ];
+        dependencies = [
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "js-sys";
+            packageId = "js-sys";
+            optional = true;
+            target = { target, features }: ((("wasm32" == target."arch" or null) || ("wasm64" == target."arch" or null)) && ("unknown" == target."os" or null));
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            usesDefaultFeatures = false;
+            target = { target, features }: (target."unix" or false);
+          }
+          {
+            name = "wasi";
+            packageId = "wasi";
+            usesDefaultFeatures = false;
+            target = { target, features }: ("wasi" == target."os" or null);
+          }
+          {
+            name = "wasm-bindgen";
+            packageId = "wasm-bindgen";
+            optional = true;
+            usesDefaultFeatures = false;
+            target = { target, features }: ((("wasm32" == target."arch" or null) || ("wasm64" == target."arch" or null)) && ("unknown" == target."os" or null));
+          }
+        ];
+        features = {
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "core" = [ "dep:core" ];
+          "js" = [ "wasm-bindgen" "js-sys" ];
+          "js-sys" = [ "dep:js-sys" ];
+          "rustc-dep-of-std" = [ "compiler_builtins" "core" "libc/rustc-dep-of-std" "wasi/rustc-dep-of-std" ];
+          "wasm-bindgen" = [ "dep:wasm-bindgen" ];
+        };
+        resolvedDefaultFeatures = [ "js" "js-sys" "std" "wasm-bindgen" ];
+      };
+      "gimli" = rec {
+        crateName = "gimli";
+        version = "0.28.0";
+        edition = "2018";
+        sha256 = "1h7hcl3chfvd2gfrrxjymnwj7anqxjslvz20kcargkvsya2dgf3g";
+        features = {
+          "default" = [ "read-all" "write" ];
+          "endian-reader" = [ "read" "dep:stable_deref_trait" ];
+          "fallible-iterator" = [ "dep:fallible-iterator" ];
+          "read" = [ "read-core" ];
+          "read-all" = [ "read" "std" "fallible-iterator" "endian-reader" ];
+          "rustc-dep-of-std" = [ "dep:core" "dep:alloc" "dep:compiler_builtins" ];
+          "std" = [ "fallible-iterator?/std" "stable_deref_trait?/std" ];
+          "write" = [ "dep:indexmap" ];
+        };
+        resolvedDefaultFeatures = [ "read" "read-core" ];
+      };
+      "glob" = rec {
+        crateName = "glob";
+        version = "0.3.1";
+        edition = "2015";
+        sha256 = "16zca52nglanv23q5qrwd5jinw3d3as5ylya6y1pbx47vkxvrynj";
+        authors = [
+          "The Rust Project Developers"
+        ];
+
+      };
+      "hashbrown" = rec {
+        crateName = "hashbrown";
+        version = "0.14.2";
+        edition = "2021";
+        sha256 = "0mj1x1d16acxf4zg7wr7q2x8pgzfi1bzpifygcsxmg4d2n972gpr";
+        authors = [
+          "Amanieu d'Antras <amanieu@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "ahash";
+            packageId = "ahash";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "allocator-api2";
+            packageId = "allocator-api2";
+            optional = true;
+            usesDefaultFeatures = false;
+            features = [ "alloc" ];
+          }
+          {
+            name = "rayon";
+            packageId = "rayon";
+            optional = true;
+          }
+        ];
+        devDependencies = [
+          {
+            name = "rayon";
+            packageId = "rayon";
+          }
+        ];
+        features = {
+          "ahash" = [ "dep:ahash" ];
+          "alloc" = [ "dep:alloc" ];
+          "allocator-api2" = [ "dep:allocator-api2" ];
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "core" = [ "dep:core" ];
+          "default" = [ "ahash" "inline-more" "allocator-api2" ];
+          "equivalent" = [ "dep:equivalent" ];
+          "nightly" = [ "allocator-api2?/nightly" "bumpalo/allocator_api" ];
+          "rayon" = [ "dep:rayon" ];
+          "rkyv" = [ "dep:rkyv" ];
+          "rustc-dep-of-std" = [ "nightly" "core" "compiler_builtins" "alloc" "rustc-internal-api" ];
+          "serde" = [ "dep:serde" ];
+        };
+        resolvedDefaultFeatures = [ "ahash" "allocator-api2" "default" "inline-more" "raw" "rayon" ];
+      };
+      "heck" = rec {
+        crateName = "heck";
+        version = "0.4.1";
+        edition = "2018";
+        sha256 = "1a7mqsnycv5z4z5vnv1k34548jzmc0ajic7c1j8jsaspnhw5ql4m";
+        authors = [
+          "Without Boats <woboats@gmail.com>"
+        ];
+        features = {
+          "unicode" = [ "unicode-segmentation" ];
+          "unicode-segmentation" = [ "dep:unicode-segmentation" ];
+        };
+        resolvedDefaultFeatures = [ "default" ];
+      };
+      "hermit-abi" = rec {
+        crateName = "hermit-abi";
+        version = "0.3.3";
+        edition = "2021";
+        sha256 = "1dyc8qsjh876n74a3rcz8h43s27nj1sypdhsn2ms61bd3b47wzyp";
+        authors = [
+          "Stefan Lankes"
+        ];
+        features = {
+          "alloc" = [ "dep:alloc" ];
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "core" = [ "dep:core" ];
+          "rustc-dep-of-std" = [ "core" "alloc" "compiler_builtins/rustc-dep-of-std" ];
+        };
+        resolvedDefaultFeatures = [ "default" ];
+      };
+      "home" = rec {
+        crateName = "home";
+        version = "0.5.5";
+        edition = "2018";
+        sha256 = "1nqx1krijvpd03d96avsdyknd12h8hs3xhxwgqghf8v9xxzc4i2l";
+        authors = [
+          "Brian Anderson <andersrb@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "windows-sys";
+            packageId = "windows-sys";
+            target = { target, features }: (target."windows" or false);
+            features = [ "Win32_Foundation" "Win32_UI_Shell" ];
+          }
+        ];
+
+      };
+      "iana-time-zone" = rec {
+        crateName = "iana-time-zone";
+        version = "0.1.58";
+        edition = "2018";
+        sha256 = "081vcr8z8ddhl5r1ywif6grnswk01b2ac4nks2bhn8zzdimvh9l3";
+        authors = [
+          "Andrew Straw <strawman@astraw.com>"
+          "René Kijewski <rene.kijewski@fu-berlin.de>"
+          "Ryan Lopopolo <rjl@hyperbo.la>"
+        ];
+        dependencies = [
+          {
+            name = "android_system_properties";
+            packageId = "android_system_properties";
+            target = { target, features }: ("android" == target."os" or null);
+          }
+          {
+            name = "core-foundation-sys";
+            packageId = "core-foundation-sys";
+            target = { target, features }: (("macos" == target."os" or null) || ("ios" == target."os" or null));
+          }
+          {
+            name = "iana-time-zone-haiku";
+            packageId = "iana-time-zone-haiku";
+            target = { target, features }: ("haiku" == target."os" or null);
+          }
+          {
+            name = "js-sys";
+            packageId = "js-sys";
+            target = { target, features }: ("wasm32" == target."arch" or null);
+          }
+          {
+            name = "wasm-bindgen";
+            packageId = "wasm-bindgen";
+            target = { target, features }: ("wasm32" == target."arch" or null);
+          }
+          {
+            name = "windows-core";
+            packageId = "windows-core 0.51.1";
+            target = { target, features }: ("windows" == target."os" or null);
+          }
+        ];
+        features = { };
+        resolvedDefaultFeatures = [ "fallback" ];
+      };
+      "iana-time-zone-haiku" = rec {
+        crateName = "iana-time-zone-haiku";
+        version = "0.1.2";
+        edition = "2018";
+        sha256 = "17r6jmj31chn7xs9698r122mapq85mfnv98bb4pg6spm0si2f67k";
+        authors = [
+          "René Kijewski <crates.io@k6i.de>"
+        ];
+        buildDependencies = [
+          {
+            name = "cc";
+            packageId = "cc";
+          }
+        ];
+
+      };
+      "indexmap" = rec {
+        crateName = "indexmap";
+        version = "2.1.0";
+        edition = "2021";
+        sha256 = "07rxrqmryr1xfnmhrjlz8ic6jw28v6h5cig3ws2c9d0wifhy2c6m";
+        dependencies = [
+          {
+            name = "equivalent";
+            packageId = "equivalent";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "hashbrown";
+            packageId = "hashbrown";
+            usesDefaultFeatures = false;
+            features = [ "raw" ];
+          }
+        ];
+        features = {
+          "arbitrary" = [ "dep:arbitrary" ];
+          "default" = [ "std" ];
+          "quickcheck" = [ "dep:quickcheck" ];
+          "rayon" = [ "dep:rayon" ];
+          "rustc-rayon" = [ "dep:rustc-rayon" ];
+          "serde" = [ "dep:serde" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "itoa" = rec {
+        crateName = "itoa";
+        version = "1.0.9";
+        edition = "2018";
+        sha256 = "0f6cpb4yqzhkrhhg6kqsw3wnmmhdnnffi6r2xzy248gzi2v0l5dg";
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        features = {
+          "no-panic" = [ "dep:no-panic" ];
+        };
+      };
+      "jemalloc-sys" = rec {
+        crateName = "jemalloc-sys";
+        version = "0.5.4+5.3.0-patched";
+        edition = "2018";
+        links = "jemalloc";
+        sha256 = "1wpbpwhfs6wd484cdfpl0zdf441ann9wj0fypy67i8ffw531jv5c";
+        authors = [
+          "Alex Crichton <alex@alexcrichton.com>"
+          "Gonzalo Brito Gadeschi <gonzalobg88@gmail.com>"
+          "The TiKV Project Developers"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+            usesDefaultFeatures = false;
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "cc";
+            packageId = "cc";
+          }
+        ];
+        features = {
+          "background_threads" = [ "background_threads_runtime_support" ];
+          "default" = [ "background_threads_runtime_support" ];
+        };
+        resolvedDefaultFeatures = [ "background_threads_runtime_support" ];
+      };
+      "jemallocator" = rec {
+        crateName = "jemallocator";
+        version = "0.5.4";
+        edition = "2018";
+        sha256 = "1g6k9ly6wxj53bp8lz9lg9nj4s662k6612jydw71aqwfkx53gpm0";
+        authors = [
+          "Alex Crichton <alex@alexcrichton.com>"
+          "Gonzalo Brito Gadeschi <gonzalobg88@gmail.com>"
+          "Simon Sapin <simon.sapin@exyr.org>"
+          "Steven Fackler <sfackler@gmail.com>"
+          "The TiKV Project Developers"
+        ];
+        dependencies = [
+          {
+            name = "jemalloc-sys";
+            packageId = "jemalloc-sys";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "background_threads" = [ "jemalloc-sys/background_threads" ];
+          "background_threads_runtime_support" = [ "jemalloc-sys/background_threads_runtime_support" ];
+          "debug" = [ "jemalloc-sys/debug" ];
+          "default" = [ "background_threads_runtime_support" ];
+          "disable_initial_exec_tls" = [ "jemalloc-sys/disable_initial_exec_tls" ];
+          "profiling" = [ "jemalloc-sys/profiling" ];
+          "stats" = [ "jemalloc-sys/stats" ];
+          "unprefixed_malloc_on_supported_platforms" = [ "jemalloc-sys/unprefixed_malloc_on_supported_platforms" ];
+        };
+        resolvedDefaultFeatures = [ "background_threads_runtime_support" "default" ];
+      };
+      "jobserver" = rec {
+        crateName = "jobserver";
+        version = "0.1.27";
+        edition = "2018";
+        sha256 = "0z9w6vfqwbr6hfk9yaw7kydlh6f7k39xdlszxlh39in4acwzcdwc";
+        authors = [
+          "Alex Crichton <alex@alexcrichton.com>"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (target."unix" or false);
+          }
+        ];
+
+      };
+      "js-sys" = rec {
+        crateName = "js-sys";
+        version = "0.3.65";
+        edition = "2018";
+        sha256 = "1s1gaxgzpqfyygc7f2pwp9y128rh5f8zvsc4nm5yazgna9cw7h2l";
+        authors = [
+          "The wasm-bindgen Developers"
+        ];
+        dependencies = [
+          {
+            name = "wasm-bindgen";
+            packageId = "wasm-bindgen";
+          }
+        ];
+
+      };
+      "libc" = rec {
+        crateName = "libc";
+        version = "0.2.150";
+        edition = "2015";
+        sha256 = "0g10n8c830alndgjb8xk1i9kz5z727np90z1z81119pr8d3jmnc9";
+        authors = [
+          "The Rust Project Developers"
+        ];
+        features = {
+          "default" = [ "std" ];
+          "rustc-dep-of-std" = [ "align" "rustc-std-workspace-core" ];
+          "rustc-std-workspace-core" = [ "dep:rustc-std-workspace-core" ];
+          "use_std" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "extra_traits" "std" ];
+      };
+      "libm" = rec {
+        crateName = "libm";
+        version = "0.2.8";
+        edition = "2018";
+        sha256 = "0n4hk1rs8pzw8hdfmwn96c4568s93kfxqgcqswr7sajd2diaihjf";
+        authors = [
+          "Jorge Aparicio <jorge@japaric.io>"
+        ];
+        features = {
+          "musl-reference-tests" = [ "rand" ];
+          "rand" = [ "dep:rand" ];
+        };
+        resolvedDefaultFeatures = [ "default" ];
+      };
+      "linux-raw-sys" = rec {
+        crateName = "linux-raw-sys";
+        version = "0.4.10";
+        edition = "2021";
+        sha256 = "0gz0671d4hgrdngrryaajxl962ny4g40pykg0vq0pr32q3l7j96s";
+        authors = [
+          "Dan Gohman <dev@sunfishcode.online>"
+        ];
+        features = {
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "core" = [ "dep:core" ];
+          "default" = [ "std" "general" "errno" ];
+          "rustc-dep-of-std" = [ "core" "compiler_builtins" "no_std" ];
+        };
+        resolvedDefaultFeatures = [ "elf" "errno" "general" "ioctl" "no_std" ];
+      };
+      "log" = rec {
+        crateName = "log";
+        version = "0.4.20";
+        edition = "2015";
+        sha256 = "13rf7wphnwd61vazpxr7fiycin6cb1g8fmvgqg18i464p0y1drmm";
+        authors = [
+          "The Rust Project Developers"
+        ];
+        features = {
+          "kv_unstable" = [ "value-bag" ];
+          "kv_unstable_serde" = [ "kv_unstable_std" "value-bag/serde" "serde" ];
+          "kv_unstable_std" = [ "std" "kv_unstable" "value-bag/error" ];
+          "kv_unstable_sval" = [ "kv_unstable" "value-bag/sval" "sval" "sval_ref" ];
+          "serde" = [ "dep:serde" ];
+          "sval" = [ "dep:sval" ];
+          "sval_ref" = [ "dep:sval_ref" ];
+          "value-bag" = [ "dep:value-bag" ];
+        };
+      };
+      "lz4" = rec {
+        crateName = "lz4";
+        version = "1.24.0";
+        edition = "2018";
+        crateBin = [ ];
+        sha256 = "1wad97k0asgvaj16ydd09gqs2yvgaanzcvqglrhffv7kdpc2v7ky";
+        authors = [
+          "Jens Heyens <jens.heyens@ewetel.net>"
+          "Artem V. Navrotskiy <bozaro@buzzsoft.ru>"
+          "Patrick Marks <pmarks@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+          }
+          {
+            name = "lz4-sys";
+            packageId = "lz4-sys";
+          }
+        ];
+
+      };
+      "lz4-sys" = rec {
+        crateName = "lz4-sys";
+        version = "1.9.4";
+        edition = "2015";
+        links = "lz4";
+        sha256 = "0059ik4xlvnss5qfh6l691psk4g3350ljxaykzv10yr0gqqppljp";
+        authors = [
+          "Jens Heyens <jens.heyens@ewetel.net>"
+          "Artem V. Navrotskiy <bozaro@buzzsoft.ru>"
+          "Patrick Marks <pmarks@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "cc";
+            packageId = "cc";
+          }
+        ];
+
+      };
+      "memchr" = rec {
+        crateName = "memchr";
+        version = "2.6.4";
+        edition = "2021";
+        sha256 = "0rq1ka8790ns41j147npvxcqcl2anxyngsdimy85ag2api0fwrgn";
+        authors = [
+          "Andrew Gallant <jamslam@gmail.com>"
+          "bluss"
+        ];
+        features = {
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "core" = [ "dep:core" ];
+          "default" = [ "std" ];
+          "logging" = [ "dep:log" ];
+          "rustc-dep-of-std" = [ "core" "compiler_builtins" ];
+          "std" = [ "alloc" ];
+          "use_std" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "std" ];
+      };
+      "memmap2" = rec {
+        crateName = "memmap2";
+        version = "0.7.1";
+        edition = "2018";
+        sha256 = "1il82b0mw304jlwvl0m89aa8bj5dgmm3vbb0jg8lqlrk0p98i4zl";
+        authors = [
+          "Dan Burkert <dan@danburkert.com>"
+          "Yevhenii Reizner <razrfalcon@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (target."unix" or false);
+          }
+        ];
+        features = {
+          "stable_deref_trait" = [ "dep:stable_deref_trait" ];
+        };
+      };
+      "memoffset" = rec {
+        crateName = "memoffset";
+        version = "0.9.0";
+        edition = "2015";
+        sha256 = "0v20ihhdzkfw1jx00a7zjpk2dcp5qjq6lz302nyqamd9c4f4nqss";
+        authors = [
+          "Gilad Naaman <gilad.naaman@gmail.com>"
+        ];
+        buildDependencies = [
+          {
+            name = "autocfg";
+            packageId = "autocfg";
+          }
+        ];
+        features = { };
+        resolvedDefaultFeatures = [ "default" ];
+      };
+      "minimal-lexical" = rec {
+        crateName = "minimal-lexical";
+        version = "0.2.1";
+        edition = "2018";
+        sha256 = "16ppc5g84aijpri4jzv14rvcnslvlpphbszc7zzp6vfkddf4qdb8";
+        authors = [
+          "Alex Huszagh <ahuszagh@gmail.com>"
+        ];
+        features = {
+          "default" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "std" ];
+      };
+      "miniz_oxide" = rec {
+        crateName = "miniz_oxide";
+        version = "0.7.1";
+        edition = "2018";
+        sha256 = "1ivl3rbbdm53bzscrd01g60l46lz5krl270487d8lhjvwl5hx0g7";
+        authors = [
+          "Frommi <daniil.liferenko@gmail.com>"
+          "oyvindln <oyvindln@users.noreply.github.com>"
+        ];
+        dependencies = [
+          {
+            name = "adler";
+            packageId = "adler";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "alloc" = [ "dep:alloc" ];
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "core" = [ "dep:core" ];
+          "default" = [ "with-alloc" ];
+          "rustc-dep-of-std" = [ "core" "alloc" "compiler_builtins" "adler/rustc-dep-of-std" ];
+          "simd" = [ "simd-adler32" ];
+          "simd-adler32" = [ "dep:simd-adler32" ];
+        };
+        resolvedDefaultFeatures = [ "with-alloc" ];
+      };
+      "mio" = rec {
+        crateName = "mio";
+        version = "0.8.9";
+        edition = "2018";
+        sha256 = "1l23hg513c23nhcdzvk25caaj28mic6qgqadbn8axgj6bqf2ikix";
+        authors = [
+          "Carl Lerche <me@carllerche.com>"
+          "Thomas de Zeeuw <thomasdezeeuw@gmail.com>"
+          "Tokio Contributors <team@tokio.rs>"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: ("wasi" == target."os" or null);
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (target."unix" or false);
+          }
+          {
+            name = "wasi";
+            packageId = "wasi";
+            target = { target, features }: ("wasi" == target."os" or null);
+          }
+          {
+            name = "windows-sys";
+            packageId = "windows-sys";
+            target = { target, features }: (target."windows" or false);
+            features = [ "Win32_Foundation" "Win32_Networking_WinSock" "Win32_Storage_FileSystem" "Win32_System_IO" "Win32_System_WindowsProgramming" ];
+          }
+        ];
+        features = {
+          "default" = [ "log" ];
+          "log" = [ "dep:log" ];
+          "os-ext" = [ "os-poll" "windows-sys/Win32_System_Pipes" "windows-sys/Win32_Security" ];
+        };
+        resolvedDefaultFeatures = [ "net" "os-ext" "os-poll" ];
+      };
+      "multiversion" = rec {
+        crateName = "multiversion";
+        version = "0.7.3";
+        edition = "2021";
+        sha256 = "0al7yrf489lqzxx291sx9566n7slk2njwlqrxbjhqxk1zvbvkixj";
+        authors = [
+          "Caleb Zulawski <caleb.zulawski@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "multiversion-macros";
+            packageId = "multiversion-macros";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "target-features";
+            packageId = "target-features";
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "std" = [ "multiversion-macros/std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "multiversion-macros" = rec {
+        crateName = "multiversion-macros";
+        version = "0.7.3";
+        edition = "2021";
+        sha256 = "1j1avbxw7jscyi7dmnywhlwbiny1fvg1vpp9fy4dc1pd022kva16";
+        procMacro = true;
+        authors = [
+          "Caleb Zulawski <caleb.zulawski@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn 1.0.109";
+            features = [ "full" "extra-traits" "visit-mut" ];
+          }
+          {
+            name = "target-features";
+            packageId = "target-features";
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "std" ];
+      };
+      "narinfo2parquet" = rec {
+        crateName = "narinfo2parquet";
+        version = "0.1.0";
+        edition = "2021";
+        crateBin = [
+          {
+            name = "narinfo2parquet";
+            path = "src/main.rs";
+            requiredFeatures = [ ];
+          }
+        ];
+        # We can't filter paths with references in Nix 2.4
+        # See https://github.com/NixOS/nix/issues/5410
+        src =
+          if ((lib.versionOlder builtins.nixVersion "2.4pre20211007") || (lib.versionOlder "2.5" builtins.nixVersion))
+          then lib.cleanSourceWith { filter = sourceFilter; src = ./.; }
+          else ./.;
+        dependencies = [
+          {
+            name = "anyhow";
+            packageId = "anyhow";
+            features = [ "backtrace" ];
+          }
+          {
+            name = "jemallocator";
+            packageId = "jemallocator";
+          }
+          {
+            name = "nix-compat";
+            packageId = "nix-compat";
+          }
+          {
+            name = "polars";
+            packageId = "polars";
+            usesDefaultFeatures = false;
+            features = [ "parquet" "polars-io" "dtype-categorical" ];
+          }
+          {
+            name = "tempfile-fast";
+            packageId = "tempfile-fast";
+          }
+          {
+            name = "zstd";
+            packageId = "zstd";
+          }
+        ];
+
+      };
+      "nix-compat" = rec {
+        crateName = "nix-compat";
+        version = "0.1.0";
+        edition = "2021";
+        crateBin = [ ];
+        # We can't filter paths with references in Nix 2.4
+        # See https://github.com/NixOS/nix/issues/5410
+        src =
+          if ((lib.versionOlder builtins.nixVersion "2.4pre20211007") || (lib.versionOlder "2.5" builtins.nixVersion))
+          then lib.cleanSourceWith { filter = sourceFilter; src = ../../nix-compat; }
+          else ../../nix-compat;
+        dependencies = [
+          {
+            name = "bitflags";
+            packageId = "bitflags 2.4.1";
+          }
+          {
+            name = "bstr";
+            packageId = "bstr";
+            features = [ "alloc" "unicode" "serde" ];
+          }
+          {
+            name = "data-encoding";
+            packageId = "data-encoding";
+          }
+          {
+            name = "ed25519";
+            packageId = "ed25519";
+          }
+          {
+            name = "ed25519-dalek";
+            packageId = "ed25519-dalek";
+          }
+          {
+            name = "glob";
+            packageId = "glob";
+          }
+          {
+            name = "nom";
+            packageId = "nom";
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+            features = [ "derive" ];
+          }
+          {
+            name = "serde_json";
+            packageId = "serde_json";
+          }
+          {
+            name = "sha2";
+            packageId = "sha2";
+          }
+          {
+            name = "thiserror";
+            packageId = "thiserror";
+          }
+        ];
+        devDependencies = [
+          {
+            name = "serde_json";
+            packageId = "serde_json";
+          }
+        ];
+        features = {
+          "async" = [ "futures-util" ];
+          "futures-util" = [ "dep:futures-util" ];
+        };
+      };
+      "nom" = rec {
+        crateName = "nom";
+        version = "7.1.3";
+        edition = "2018";
+        sha256 = "0jha9901wxam390jcf5pfa0qqfrgh8li787jx2ip0yk5b8y9hwyj";
+        authors = [
+          "contact@geoffroycouprie.com"
+        ];
+        dependencies = [
+          {
+            name = "memchr";
+            packageId = "memchr";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "minimal-lexical";
+            packageId = "minimal-lexical";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "std" = [ "alloc" "memchr/std" "minimal-lexical/std" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "std" ];
+      };
+      "now" = rec {
+        crateName = "now";
+        version = "0.1.3";
+        edition = "2018";
+        sha256 = "1l135786rb43rjfhwfdj7hi3b5zxxyl9gwf15yjz18cp8f3yk2bd";
+        authors = [
+          "Kilerd <blove694@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "chrono";
+            packageId = "chrono";
+            usesDefaultFeatures = false;
+            features = [ "clock" "std" ];
+          }
+        ];
+
+      };
+      "ntapi" = rec {
+        crateName = "ntapi";
+        version = "0.4.1";
+        edition = "2018";
+        sha256 = "1r38zhbwdvkis2mzs6671cm1p6djgsl49i7bwxzrvhwicdf8k8z8";
+        authors = [
+          "MSxDOS <melcodos@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "winapi";
+            packageId = "winapi";
+            features = [ "cfg" "evntrace" "in6addr" "inaddr" "minwinbase" "ntsecapi" "windef" "winioctl" ];
+          }
+        ];
+        features = {
+          "default" = [ "user" ];
+          "impl-default" = [ "winapi/impl-default" ];
+        };
+        resolvedDefaultFeatures = [ "default" "user" ];
+      };
+      "num-traits" = rec {
+        crateName = "num-traits";
+        version = "0.2.17";
+        edition = "2018";
+        sha256 = "0z16bi5zwgfysz6765v3rd6whfbjpihx3mhsn4dg8dzj2c221qrr";
+        authors = [
+          "The Rust Project Developers"
+        ];
+        dependencies = [
+          {
+            name = "libm";
+            packageId = "libm";
+            optional = true;
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "autocfg";
+            packageId = "autocfg";
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "libm" = [ "dep:libm" ];
+        };
+        resolvedDefaultFeatures = [ "default" "libm" "std" ];
+      };
+      "num_cpus" = rec {
+        crateName = "num_cpus";
+        version = "1.16.0";
+        edition = "2015";
+        sha256 = "0hra6ihpnh06dvfvz9ipscys0xfqa9ca9hzp384d5m02ssvgqqa1";
+        authors = [
+          "Sean McArthur <sean@seanmonstar.com>"
+        ];
+        dependencies = [
+          {
+            name = "hermit-abi";
+            packageId = "hermit-abi";
+            target = { target, features }: ("hermit" == target."os" or null);
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (!(target."windows" or false));
+          }
+        ];
+
+      };
+      "object" = rec {
+        crateName = "object";
+        version = "0.32.1";
+        edition = "2018";
+        sha256 = "1c02x4kvqpnl3wn7gz9idm4jrbirbycyqjgiw6lm1g9k77fzkxcw";
+        dependencies = [
+          {
+            name = "memchr";
+            packageId = "memchr";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "all" = [ "read" "write" "std" "compression" "wasm" ];
+          "alloc" = [ "dep:alloc" ];
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "compression" = [ "dep:flate2" "dep:ruzstd" "std" ];
+          "core" = [ "dep:core" ];
+          "default" = [ "read" "compression" ];
+          "doc" = [ "read_core" "write_std" "std" "compression" "archive" "coff" "elf" "macho" "pe" "wasm" "xcoff" ];
+          "pe" = [ "coff" ];
+          "read" = [ "read_core" "archive" "coff" "elf" "macho" "pe" "xcoff" "unaligned" ];
+          "rustc-dep-of-std" = [ "core" "compiler_builtins" "alloc" "memchr/rustc-dep-of-std" ];
+          "std" = [ "memchr/std" ];
+          "unstable-all" = [ "all" "unstable" ];
+          "wasm" = [ "dep:wasmparser" ];
+          "write" = [ "write_std" "coff" "elf" "macho" "pe" "xcoff" ];
+          "write_core" = [ "dep:crc32fast" "dep:indexmap" "dep:hashbrown" ];
+          "write_std" = [ "write_core" "std" "indexmap?/std" "crc32fast?/std" ];
+        };
+        resolvedDefaultFeatures = [ "archive" "coff" "elf" "macho" "pe" "read_core" "unaligned" ];
+      };
+      "once_cell" = rec {
+        crateName = "once_cell";
+        version = "1.18.0";
+        edition = "2021";
+        sha256 = "0vapcd5ambwck95wyz3ymlim35jirgnqn9a0qmi19msymv95v2yx";
+        authors = [
+          "Aleksey Kladov <aleksey.kladov@gmail.com>"
+        ];
+        features = {
+          "alloc" = [ "race" ];
+          "atomic-polyfill" = [ "critical-section" ];
+          "critical-section" = [ "dep:critical-section" "dep:atomic-polyfill" ];
+          "default" = [ "std" ];
+          "parking_lot" = [ "dep:parking_lot_core" ];
+          "std" = [ "alloc" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "race" "std" "unstable" ];
+      };
+      "parquet-format-safe" = rec {
+        crateName = "parquet-format-safe";
+        version = "0.2.4";
+        edition = "2021";
+        sha256 = "07wf6wf4jrxlq5p3xldxsnabp7jl06my2qp7kiwy9m3x2r5wac8i";
+        authors = [
+          "Apache Thrift contributors <dev@thrift.apache.org>"
+          "Jorge Leitao <jorgecarleitao@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "async-trait";
+            packageId = "async-trait";
+            optional = true;
+          }
+          {
+            name = "futures";
+            packageId = "futures";
+            optional = true;
+          }
+        ];
+        features = {
+          "async" = [ "futures" "async-trait" ];
+          "async-trait" = [ "dep:async-trait" ];
+          "full" = [ "async" ];
+          "futures" = [ "dep:futures" ];
+        };
+        resolvedDefaultFeatures = [ "async" "async-trait" "default" "futures" ];
+      };
+      "percent-encoding" = rec {
+        crateName = "percent-encoding";
+        version = "2.3.0";
+        edition = "2018";
+        sha256 = "152slflmparkh27hprw62sph8rv77wckzhwl2dhqk6bf563lfalv";
+        authors = [
+          "The rust-url developers"
+        ];
+        features = {
+          "default" = [ "std" ];
+          "std" = [ "alloc" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "std" ];
+      };
+      "pin-project-lite" = rec {
+        crateName = "pin-project-lite";
+        version = "0.2.13";
+        edition = "2018";
+        sha256 = "0n0bwr5qxlf0mhn2xkl36sy55118s9qmvx2yl5f3ixkb007lbywa";
+
+      };
+      "pin-utils" = rec {
+        crateName = "pin-utils";
+        version = "0.1.0";
+        edition = "2018";
+        sha256 = "117ir7vslsl2z1a7qzhws4pd01cg2d3338c47swjyvqv2n60v1wb";
+        authors = [
+          "Josef Brandl <mail@josefbrandl.de>"
+        ];
+
+      };
+      "pkcs8" = rec {
+        crateName = "pkcs8";
+        version = "0.10.2";
+        edition = "2021";
+        sha256 = "1dx7w21gvn07azszgqd3ryjhyphsrjrmq5mmz1fbxkj5g0vv4l7r";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        dependencies = [
+          {
+            name = "der";
+            packageId = "der";
+            features = [ "oid" ];
+          }
+          {
+            name = "spki";
+            packageId = "spki";
+          }
+        ];
+        features = {
+          "3des" = [ "encryption" "pkcs5/3des" ];
+          "alloc" = [ "der/alloc" "der/zeroize" "spki/alloc" ];
+          "des-insecure" = [ "encryption" "pkcs5/des-insecure" ];
+          "encryption" = [ "alloc" "pkcs5/alloc" "pkcs5/pbes2" "rand_core" ];
+          "getrandom" = [ "rand_core/getrandom" ];
+          "pem" = [ "alloc" "der/pem" "spki/pem" ];
+          "pkcs5" = [ "dep:pkcs5" ];
+          "rand_core" = [ "dep:rand_core" ];
+          "sha1-insecure" = [ "encryption" "pkcs5/sha1-insecure" ];
+          "std" = [ "alloc" "der/std" "spki/std" ];
+          "subtle" = [ "dep:subtle" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "std" ];
+      };
+      "pkg-config" = rec {
+        crateName = "pkg-config";
+        version = "0.3.27";
+        edition = "2015";
+        sha256 = "0r39ryh1magcq4cz5g9x88jllsnxnhcqr753islvyk4jp9h2h1r6";
+        authors = [
+          "Alex Crichton <alex@alexcrichton.com>"
+        ];
+
+      };
+      "planus" = rec {
+        crateName = "planus";
+        version = "0.3.1";
+        edition = "2021";
+        sha256 = "17x8mr175b9clg998xpi5z45f9fsspb0ncfnx2644bz817fr25pw";
+        dependencies = [
+          {
+            name = "array-init-cursor";
+            packageId = "array-init-cursor";
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "platforms" = rec {
+        crateName = "platforms";
+        version = "3.2.0";
+        edition = "2018";
+        sha256 = "1c6bzwn877aqdbbmyqsl753ycbciwvbdh4lpzijb8vrfb4zsprhl";
+        authors = [
+          "Tony Arcieri <bascule@gmail.com>"
+          "Sergey \"Shnatsel\" Davidoff <shnatsel@gmail.com>"
+        ];
+        features = {
+          "default" = [ "std" ];
+          "serde" = [ "dep:serde" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "polars" = rec {
+        crateName = "polars";
+        version = "0.36.2";
+        edition = "2021";
+        sha256 = "0swv6i0gq25zafw1ir2irqij9a8mnkhvws5idv72m3kavby4i04k";
+        authors = [
+          "Ritchie Vink <ritchie46@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "getrandom";
+            packageId = "getrandom";
+            target = { target, features }: (builtins.elem "wasm" target."family");
+            features = [ "js" ];
+          }
+          {
+            name = "polars-core";
+            packageId = "polars-core";
+            usesDefaultFeatures = false;
+            features = [ "algorithm_group_by" ];
+          }
+          {
+            name = "polars-io";
+            packageId = "polars-io";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-lazy";
+            packageId = "polars-lazy";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-ops";
+            packageId = "polars-ops";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-sql";
+            packageId = "polars-sql";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "version_check";
+            packageId = "version_check";
+          }
+        ];
+        features = {
+          "abs" = [ "polars-ops/abs" "polars-lazy?/abs" ];
+          "approx_unique" = [ "polars-lazy?/approx_unique" "polars-ops/approx_unique" ];
+          "arg_where" = [ "polars-lazy?/arg_where" ];
+          "array_any_all" = [ "polars-lazy?/array_any_all" "dtype-array" ];
+          "asof_join" = [ "polars-core/asof_join" "polars-lazy?/asof_join" "polars-ops/asof_join" ];
+          "async" = [ "polars-lazy?/async" ];
+          "avro" = [ "polars-io" "polars-io/avro" ];
+          "avx512" = [ "polars-core/avx512" ];
+          "aws" = [ "async" "cloud" "polars-io/aws" ];
+          "azure" = [ "async" "cloud" "polars-io/azure" ];
+          "bench" = [ "lazy" ];
+          "bigidx" = [ "polars-core/bigidx" "polars-lazy?/bigidx" "polars-ops/big_idx" ];
+          "binary_encoding" = [ "polars-ops/binary_encoding" "polars-lazy?/binary_encoding" ];
+          "checked_arithmetic" = [ "polars-core/checked_arithmetic" ];
+          "chunked_ids" = [ "polars-lazy?/chunked_ids" "polars-core/chunked_ids" "polars-ops/chunked_ids" ];
+          "cloud" = [ "polars-lazy?/cloud" "polars-io/cloud" ];
+          "cloud_write" = [ "cloud" "polars-lazy?/cloud_write" ];
+          "coalesce" = [ "polars-lazy?/coalesce" ];
+          "concat_str" = [ "polars-lazy?/concat_str" ];
+          "cov" = [ "polars-lazy/cov" ];
+          "cross_join" = [ "polars-lazy?/cross_join" "polars-ops/cross_join" ];
+          "cse" = [ "polars-lazy?/cse" ];
+          "csv" = [ "polars-io" "polars-io/csv" "polars-lazy?/csv" "polars-sql?/csv" ];
+          "cum_agg" = [ "polars-ops/cum_agg" "polars-lazy?/cum_agg" ];
+          "cumulative_eval" = [ "polars-lazy?/cumulative_eval" ];
+          "cutqcut" = [ "polars-lazy?/cutqcut" ];
+          "dataframe_arithmetic" = [ "polars-core/dataframe_arithmetic" ];
+          "date_offset" = [ "polars-lazy?/date_offset" ];
+          "decompress" = [ "polars-io/decompress" ];
+          "decompress-fast" = [ "polars-io/decompress-fast" ];
+          "default" = [ "docs" "zip_with" "csv" "temporal" "fmt" "dtype-slim" ];
+          "describe" = [ "polars-core/describe" ];
+          "diagonal_concat" = [ "polars-core/diagonal_concat" "polars-lazy?/diagonal_concat" "polars-sql?/diagonal_concat" ];
+          "diff" = [ "polars-ops/diff" "polars-lazy?/diff" ];
+          "docs" = [ "polars-core/docs" ];
+          "docs-selection" = [ "csv" "json" "parquet" "ipc" "ipc_streaming" "dtype-full" "is_in" "rows" "docs" "strings" "object" "lazy" "temporal" "random" "zip_with" "round_series" "checked_arithmetic" "ndarray" "repeat_by" "is_first_distinct" "is_last_distinct" "asof_join" "cross_join" "concat_str" "string_reverse" "string_to_integer" "decompress" "mode" "take_opt_iter" "cum_agg" "rolling_window" "interpolate" "diff" "rank" "range" "diagonal_concat" "horizontal_concat" "abs" "dot_diagram" "string_encoding" "product" "to_dummies" "describe" "list_eval" "cumulative_eval" "timezones" "arg_where" "propagate_nans" "coalesce" "dynamic_group_by" "extract_groups" "replace" ];
+          "dot_diagram" = [ "polars-lazy?/dot_diagram" ];
+          "dot_product" = [ "polars-core/dot_product" ];
+          "dtype-array" = [ "polars-core/dtype-array" "polars-lazy?/dtype-array" "polars-ops/dtype-array" ];
+          "dtype-categorical" = [ "polars-core/dtype-categorical" "polars-io/dtype-categorical" "polars-lazy?/dtype-categorical" "polars-ops/dtype-categorical" ];
+          "dtype-date" = [ "polars-core/dtype-date" "polars-lazy?/dtype-date" "polars-io/dtype-date" "polars-time?/dtype-date" "polars-core/dtype-date" "polars-ops/dtype-date" ];
+          "dtype-datetime" = [ "polars-core/dtype-datetime" "polars-lazy?/dtype-datetime" "polars-io/dtype-datetime" "polars-time?/dtype-datetime" "polars-ops/dtype-datetime" ];
+          "dtype-decimal" = [ "polars-core/dtype-decimal" "polars-lazy?/dtype-decimal" "polars-ops/dtype-decimal" "polars-io/dtype-decimal" ];
+          "dtype-duration" = [ "polars-core/dtype-duration" "polars-lazy?/dtype-duration" "polars-time?/dtype-duration" "polars-core/dtype-duration" "polars-ops/dtype-duration" ];
+          "dtype-full" = [ "dtype-date" "dtype-datetime" "dtype-duration" "dtype-time" "dtype-array" "dtype-i8" "dtype-i16" "dtype-decimal" "dtype-u8" "dtype-u16" "dtype-categorical" "dtype-struct" ];
+          "dtype-i16" = [ "polars-core/dtype-i16" "polars-lazy?/dtype-i16" "polars-ops/dtype-i16" ];
+          "dtype-i8" = [ "polars-core/dtype-i8" "polars-lazy?/dtype-i8" "polars-ops/dtype-i8" ];
+          "dtype-slim" = [ "dtype-date" "dtype-datetime" "dtype-duration" ];
+          "dtype-struct" = [ "polars-core/dtype-struct" "polars-lazy?/dtype-struct" "polars-ops/dtype-struct" "polars-io/dtype-struct" ];
+          "dtype-time" = [ "polars-core/dtype-time" "polars-io/dtype-time" "polars-time?/dtype-time" "polars-ops/dtype-time" ];
+          "dtype-u16" = [ "polars-core/dtype-u16" "polars-lazy?/dtype-u16" "polars-ops/dtype-u16" ];
+          "dtype-u8" = [ "polars-core/dtype-u8" "polars-lazy?/dtype-u8" "polars-ops/dtype-u8" ];
+          "dynamic_group_by" = [ "polars-core/dynamic_group_by" "polars-lazy?/dynamic_group_by" ];
+          "ewma" = [ "polars-ops/ewma" "polars-lazy?/ewma" ];
+          "extract_groups" = [ "polars-lazy?/extract_groups" ];
+          "extract_jsonpath" = [ "polars-core/strings" "polars-ops/extract_jsonpath" "polars-ops/strings" "polars-lazy?/extract_jsonpath" ];
+          "find_many" = [ "polars-plan/find_many" ];
+          "fmt" = [ "polars-core/fmt" ];
+          "fmt_no_tty" = [ "polars-core/fmt_no_tty" ];
+          "fused" = [ "polars-ops/fused" "polars-lazy?/fused" ];
+          "gcp" = [ "async" "cloud" "polars-io/gcp" ];
+          "group_by_list" = [ "polars-core/group_by_list" "polars-ops/group_by_list" ];
+          "hist" = [ "polars-ops/hist" "polars-lazy/hist" ];
+          "horizontal_concat" = [ "polars-core/horizontal_concat" "polars-lazy?/horizontal_concat" ];
+          "http" = [ "async" "cloud" "polars-io/http" ];
+          "interpolate" = [ "polars-ops/interpolate" "polars-lazy?/interpolate" ];
+          "ipc" = [ "polars-io" "polars-io/ipc" "polars-lazy?/ipc" "polars-sql?/ipc" ];
+          "ipc_streaming" = [ "polars-io" "polars-io/ipc_streaming" "polars-lazy?/ipc" ];
+          "is_first_distinct" = [ "polars-lazy?/is_first_distinct" "polars-ops/is_first_distinct" ];
+          "is_in" = [ "polars-lazy?/is_in" ];
+          "is_last_distinct" = [ "polars-lazy?/is_last_distinct" "polars-ops/is_last_distinct" ];
+          "is_unique" = [ "polars-lazy?/is_unique" "polars-ops/is_unique" ];
+          "json" = [ "polars-io" "polars-io/json" "polars-lazy?/json" "polars-sql?/json" "dtype-struct" ];
+          "lazy" = [ "polars-core/lazy" "polars-lazy" ];
+          "lazy_regex" = [ "polars-lazy?/regex" ];
+          "list_any_all" = [ "polars-lazy?/list_any_all" ];
+          "list_count" = [ "polars-ops/list_count" "polars-lazy?/list_count" ];
+          "list_drop_nulls" = [ "polars-lazy?/list_drop_nulls" ];
+          "list_eval" = [ "polars-lazy?/list_eval" ];
+          "list_gather" = [ "polars-ops/list_gather" "polars-lazy?/list_gather" ];
+          "list_sample" = [ "polars-lazy?/list_sample" ];
+          "list_sets" = [ "polars-lazy?/list_sets" ];
+          "list_to_struct" = [ "polars-ops/list_to_struct" "polars-lazy?/list_to_struct" ];
+          "log" = [ "polars-ops/log" "polars-lazy?/log" ];
+          "merge_sorted" = [ "polars-lazy?/merge_sorted" ];
+          "meta" = [ "polars-lazy?/meta" ];
+          "mode" = [ "polars-ops/mode" "polars-lazy?/mode" ];
+          "moment" = [ "polars-ops/moment" "polars-lazy?/moment" ];
+          "ndarray" = [ "polars-core/ndarray" ];
+          "nightly" = [ "polars-core/nightly" "polars-ops?/nightly" "simd" "polars-lazy?/nightly" "polars-sql/nightly" ];
+          "object" = [ "polars-core/object" "polars-lazy?/object" "polars-io/object" ];
+          "parquet" = [ "polars-io" "polars-lazy?/parquet" "polars-io/parquet" "polars-sql?/parquet" ];
+          "partition_by" = [ "polars-core/partition_by" ];
+          "pct_change" = [ "polars-ops/pct_change" "polars-lazy?/pct_change" ];
+          "peaks" = [ "polars-lazy/peaks" ];
+          "performant" = [ "polars-core/performant" "chunked_ids" "dtype-u8" "dtype-u16" "dtype-struct" "cse" "polars-ops/performant" "streaming" "fused" ];
+          "pivot" = [ "polars-lazy?/pivot" ];
+          "polars-io" = [ "dep:polars-io" ];
+          "polars-lazy" = [ "dep:polars-lazy" ];
+          "polars-ops" = [ "dep:polars-ops" ];
+          "polars-plan" = [ "dep:polars-plan" ];
+          "polars-sql" = [ "dep:polars-sql" ];
+          "polars-time" = [ "dep:polars-time" ];
+          "product" = [ "polars-core/product" ];
+          "propagate_nans" = [ "polars-lazy?/propagate_nans" ];
+          "random" = [ "polars-core/random" "polars-lazy?/random" "polars-ops/random" ];
+          "range" = [ "polars-lazy?/range" ];
+          "rank" = [ "polars-lazy?/rank" "polars-ops/rank" ];
+          "reinterpret" = [ "polars-core/reinterpret" ];
+          "repeat_by" = [ "polars-ops/repeat_by" "polars-lazy?/repeat_by" ];
+          "replace" = [ "polars-ops/replace" "polars-lazy?/replace" ];
+          "rle" = [ "polars-lazy?/rle" ];
+          "rolling_window" = [ "polars-core/rolling_window" "polars-lazy?/rolling_window" "polars-time/rolling_window" ];
+          "round_series" = [ "polars-ops/round_series" "polars-lazy?/round_series" ];
+          "row_hash" = [ "polars-core/row_hash" "polars-lazy?/row_hash" ];
+          "rows" = [ "polars-core/rows" ];
+          "search_sorted" = [ "polars-lazy?/search_sorted" ];
+          "semi_anti_join" = [ "polars-lazy?/semi_anti_join" "polars-ops/semi_anti_join" "polars-sql?/semi_anti_join" ];
+          "serde" = [ "polars-core/serde" ];
+          "serde-lazy" = [ "polars-core/serde-lazy" "polars-lazy?/serde" "polars-time?/serde" "polars-io?/serde" "polars-ops?/serde" ];
+          "sign" = [ "polars-lazy?/sign" ];
+          "simd" = [ "polars-core/simd" "polars-io/simd" "polars-ops?/simd" ];
+          "sql" = [ "polars-sql" ];
+          "streaming" = [ "polars-lazy?/streaming" ];
+          "string_encoding" = [ "polars-ops/string_encoding" "polars-lazy?/string_encoding" "polars-core/strings" ];
+          "string_pad" = [ "polars-lazy?/string_pad" "polars-ops/string_pad" ];
+          "string_reverse" = [ "polars-lazy?/string_reverse" "polars-ops/string_reverse" ];
+          "string_to_integer" = [ "polars-lazy?/string_to_integer" "polars-ops/string_to_integer" ];
+          "strings" = [ "polars-core/strings" "polars-lazy?/strings" "polars-ops/strings" ];
+          "take_opt_iter" = [ "polars-core/take_opt_iter" ];
+          "temporal" = [ "polars-core/temporal" "polars-lazy?/temporal" "polars-io/temporal" "polars-time" ];
+          "test" = [ "lazy" "rolling_window" "rank" "round_series" "csv" "dtype-categorical" "cum_agg" "fmt" "diff" "abs" "parquet" "ipc" "ipc_streaming" "json" ];
+          "timezones" = [ "polars-core/timezones" "polars-lazy?/timezones" "polars-io/timezones" ];
+          "to_dummies" = [ "polars-ops/to_dummies" ];
+          "top_k" = [ "polars-lazy?/top_k" ];
+          "trigonometry" = [ "polars-lazy?/trigonometry" ];
+          "true_div" = [ "polars-lazy?/true_div" ];
+          "unique_counts" = [ "polars-ops/unique_counts" "polars-lazy?/unique_counts" ];
+          "zip_with" = [ "polars-core/zip_with" ];
+        };
+        resolvedDefaultFeatures = [ "dtype-categorical" "parquet" "polars-io" "polars-ops" ];
+      };
+      "polars-arrow" = rec {
+        crateName = "polars-arrow";
+        version = "0.36.2";
+        edition = "2021";
+        sha256 = "0vxql6amvwyp6qj0w87vm21y33qa0i61pshs4ry7ixwgd4ps0s6f";
+        authors = [
+          "Jorge C. Leitao <jorgecarleitao@gmail.com>"
+          "Apache Arrow <dev@arrow.apache.org>"
+          "Ritchie Vink <ritchie46@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "ahash";
+            packageId = "ahash";
+          }
+          {
+            name = "arrow-format";
+            packageId = "arrow-format";
+            optional = true;
+            features = [ "ipc" ];
+          }
+          {
+            name = "atoi_simd";
+            packageId = "atoi_simd";
+            optional = true;
+          }
+          {
+            name = "bytemuck";
+            packageId = "bytemuck";
+            features = [ "derive" "extern_crate_alloc" ];
+          }
+          {
+            name = "chrono";
+            packageId = "chrono";
+            usesDefaultFeatures = false;
+            features = [ "std" ];
+          }
+          {
+            name = "dyn-clone";
+            packageId = "dyn-clone";
+          }
+          {
+            name = "either";
+            packageId = "either";
+          }
+          {
+            name = "ethnum";
+            packageId = "ethnum";
+          }
+          {
+            name = "fast-float";
+            packageId = "fast-float";
+            optional = true;
+          }
+          {
+            name = "foreign_vec";
+            packageId = "foreign_vec";
+          }
+          {
+            name = "futures";
+            packageId = "futures";
+            optional = true;
+          }
+          {
+            name = "getrandom";
+            packageId = "getrandom";
+            target = { target, features }: (pkgs.rust.lib.toRustTarget stdenv.hostPlatform == "wasm32-unknown-unknown");
+            features = [ "js" ];
+          }
+          {
+            name = "hashbrown";
+            packageId = "hashbrown";
+            features = [ "rayon" "ahash" ];
+          }
+          {
+            name = "itoa";
+            packageId = "itoa";
+            optional = true;
+          }
+          {
+            name = "lz4";
+            packageId = "lz4";
+            optional = true;
+          }
+          {
+            name = "multiversion";
+            packageId = "multiversion";
+            optional = true;
+          }
+          {
+            name = "num-traits";
+            packageId = "num-traits";
+          }
+          {
+            name = "polars-error";
+            packageId = "polars-error";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-utils";
+            packageId = "polars-utils";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "ryu";
+            packageId = "ryu";
+            optional = true;
+          }
+          {
+            name = "simdutf8";
+            packageId = "simdutf8";
+          }
+          {
+            name = "streaming-iterator";
+            packageId = "streaming-iterator";
+          }
+          {
+            name = "strength_reduce";
+            packageId = "strength_reduce";
+            optional = true;
+          }
+          {
+            name = "zstd";
+            packageId = "zstd";
+            optional = true;
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "version_check";
+            packageId = "version_check";
+          }
+        ];
+        features = {
+          "arrow-array" = [ "dep:arrow-array" ];
+          "arrow-buffer" = [ "dep:arrow-buffer" ];
+          "arrow-data" = [ "dep:arrow-data" ];
+          "arrow-format" = [ "dep:arrow-format" ];
+          "arrow-schema" = [ "dep:arrow-schema" ];
+          "arrow_rs" = [ "arrow-buffer" "arrow-schema" "arrow-data" "arrow-array" ];
+          "async-stream" = [ "dep:async-stream" ];
+          "atoi" = [ "dep:atoi" ];
+          "atoi_simd" = [ "dep:atoi_simd" ];
+          "avro-schema" = [ "dep:avro-schema" ];
+          "chrono-tz" = [ "dep:chrono-tz" ];
+          "compute" = [ "compute_aggregate" "compute_arithmetics" "compute_bitwise" "compute_boolean" "compute_boolean_kleene" "compute_cast" "compute_comparison" "compute_concatenate" "compute_filter" "compute_hash" "compute_if_then_else" "compute_take" "compute_temporal" ];
+          "compute_aggregate" = [ "multiversion" ];
+          "compute_arithmetics" = [ "strength_reduce" "compute_arithmetics_decimal" ];
+          "compute_arithmetics_decimal" = [ "strength_reduce" ];
+          "compute_cast" = [ "compute_take" "ryu" "atoi_simd" "itoa" "fast-float" ];
+          "compute_comparison" = [ "compute_take" "compute_boolean" ];
+          "compute_hash" = [ "multiversion" ];
+          "dtype-decimal" = [ "atoi" ];
+          "fast-float" = [ "dep:fast-float" ];
+          "full" = [ "arrow_rs" "io_ipc" "io_flight" "io_ipc_write_async" "io_ipc_read_async" "io_ipc_compression" "io_avro" "io_avro_compression" "io_avro_async" "regex-syntax" "compute" "chrono-tz" ];
+          "futures" = [ "dep:futures" ];
+          "hex" = [ "dep:hex" ];
+          "indexmap" = [ "dep:indexmap" ];
+          "io_avro" = [ "avro-schema" "polars-error/avro-schema" ];
+          "io_avro_async" = [ "avro-schema/async" ];
+          "io_avro_compression" = [ "avro-schema/compression" ];
+          "io_flight" = [ "io_ipc" "arrow-format/flight-data" ];
+          "io_ipc" = [ "arrow-format" "polars-error/arrow-format" ];
+          "io_ipc_compression" = [ "lz4" "zstd" ];
+          "io_ipc_read_async" = [ "io_ipc" "futures" "async-stream" ];
+          "io_ipc_write_async" = [ "io_ipc" "futures" ];
+          "itoa" = [ "dep:itoa" ];
+          "lz4" = [ "dep:lz4" ];
+          "multiversion" = [ "dep:multiversion" ];
+          "regex" = [ "dep:regex" ];
+          "regex-syntax" = [ "dep:regex-syntax" ];
+          "ryu" = [ "dep:ryu" ];
+          "serde" = [ "dep:serde" ];
+          "strength_reduce" = [ "dep:strength_reduce" ];
+          "zstd" = [ "dep:zstd" ];
+        };
+        resolvedDefaultFeatures = [ "arrow-format" "atoi_simd" "compute" "compute_aggregate" "compute_arithmetics" "compute_arithmetics_decimal" "compute_bitwise" "compute_boolean" "compute_boolean_kleene" "compute_cast" "compute_comparison" "compute_concatenate" "compute_filter" "compute_hash" "compute_if_then_else" "compute_take" "compute_temporal" "fast-float" "futures" "io_ipc" "io_ipc_compression" "io_ipc_write_async" "itoa" "lz4" "multiversion" "ryu" "strength_reduce" "strings" "temporal" "zstd" ];
+      };
+      "polars-compute" = rec {
+        crateName = "polars-compute";
+        version = "0.36.2";
+        edition = "2021";
+        sha256 = "1pa4l1w41gdzdff81ih1z05z3gz568k81i6flibbca8v2igvqkxi";
+        authors = [
+          "Ritchie Vink <ritchie46@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "bytemuck";
+            packageId = "bytemuck";
+            features = [ "derive" "extern_crate_alloc" ];
+          }
+          {
+            name = "num-traits";
+            packageId = "num-traits";
+          }
+          {
+            name = "polars-arrow";
+            packageId = "polars-arrow";
+            rename = "arrow";
+            usesDefaultFeatures = false;
+            features = [ "compute_aggregate" "compute_arithmetics" "compute_boolean" "compute_boolean_kleene" "compute_cast" "compute_comparison" "compute_concatenate" "compute_filter" "compute_if_then_else" ];
+          }
+          {
+            name = "polars-utils";
+            packageId = "polars-utils";
+            usesDefaultFeatures = false;
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "version_check";
+            packageId = "version_check";
+          }
+        ];
+        features = { };
+      };
+      "polars-core" = rec {
+        crateName = "polars-core";
+        version = "0.36.2";
+        edition = "2021";
+        sha256 = "08sar9h97znpb8ziyvrrvvx28lyzc21vwsd7gvwybjxn6kkyzxfh";
+        authors = [
+          "Ritchie Vink <ritchie46@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "ahash";
+            packageId = "ahash";
+          }
+          {
+            name = "bitflags";
+            packageId = "bitflags 2.4.1";
+          }
+          {
+            name = "bytemuck";
+            packageId = "bytemuck";
+            features = [ "derive" "extern_crate_alloc" ];
+          }
+          {
+            name = "chrono";
+            packageId = "chrono";
+            optional = true;
+            usesDefaultFeatures = false;
+            features = [ "std" ];
+          }
+          {
+            name = "either";
+            packageId = "either";
+          }
+          {
+            name = "hashbrown";
+            packageId = "hashbrown";
+            features = [ "rayon" "ahash" ];
+          }
+          {
+            name = "indexmap";
+            packageId = "indexmap";
+            features = [ "std" ];
+          }
+          {
+            name = "num-traits";
+            packageId = "num-traits";
+          }
+          {
+            name = "once_cell";
+            packageId = "once_cell";
+          }
+          {
+            name = "polars-arrow";
+            packageId = "polars-arrow";
+            rename = "arrow";
+            usesDefaultFeatures = false;
+            features = [ "compute_aggregate" "compute_arithmetics" "compute_boolean" "compute_boolean_kleene" "compute_cast" "compute_comparison" "compute_concatenate" "compute_filter" "compute_if_then_else" ];
+          }
+          {
+            name = "polars-compute";
+            packageId = "polars-compute";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-error";
+            packageId = "polars-error";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-row";
+            packageId = "polars-row";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-utils";
+            packageId = "polars-utils";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "rand";
+            packageId = "rand";
+            optional = true;
+            features = [ "small_rng" "std" ];
+          }
+          {
+            name = "rand_distr";
+            packageId = "rand_distr";
+            optional = true;
+          }
+          {
+            name = "rayon";
+            packageId = "rayon";
+          }
+          {
+            name = "regex";
+            packageId = "regex";
+            optional = true;
+          }
+          {
+            name = "smartstring";
+            packageId = "smartstring";
+          }
+          {
+            name = "thiserror";
+            packageId = "thiserror";
+          }
+          {
+            name = "xxhash-rust";
+            packageId = "xxhash-rust";
+            features = [ "xxh3" ];
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "version_check";
+            packageId = "version_check";
+          }
+        ];
+        features = {
+          "arrow-array" = [ "dep:arrow-array" ];
+          "arrow_rs" = [ "arrow-array" "arrow/arrow_rs" ];
+          "bigidx" = [ "arrow/bigidx" "polars-utils/bigidx" ];
+          "chrono" = [ "dep:chrono" ];
+          "chrono-tz" = [ "dep:chrono-tz" ];
+          "comfy-table" = [ "dep:comfy-table" ];
+          "default" = [ "algorithm_group_by" ];
+          "docs-selection" = [ "ndarray" "rows" "docs" "strings" "object" "lazy" "temporal" "random" "zip_with" "checked_arithmetic" "is_first_distinct" "is_last_distinct" "asof_join" "dot_product" "row_hash" "rolling_window" "dtype-categorical" "dtype-decimal" "diagonal_concat" "horizontal_concat" "dataframe_arithmetic" "product" "describe" "chunked_ids" "partition_by" "algorithm_group_by" ];
+          "dtype-array" = [ "arrow/dtype-array" "polars-compute/dtype-array" ];
+          "dtype-date" = [ "temporal" ];
+          "dtype-datetime" = [ "temporal" ];
+          "dtype-decimal" = [ "dep:itoap" "arrow/dtype-decimal" ];
+          "dtype-duration" = [ "temporal" ];
+          "dtype-time" = [ "temporal" ];
+          "dynamic_group_by" = [ "dtype-datetime" "dtype-date" ];
+          "fmt" = [ "comfy-table/tty" ];
+          "fmt_no_tty" = [ "comfy-table" ];
+          "ndarray" = [ "dep:ndarray" ];
+          "nightly" = [ "simd" "hashbrown/nightly" "polars-utils/nightly" "arrow/nightly" ];
+          "object" = [ "serde_json" ];
+          "performant" = [ "arrow/performant" "reinterpret" ];
+          "rand" = [ "dep:rand" ];
+          "rand_distr" = [ "dep:rand_distr" ];
+          "random" = [ "rand" "rand_distr" ];
+          "regex" = [ "dep:regex" ];
+          "serde" = [ "dep:serde" "smartstring/serde" "bitflags/serde" ];
+          "serde-lazy" = [ "serde" "arrow/serde" "indexmap/serde" "smartstring/serde" "chrono/serde" ];
+          "serde_json" = [ "dep:serde_json" ];
+          "simd" = [ "arrow/simd" "polars-compute/simd" ];
+          "strings" = [ "regex" "arrow/strings" "polars-error/regex" ];
+          "temporal" = [ "regex" "chrono" "polars-error/regex" ];
+          "timezones" = [ "chrono-tz" "arrow/chrono-tz" "arrow/timezones" ];
+        };
+        resolvedDefaultFeatures = [ "algorithm_group_by" "chrono" "chunked_ids" "dtype-categorical" "dtype-date" "dtype-datetime" "dtype-duration" "dtype-i16" "dtype-i8" "dtype-time" "lazy" "rand" "rand_distr" "random" "regex" "reinterpret" "rows" "strings" "temporal" "zip_with" ];
+      };
+      "polars-error" = rec {
+        crateName = "polars-error";
+        version = "0.36.2";
+        edition = "2021";
+        sha256 = "11m80a899kp45b3dz9jbvrn5001hw8izbdp7d2czrswrixwdx5k3";
+        authors = [
+          "Ritchie Vink <ritchie46@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "arrow-format";
+            packageId = "arrow-format";
+            optional = true;
+          }
+          {
+            name = "regex";
+            packageId = "regex";
+            optional = true;
+          }
+          {
+            name = "simdutf8";
+            packageId = "simdutf8";
+          }
+          {
+            name = "thiserror";
+            packageId = "thiserror";
+          }
+        ];
+        features = {
+          "arrow-format" = [ "dep:arrow-format" ];
+          "avro-schema" = [ "dep:avro-schema" ];
+          "object_store" = [ "dep:object_store" ];
+          "regex" = [ "dep:regex" ];
+        };
+        resolvedDefaultFeatures = [ "arrow-format" "regex" ];
+      };
+      "polars-io" = rec {
+        crateName = "polars-io";
+        version = "0.36.2";
+        edition = "2021";
+        sha256 = "1smamd34ghlxyxdd4aqdplk7k8xm1l626brmzlc4fvwlx3pmh13x";
+        authors = [
+          "Ritchie Vink <ritchie46@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "ahash";
+            packageId = "ahash";
+          }
+          {
+            name = "async-trait";
+            packageId = "async-trait";
+            optional = true;
+          }
+          {
+            name = "bytes";
+            packageId = "bytes";
+          }
+          {
+            name = "futures";
+            packageId = "futures";
+            optional = true;
+          }
+          {
+            name = "home";
+            packageId = "home";
+            target = { target, features }: (!(builtins.elem "wasm" target."family"));
+          }
+          {
+            name = "memchr";
+            packageId = "memchr";
+          }
+          {
+            name = "memmap2";
+            packageId = "memmap2";
+            rename = "memmap";
+          }
+          {
+            name = "num-traits";
+            packageId = "num-traits";
+          }
+          {
+            name = "once_cell";
+            packageId = "once_cell";
+          }
+          {
+            name = "percent-encoding";
+            packageId = "percent-encoding";
+          }
+          {
+            name = "polars-arrow";
+            packageId = "polars-arrow";
+            rename = "arrow";
+            usesDefaultFeatures = false;
+            features = [ "compute_aggregate" "compute_arithmetics" "compute_boolean" "compute_boolean_kleene" "compute_cast" "compute_comparison" "compute_concatenate" "compute_filter" "compute_if_then_else" ];
+          }
+          {
+            name = "polars-core";
+            packageId = "polars-core";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-error";
+            packageId = "polars-error";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-parquet";
+            packageId = "polars-parquet";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-utils";
+            packageId = "polars-utils";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "rayon";
+            packageId = "rayon";
+          }
+          {
+            name = "regex";
+            packageId = "regex";
+          }
+          {
+            name = "smartstring";
+            packageId = "smartstring";
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+            optional = true;
+            features = [ "net" "rt-multi-thread" "time" "sync" ];
+          }
+          {
+            name = "tokio-util";
+            packageId = "tokio-util";
+            optional = true;
+            features = [ "io" "io-util" ];
+          }
+        ];
+        features = {
+          "async" = [ "async-trait" "futures" "tokio" "tokio-util" "arrow/io_ipc_write_async" "polars-error/regex" "polars-parquet?/async" ];
+          "async-trait" = [ "dep:async-trait" ];
+          "atoi_simd" = [ "dep:atoi_simd" ];
+          "avro" = [ "arrow/io_avro" "arrow/io_avro_compression" ];
+          "aws" = [ "object_store/aws" "cloud" "reqwest" ];
+          "azure" = [ "object_store/azure" "cloud" ];
+          "chrono" = [ "dep:chrono" ];
+          "chrono-tz" = [ "dep:chrono-tz" ];
+          "cloud" = [ "object_store" "async" "polars-error/object_store" "url" ];
+          "csv" = [ "atoi_simd" "polars-core/rows" "itoa" "ryu" "fast-float" "simdutf8" ];
+          "decompress" = [ "flate2/rust_backend" "zstd" ];
+          "decompress-fast" = [ "flate2/zlib-ng" "zstd" ];
+          "default" = [ "decompress" ];
+          "dtype-categorical" = [ "polars-core/dtype-categorical" ];
+          "dtype-date" = [ "polars-core/dtype-date" "polars-time/dtype-date" ];
+          "dtype-datetime" = [ "polars-core/dtype-datetime" "polars-core/temporal" "polars-time/dtype-datetime" "chrono" ];
+          "dtype-decimal" = [ "polars-core/dtype-decimal" ];
+          "dtype-struct" = [ "polars-core/dtype-struct" ];
+          "dtype-time" = [ "polars-core/dtype-time" "polars-core/temporal" "polars-time/dtype-time" ];
+          "fast-float" = [ "dep:fast-float" ];
+          "flate2" = [ "dep:flate2" ];
+          "fmt" = [ "polars-core/fmt" ];
+          "futures" = [ "dep:futures" ];
+          "gcp" = [ "object_store/gcp" "cloud" ];
+          "http" = [ "object_store/http" "cloud" ];
+          "ipc" = [ "arrow/io_ipc" "arrow/io_ipc_compression" ];
+          "ipc_streaming" = [ "arrow/io_ipc" "arrow/io_ipc_compression" ];
+          "itoa" = [ "dep:itoa" ];
+          "json" = [ "polars-json" "simd-json" "atoi_simd" "serde_json" "dtype-struct" "csv" ];
+          "object_store" = [ "dep:object_store" ];
+          "parquet" = [ "polars-parquet" "polars-parquet/compression" ];
+          "partition" = [ "polars-core/partition_by" ];
+          "polars-json" = [ "dep:polars-json" ];
+          "polars-parquet" = [ "dep:polars-parquet" ];
+          "polars-time" = [ "dep:polars-time" ];
+          "python" = [ "polars-error/python" ];
+          "reqwest" = [ "dep:reqwest" ];
+          "ryu" = [ "dep:ryu" ];
+          "serde" = [ "dep:serde" "polars-core/serde-lazy" ];
+          "serde_json" = [ "dep:serde_json" ];
+          "simd-json" = [ "dep:simd-json" ];
+          "simdutf8" = [ "dep:simdutf8" ];
+          "temporal" = [ "dtype-datetime" "dtype-date" "dtype-time" ];
+          "timezones" = [ "chrono-tz" "dtype-datetime" ];
+          "tokio" = [ "dep:tokio" ];
+          "tokio-util" = [ "dep:tokio-util" ];
+          "url" = [ "dep:url" ];
+          "zstd" = [ "dep:zstd" ];
+        };
+        resolvedDefaultFeatures = [ "async" "async-trait" "dtype-categorical" "futures" "ipc" "lazy" "parquet" "polars-parquet" "tokio" "tokio-util" ];
+      };
+      "polars-lazy" = rec {
+        crateName = "polars-lazy";
+        version = "0.36.2";
+        edition = "2021";
+        sha256 = "1mdml4hs574njb23mb9gl6x92x2bb4vdfzsazkl3ifq516s0awcx";
+        authors = [
+          "Ritchie Vink <ritchie46@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "ahash";
+            packageId = "ahash";
+          }
+          {
+            name = "bitflags";
+            packageId = "bitflags 2.4.1";
+          }
+          {
+            name = "glob";
+            packageId = "glob";
+          }
+          {
+            name = "once_cell";
+            packageId = "once_cell";
+          }
+          {
+            name = "polars-arrow";
+            packageId = "polars-arrow";
+            rename = "arrow";
+            usesDefaultFeatures = false;
+            features = [ "compute_aggregate" "compute_arithmetics" "compute_boolean" "compute_boolean_kleene" "compute_cast" "compute_comparison" "compute_concatenate" "compute_filter" "compute_if_then_else" ];
+          }
+          {
+            name = "polars-core";
+            packageId = "polars-core";
+            usesDefaultFeatures = false;
+            features = [ "lazy" "zip_with" "random" ];
+          }
+          {
+            name = "polars-io";
+            packageId = "polars-io";
+            usesDefaultFeatures = false;
+            features = [ "lazy" ];
+          }
+          {
+            name = "polars-ops";
+            packageId = "polars-ops";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-pipe";
+            packageId = "polars-pipe";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-plan";
+            packageId = "polars-plan";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-time";
+            packageId = "polars-time";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-utils";
+            packageId = "polars-utils";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "rayon";
+            packageId = "rayon";
+          }
+          {
+            name = "smartstring";
+            packageId = "smartstring";
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "version_check";
+            packageId = "version_check";
+          }
+        ];
+        features = {
+          "abs" = [ "polars-plan/abs" ];
+          "approx_unique" = [ "polars-plan/approx_unique" ];
+          "arg_where" = [ "polars-plan/arg_where" ];
+          "array_any_all" = [ "polars-ops/array_any_all" "polars-plan/array_any_all" "dtype-array" ];
+          "asof_join" = [ "polars-plan/asof_join" "polars-time" "polars-ops/asof_join" ];
+          "async" = [ "polars-plan/async" "polars-io/cloud" "polars-pipe?/async" ];
+          "bigidx" = [ "polars-plan/bigidx" ];
+          "binary_encoding" = [ "polars-plan/binary_encoding" ];
+          "chunked_ids" = [ "polars-plan/chunked_ids" "polars-core/chunked_ids" "polars-ops/chunked_ids" ];
+          "cloud" = [ "async" "polars-pipe?/cloud" "polars-plan/cloud" "tokio" "futures" ];
+          "cloud_write" = [ "cloud" ];
+          "coalesce" = [ "polars-plan/coalesce" ];
+          "concat_str" = [ "polars-plan/concat_str" ];
+          "cov" = [ "polars-ops/cov" "polars-plan/cov" ];
+          "cross_join" = [ "polars-plan/cross_join" "polars-pipe?/cross_join" "polars-ops/cross_join" ];
+          "cse" = [ "polars-plan/cse" ];
+          "csv" = [ "polars-io/csv" "polars-plan/csv" "polars-pipe?/csv" ];
+          "cum_agg" = [ "polars-plan/cum_agg" ];
+          "cutqcut" = [ "polars-plan/cutqcut" "polars-ops/cutqcut" ];
+          "date_offset" = [ "polars-plan/date_offset" ];
+          "diff" = [ "polars-plan/diff" "polars-plan/diff" ];
+          "dot_diagram" = [ "polars-plan/dot_diagram" ];
+          "dtype-array" = [ "polars-plan/dtype-array" "polars-pipe?/dtype-array" "polars-ops/dtype-array" ];
+          "dtype-categorical" = [ "polars-plan/dtype-categorical" "polars-pipe?/dtype-categorical" ];
+          "dtype-date" = [ "polars-plan/dtype-date" "polars-time/dtype-date" "temporal" ];
+          "dtype-datetime" = [ "polars-plan/dtype-datetime" "polars-time/dtype-datetime" "temporal" ];
+          "dtype-decimal" = [ "polars-plan/dtype-decimal" "polars-pipe?/dtype-decimal" ];
+          "dtype-duration" = [ "polars-plan/dtype-duration" "polars-time/dtype-duration" "temporal" ];
+          "dtype-i16" = [ "polars-plan/dtype-i16" "polars-pipe?/dtype-i16" ];
+          "dtype-i8" = [ "polars-plan/dtype-i8" "polars-pipe?/dtype-i8" ];
+          "dtype-struct" = [ "polars-plan/dtype-struct" ];
+          "dtype-time" = [ "polars-core/dtype-time" "temporal" ];
+          "dtype-u16" = [ "polars-plan/dtype-u16" "polars-pipe?/dtype-u16" ];
+          "dtype-u8" = [ "polars-plan/dtype-u8" "polars-pipe?/dtype-u8" ];
+          "dynamic_group_by" = [ "polars-plan/dynamic_group_by" "polars-time" "temporal" ];
+          "ewma" = [ "polars-plan/ewma" ];
+          "extract_groups" = [ "polars-plan/extract_groups" ];
+          "extract_jsonpath" = [ "polars-plan/extract_jsonpath" "polars-ops/extract_jsonpath" ];
+          "fmt" = [ "polars-core/fmt" "polars-plan/fmt" ];
+          "fused" = [ "polars-plan/fused" "polars-ops/fused" ];
+          "futures" = [ "dep:futures" ];
+          "hist" = [ "polars-plan/hist" ];
+          "horizontal_concat" = [ "polars-plan/horizontal_concat" "polars-core/horizontal_concat" ];
+          "interpolate" = [ "polars-plan/interpolate" ];
+          "ipc" = [ "polars-io/ipc" "polars-plan/ipc" "polars-pipe?/ipc" ];
+          "is_first_distinct" = [ "polars-plan/is_first_distinct" ];
+          "is_in" = [ "polars-plan/is_in" "polars-ops/is_in" ];
+          "is_last_distinct" = [ "polars-plan/is_last_distinct" ];
+          "is_unique" = [ "polars-plan/is_unique" ];
+          "json" = [ "polars-io/json" "polars-plan/json" "polars-json" "polars-pipe/json" ];
+          "list_any_all" = [ "polars-ops/list_any_all" "polars-plan/list_any_all" ];
+          "list_count" = [ "polars-ops/list_count" "polars-plan/list_count" ];
+          "list_drop_nulls" = [ "polars-ops/list_drop_nulls" "polars-plan/list_drop_nulls" ];
+          "list_gather" = [ "polars-ops/list_gather" "polars-plan/list_gather" ];
+          "list_sample" = [ "polars-ops/list_sample" "polars-plan/list_sample" ];
+          "list_sets" = [ "polars-plan/list_sets" "polars-ops/list_sets" ];
+          "list_to_struct" = [ "polars-plan/list_to_struct" ];
+          "log" = [ "polars-plan/log" ];
+          "merge_sorted" = [ "polars-plan/merge_sorted" ];
+          "meta" = [ "polars-plan/meta" ];
+          "mode" = [ "polars-plan/mode" ];
+          "moment" = [ "polars-plan/moment" "polars-ops/moment" ];
+          "nightly" = [ "polars-core/nightly" "polars-pipe?/nightly" "polars-plan/nightly" ];
+          "object" = [ "polars-plan/object" ];
+          "panic_on_schema" = [ "polars-plan/panic_on_schema" ];
+          "parquet" = [ "polars-io/parquet" "polars-plan/parquet" "polars-pipe?/parquet" ];
+          "pct_change" = [ "polars-plan/pct_change" ];
+          "peaks" = [ "polars-plan/peaks" ];
+          "pivot" = [ "polars-core/rows" "polars-ops/pivot" ];
+          "polars-json" = [ "dep:polars-json" ];
+          "polars-pipe" = [ "dep:polars-pipe" ];
+          "polars-time" = [ "dep:polars-time" ];
+          "propagate_nans" = [ "polars-plan/propagate_nans" ];
+          "pyo3" = [ "dep:pyo3" ];
+          "python" = [ "pyo3" "polars-plan/python" "polars-core/python" "polars-io/python" ];
+          "random" = [ "polars-plan/random" ];
+          "range" = [ "polars-plan/range" ];
+          "rank" = [ "polars-plan/rank" ];
+          "regex" = [ "polars-plan/regex" ];
+          "repeat_by" = [ "polars-plan/repeat_by" ];
+          "replace" = [ "polars-plan/replace" ];
+          "rle" = [ "polars-plan/rle" "polars-ops/rle" ];
+          "rolling_window" = [ "polars-plan/rolling_window" "polars-time/rolling_window" ];
+          "round_series" = [ "polars-plan/round_series" "polars-ops/round_series" ];
+          "row_hash" = [ "polars-plan/row_hash" ];
+          "search_sorted" = [ "polars-plan/search_sorted" ];
+          "semi_anti_join" = [ "polars-plan/semi_anti_join" ];
+          "serde" = [ "polars-plan/serde" "arrow/serde" "polars-core/serde-lazy" "polars-time?/serde" "polars-io/serde" "polars-ops/serde" ];
+          "sign" = [ "polars-plan/sign" ];
+          "streaming" = [ "chunked_ids" "polars-pipe" "polars-plan/streaming" "polars-ops/chunked_ids" ];
+          "string_encoding" = [ "polars-plan/string_encoding" ];
+          "string_pad" = [ "polars-plan/string_pad" ];
+          "string_reverse" = [ "polars-plan/string_reverse" ];
+          "string_to_integer" = [ "polars-plan/string_to_integer" ];
+          "strings" = [ "polars-plan/strings" ];
+          "temporal" = [ "dtype-datetime" "dtype-date" "dtype-time" "dtype-i8" "dtype-i16" "dtype-duration" "polars-plan/temporal" ];
+          "test" = [ "polars-plan/debugging" "panic_on_schema" "rolling_window" "rank" "round_series" "csv" "dtype-categorical" "cum_agg" "regex" "polars-core/fmt" "diff" "abs" "parquet" "ipc" "dtype-date" ];
+          "test_all" = [ "test" "strings" "regex" "ipc" "row_hash" "string_pad" "string_to_integer" "search_sorted" "top_k" "pivot" "semi_anti_join" "cse" ];
+          "timezones" = [ "polars-plan/timezones" ];
+          "tokio" = [ "dep:tokio" ];
+          "top_k" = [ "polars-plan/top_k" ];
+          "trigonometry" = [ "polars-plan/trigonometry" ];
+          "true_div" = [ "polars-plan/true_div" ];
+          "unique_counts" = [ "polars-plan/unique_counts" ];
+        };
+        resolvedDefaultFeatures = [ "abs" "cross_join" "cum_agg" "dtype-categorical" "dtype-date" "dtype-datetime" "dtype-duration" "dtype-i16" "dtype-i8" "dtype-time" "is_in" "log" "meta" "parquet" "polars-time" "regex" "round_series" "strings" "temporal" "trigonometry" ];
+      };
+      "polars-ops" = rec {
+        crateName = "polars-ops";
+        version = "0.36.2";
+        edition = "2021";
+        sha256 = "13m7dh4vpdmcah04a7kql933l7y7045f0hybbmgff4dbav2ay29f";
+        authors = [
+          "Ritchie Vink <ritchie46@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "ahash";
+            packageId = "ahash";
+          }
+          {
+            name = "argminmax";
+            packageId = "argminmax";
+            usesDefaultFeatures = false;
+            features = [ "float" ];
+          }
+          {
+            name = "bytemuck";
+            packageId = "bytemuck";
+            features = [ "derive" "extern_crate_alloc" ];
+          }
+          {
+            name = "either";
+            packageId = "either";
+          }
+          {
+            name = "hashbrown";
+            packageId = "hashbrown";
+            features = [ "rayon" "ahash" ];
+          }
+          {
+            name = "indexmap";
+            packageId = "indexmap";
+            features = [ "std" ];
+          }
+          {
+            name = "memchr";
+            packageId = "memchr";
+          }
+          {
+            name = "num-traits";
+            packageId = "num-traits";
+          }
+          {
+            name = "polars-arrow";
+            packageId = "polars-arrow";
+            rename = "arrow";
+            usesDefaultFeatures = false;
+            features = [ "compute_aggregate" "compute_arithmetics" "compute_boolean" "compute_boolean_kleene" "compute_cast" "compute_comparison" "compute_concatenate" "compute_filter" "compute_if_then_else" ];
+          }
+          {
+            name = "polars-compute";
+            packageId = "polars-compute";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-core";
+            packageId = "polars-core";
+            usesDefaultFeatures = false;
+            features = [ "algorithm_group_by" "zip_with" ];
+          }
+          {
+            name = "polars-error";
+            packageId = "polars-error";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-utils";
+            packageId = "polars-utils";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "rayon";
+            packageId = "rayon";
+          }
+          {
+            name = "regex";
+            packageId = "regex";
+          }
+          {
+            name = "smartstring";
+            packageId = "smartstring";
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "version_check";
+            packageId = "version_check";
+          }
+        ];
+        features = {
+          "aho-corasick" = [ "dep:aho-corasick" ];
+          "array_any_all" = [ "dtype-array" ];
+          "asof_join" = [ "polars-core/asof_join" ];
+          "base64" = [ "dep:base64" ];
+          "big_idx" = [ "polars-core/bigidx" ];
+          "binary_encoding" = [ "base64" "hex" ];
+          "chrono" = [ "dep:chrono" ];
+          "chrono-tz" = [ "dep:chrono-tz" ];
+          "chunked_ids" = [ "polars-core/chunked_ids" ];
+          "cutqcut" = [ "dtype-categorical" "dtype-struct" ];
+          "dtype-array" = [ "polars-core/dtype-array" ];
+          "dtype-categorical" = [ "polars-core/dtype-categorical" ];
+          "dtype-date" = [ "polars-core/dtype-date" "polars-core/temporal" ];
+          "dtype-datetime" = [ "polars-core/dtype-datetime" "polars-core/temporal" ];
+          "dtype-decimal" = [ "polars-core/dtype-decimal" ];
+          "dtype-duration" = [ "polars-core/dtype-duration" "polars-core/temporal" ];
+          "dtype-i16" = [ "polars-core/dtype-i16" ];
+          "dtype-i8" = [ "polars-core/dtype-i8" ];
+          "dtype-struct" = [ "polars-core/dtype-struct" "polars-core/temporal" ];
+          "dtype-time" = [ "polars-core/dtype-time" "polars-core/temporal" ];
+          "dtype-u16" = [ "polars-core/dtype-u16" ];
+          "dtype-u8" = [ "polars-core/dtype-u8" ];
+          "extract_groups" = [ "dtype-struct" "polars-core/regex" ];
+          "extract_jsonpath" = [ "serde_json" "jsonpath_lib" "polars-json" ];
+          "find_many" = [ "aho-corasick" ];
+          "group_by_list" = [ "polars-core/group_by_list" ];
+          "hex" = [ "dep:hex" ];
+          "hist" = [ "dtype-categorical" "dtype-struct" ];
+          "is_in" = [ "polars-core/reinterpret" ];
+          "jsonpath_lib" = [ "dep:jsonpath_lib" ];
+          "list_to_struct" = [ "polars-core/dtype-struct" ];
+          "nightly" = [ "polars-utils/nightly" ];
+          "object" = [ "polars-core/object" ];
+          "pct_change" = [ "diff" ];
+          "performant" = [ "polars-core/performant" "fused" ];
+          "pivot" = [ "polars-core/reinterpret" ];
+          "polars-json" = [ "dep:polars-json" ];
+          "rand" = [ "dep:rand" ];
+          "rand_distr" = [ "dep:rand_distr" ];
+          "random" = [ "rand" "rand_distr" ];
+          "rank" = [ "rand" ];
+          "replace" = [ "is_in" ];
+          "rle" = [ "dtype-struct" ];
+          "rolling_window" = [ "polars-core/rolling_window" ];
+          "serde" = [ "dep:serde" ];
+          "serde_json" = [ "dep:serde_json" ];
+          "simd" = [ "argminmax/nightly_simd" ];
+          "string_encoding" = [ "base64" "hex" ];
+          "string_pad" = [ "polars-core/strings" ];
+          "string_reverse" = [ "polars-core/strings" "unicode-reverse" ];
+          "string_to_integer" = [ "polars-core/strings" ];
+          "strings" = [ "polars-core/strings" ];
+          "timezones" = [ "chrono-tz" "chrono" ];
+          "unicode-reverse" = [ "dep:unicode-reverse" ];
+        };
+        resolvedDefaultFeatures = [ "abs" "cross_join" "cum_agg" "dtype-categorical" "is_in" "log" "round_series" "search_sorted" "strings" ];
+      };
+      "polars-parquet" = rec {
+        crateName = "polars-parquet";
+        version = "0.36.2";
+        edition = "2021";
+        sha256 = "0gay037sw5hcg2q93pxcfh7krcchl1px8g838d8vhjpnn5klv8kv";
+        authors = [
+          "Jorge C. Leitao <jorgecarleitao@gmail.com>"
+          "Apache Arrow <dev@arrow.apache.org>"
+          "Ritchie Vink <ritchie46@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "ahash";
+            packageId = "ahash";
+          }
+          {
+            name = "async-stream";
+            packageId = "async-stream";
+            optional = true;
+          }
+          {
+            name = "base64";
+            packageId = "base64";
+          }
+          {
+            name = "brotli";
+            packageId = "brotli";
+            optional = true;
+          }
+          {
+            name = "ethnum";
+            packageId = "ethnum";
+          }
+          {
+            name = "flate2";
+            packageId = "flate2";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures";
+            packageId = "futures";
+            optional = true;
+          }
+          {
+            name = "lz4";
+            packageId = "lz4";
+            optional = true;
+          }
+          {
+            name = "num-traits";
+            packageId = "num-traits";
+          }
+          {
+            name = "parquet-format-safe";
+            packageId = "parquet-format-safe";
+          }
+          {
+            name = "polars-arrow";
+            packageId = "polars-arrow";
+            rename = "arrow";
+            usesDefaultFeatures = false;
+            features = [ "compute_aggregate" "compute_arithmetics" "compute_boolean" "compute_boolean_kleene" "compute_cast" "compute_comparison" "compute_concatenate" "compute_filter" "compute_if_then_else" "io_ipc" ];
+          }
+          {
+            name = "polars-error";
+            packageId = "polars-error";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-utils";
+            packageId = "polars-utils";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "seq-macro";
+            packageId = "seq-macro";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "simdutf8";
+            packageId = "simdutf8";
+          }
+          {
+            name = "snap";
+            packageId = "snap";
+            optional = true;
+          }
+          {
+            name = "streaming-decompression";
+            packageId = "streaming-decompression";
+          }
+          {
+            name = "zstd";
+            packageId = "zstd";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "async" = [ "async-stream" "futures" "parquet-format-safe/async" ];
+          "async-stream" = [ "dep:async-stream" ];
+          "bloom_filter" = [ "xxhash-rust" ];
+          "brotli" = [ "dep:brotli" ];
+          "compression" = [ "zstd" "gzip" "snappy" "lz4" "brotli" ];
+          "fallible-streaming-iterator" = [ "dep:fallible-streaming-iterator" ];
+          "flate2" = [ "dep:flate2" ];
+          "futures" = [ "dep:futures" ];
+          "gzip" = [ "flate2/rust_backend" ];
+          "gzip_zlib_ng" = [ "flate2/zlib-ng" ];
+          "lz4" = [ "dep:lz4" ];
+          "serde" = [ "dep:serde" ];
+          "serde_types" = [ "serde" ];
+          "snap" = [ "dep:snap" ];
+          "snappy" = [ "snap" ];
+          "xxhash-rust" = [ "dep:xxhash-rust" ];
+          "zstd" = [ "dep:zstd" ];
+        };
+        resolvedDefaultFeatures = [ "async" "async-stream" "brotli" "compression" "flate2" "futures" "gzip" "lz4" "snap" "snappy" "zstd" ];
+      };
+      "polars-pipe" = rec {
+        crateName = "polars-pipe";
+        version = "0.36.2";
+        edition = "2021";
+        sha256 = "00217q51mnq7i57k3sax293xnwghm5hridbpgl11fffcfg8fmdyr";
+        authors = [
+          "Ritchie Vink <ritchie46@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "crossbeam-channel";
+            packageId = "crossbeam-channel";
+          }
+          {
+            name = "crossbeam-queue";
+            packageId = "crossbeam-queue";
+          }
+          {
+            name = "enum_dispatch";
+            packageId = "enum_dispatch";
+          }
+          {
+            name = "hashbrown";
+            packageId = "hashbrown";
+            features = [ "rayon" "ahash" ];
+          }
+          {
+            name = "num-traits";
+            packageId = "num-traits";
+          }
+          {
+            name = "polars-arrow";
+            packageId = "polars-arrow";
+            rename = "arrow";
+            usesDefaultFeatures = false;
+            features = [ "compute_aggregate" "compute_arithmetics" "compute_boolean" "compute_boolean_kleene" "compute_cast" "compute_comparison" "compute_concatenate" "compute_filter" "compute_if_then_else" ];
+          }
+          {
+            name = "polars-compute";
+            packageId = "polars-compute";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-core";
+            packageId = "polars-core";
+            usesDefaultFeatures = false;
+            features = [ "lazy" "zip_with" "random" "rows" "chunked_ids" ];
+          }
+          {
+            name = "polars-io";
+            packageId = "polars-io";
+            usesDefaultFeatures = false;
+            features = [ "ipc" ];
+          }
+          {
+            name = "polars-ops";
+            packageId = "polars-ops";
+            usesDefaultFeatures = false;
+            features = [ "search_sorted" ];
+          }
+          {
+            name = "polars-plan";
+            packageId = "polars-plan";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-row";
+            packageId = "polars-row";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-utils";
+            packageId = "polars-utils";
+            usesDefaultFeatures = false;
+            features = [ "sysinfo" ];
+          }
+          {
+            name = "rayon";
+            packageId = "rayon";
+          }
+          {
+            name = "smartstring";
+            packageId = "smartstring";
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "version_check";
+            packageId = "version_check";
+          }
+        ];
+        features = {
+          "async" = [ "polars-plan/async" "polars-io/async" ];
+          "cloud" = [ "async" "polars-io/cloud" "polars-plan/cloud" "tokio" "futures" ];
+          "cross_join" = [ "polars-ops/cross_join" ];
+          "csv" = [ "polars-plan/csv" "polars-io/csv" ];
+          "dtype-array" = [ "polars-core/dtype-array" ];
+          "dtype-categorical" = [ "polars-core/dtype-categorical" ];
+          "dtype-decimal" = [ "polars-core/dtype-decimal" ];
+          "dtype-i16" = [ "polars-core/dtype-i16" ];
+          "dtype-i8" = [ "polars-core/dtype-i8" ];
+          "dtype-u16" = [ "polars-core/dtype-u16" ];
+          "dtype-u8" = [ "polars-core/dtype-u8" ];
+          "futures" = [ "dep:futures" ];
+          "ipc" = [ "polars-plan/ipc" "polars-io/ipc" ];
+          "json" = [ "polars-plan/json" "polars-io/json" ];
+          "nightly" = [ "polars-core/nightly" "polars-utils/nightly" "hashbrown/nightly" ];
+          "parquet" = [ "polars-plan/parquet" "polars-io/parquet" "polars-io/async" ];
+          "test" = [ "polars-core/chunked_ids" ];
+          "tokio" = [ "dep:tokio" ];
+        };
+        resolvedDefaultFeatures = [ "cross_join" "dtype-categorical" "dtype-i16" "dtype-i8" "parquet" ];
+      };
+      "polars-plan" = rec {
+        crateName = "polars-plan";
+        version = "0.36.2";
+        edition = "2021";
+        sha256 = "1l5ca38v7ksq4595wdr6wsd74pdgszwivq9y8wfc6l6h4ib1fjiq";
+        authors = [
+          "Ritchie Vink <ritchie46@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "ahash";
+            packageId = "ahash";
+          }
+          {
+            name = "bytemuck";
+            packageId = "bytemuck";
+            features = [ "derive" "extern_crate_alloc" ];
+          }
+          {
+            name = "once_cell";
+            packageId = "once_cell";
+          }
+          {
+            name = "percent-encoding";
+            packageId = "percent-encoding";
+          }
+          {
+            name = "polars-arrow";
+            packageId = "polars-arrow";
+            rename = "arrow";
+            usesDefaultFeatures = false;
+            features = [ "compute_aggregate" "compute_arithmetics" "compute_boolean" "compute_boolean_kleene" "compute_cast" "compute_comparison" "compute_concatenate" "compute_filter" "compute_if_then_else" ];
+          }
+          {
+            name = "polars-core";
+            packageId = "polars-core";
+            usesDefaultFeatures = false;
+            features = [ "lazy" "zip_with" "random" ];
+          }
+          {
+            name = "polars-io";
+            packageId = "polars-io";
+            usesDefaultFeatures = false;
+            features = [ "lazy" ];
+          }
+          {
+            name = "polars-ops";
+            packageId = "polars-ops";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-parquet";
+            packageId = "polars-parquet";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-time";
+            packageId = "polars-time";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-utils";
+            packageId = "polars-utils";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "rayon";
+            packageId = "rayon";
+          }
+          {
+            name = "regex";
+            packageId = "regex";
+            optional = true;
+          }
+          {
+            name = "smartstring";
+            packageId = "smartstring";
+          }
+          {
+            name = "strum_macros";
+            packageId = "strum_macros";
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "version_check";
+            packageId = "version_check";
+          }
+        ];
+        features = {
+          "abs" = [ "polars-ops/abs" ];
+          "approx_unique" = [ "polars-ops/approx_unique" ];
+          "array_any_all" = [ "polars-ops/array_any_all" "dtype-array" ];
+          "asof_join" = [ "polars-core/asof_join" "polars-time" "polars-ops/asof_join" ];
+          "async" = [ "polars-io/async" ];
+          "bigidx" = [ "polars-core/bigidx" ];
+          "binary_encoding" = [ "polars-ops/binary_encoding" ];
+          "chrono" = [ "dep:chrono" ];
+          "chrono-tz" = [ "dep:chrono-tz" ];
+          "chunked_ids" = [ "polars-core/chunked_ids" ];
+          "ciborium" = [ "dep:ciborium" ];
+          "cloud" = [ "async" "polars-io/cloud" ];
+          "cov" = [ "polars-ops/cov" ];
+          "cross_join" = [ "polars-ops/cross_join" ];
+          "csv" = [ "polars-io/csv" ];
+          "cum_agg" = [ "polars-ops/cum_agg" ];
+          "cutqcut" = [ "polars-ops/cutqcut" ];
+          "date_offset" = [ "polars-time" "chrono" ];
+          "diff" = [ "polars-ops/diff" ];
+          "dtype-array" = [ "polars-core/dtype-array" "polars-ops/dtype-array" ];
+          "dtype-categorical" = [ "polars-core/dtype-categorical" ];
+          "dtype-date" = [ "polars-core/dtype-date" "polars-time/dtype-date" "temporal" ];
+          "dtype-datetime" = [ "polars-core/dtype-datetime" "polars-time/dtype-datetime" "temporal" ];
+          "dtype-decimal" = [ "polars-core/dtype-decimal" ];
+          "dtype-duration" = [ "polars-core/dtype-duration" "polars-time/dtype-duration" "temporal" ];
+          "dtype-i16" = [ "polars-core/dtype-i16" ];
+          "dtype-i8" = [ "polars-core/dtype-i8" ];
+          "dtype-struct" = [ "polars-core/dtype-struct" ];
+          "dtype-time" = [ "polars-core/dtype-time" "polars-time/dtype-time" ];
+          "dtype-u16" = [ "polars-core/dtype-u16" ];
+          "dtype-u8" = [ "polars-core/dtype-u8" ];
+          "dynamic_group_by" = [ "polars-core/dynamic_group_by" ];
+          "ewma" = [ "polars-ops/ewma" ];
+          "extract_groups" = [ "regex" "dtype-struct" "polars-ops/extract_groups" ];
+          "extract_jsonpath" = [ "polars-ops/extract_jsonpath" ];
+          "ffi_plugin" = [ "libloading" "polars-ffi" ];
+          "find_many" = [ "polars-ops/find_many" ];
+          "fmt" = [ "polars-core/fmt" ];
+          "fused" = [ "polars-ops/fused" ];
+          "futures" = [ "dep:futures" ];
+          "hist" = [ "polars-ops/hist" ];
+          "interpolate" = [ "polars-ops/interpolate" ];
+          "ipc" = [ "polars-io/ipc" ];
+          "is_first_distinct" = [ "polars-core/is_first_distinct" "polars-ops/is_first_distinct" ];
+          "is_in" = [ "polars-ops/is_in" ];
+          "is_last_distinct" = [ "polars-core/is_last_distinct" "polars-ops/is_last_distinct" ];
+          "is_unique" = [ "polars-ops/is_unique" ];
+          "json" = [ "polars-io/json" "polars-json" ];
+          "libloading" = [ "dep:libloading" ];
+          "list_any_all" = [ "polars-ops/list_any_all" ];
+          "list_count" = [ "polars-ops/list_count" ];
+          "list_drop_nulls" = [ "polars-ops/list_drop_nulls" ];
+          "list_gather" = [ "polars-ops/list_gather" ];
+          "list_sample" = [ "polars-ops/list_sample" ];
+          "list_sets" = [ "polars-ops/list_sets" ];
+          "list_to_struct" = [ "polars-ops/list_to_struct" ];
+          "log" = [ "polars-ops/log" ];
+          "merge_sorted" = [ "polars-ops/merge_sorted" ];
+          "mode" = [ "polars-ops/mode" ];
+          "moment" = [ "polars-ops/moment" ];
+          "nightly" = [ "polars-utils/nightly" "polars-ops/nightly" ];
+          "object" = [ "polars-core/object" ];
+          "parquet" = [ "polars-io/parquet" "polars-parquet" ];
+          "pct_change" = [ "polars-ops/pct_change" ];
+          "peaks" = [ "polars-ops/peaks" ];
+          "pivot" = [ "polars-core/rows" "polars-ops/pivot" ];
+          "polars-ffi" = [ "dep:polars-ffi" ];
+          "polars-json" = [ "dep:polars-json" ];
+          "polars-parquet" = [ "dep:polars-parquet" ];
+          "polars-time" = [ "dep:polars-time" ];
+          "propagate_nans" = [ "polars-ops/propagate_nans" ];
+          "python" = [ "dep:pyo3" "ciborium" ];
+          "random" = [ "polars-core/random" ];
+          "rank" = [ "polars-ops/rank" ];
+          "regex" = [ "dep:regex" ];
+          "repeat_by" = [ "polars-ops/repeat_by" ];
+          "replace" = [ "polars-ops/replace" ];
+          "rle" = [ "polars-ops/rle" ];
+          "rolling_window" = [ "polars-core/rolling_window" "polars-time/rolling_window" "polars-ops/rolling_window" "polars-time/rolling_window" ];
+          "round_series" = [ "polars-ops/round_series" ];
+          "row_hash" = [ "polars-core/row_hash" "polars-ops/hash" ];
+          "search_sorted" = [ "polars-ops/search_sorted" ];
+          "semi_anti_join" = [ "polars-ops/semi_anti_join" ];
+          "serde" = [ "dep:serde" "polars-core/serde-lazy" "polars-time/serde" "polars-io/serde" "polars-ops/serde" ];
+          "string_encoding" = [ "polars-ops/string_encoding" ];
+          "string_pad" = [ "polars-ops/string_pad" ];
+          "string_reverse" = [ "polars-ops/string_reverse" ];
+          "string_to_integer" = [ "polars-ops/string_to_integer" ];
+          "strings" = [ "polars-core/strings" "polars-ops/strings" ];
+          "temporal" = [ "polars-core/temporal" "dtype-date" "dtype-datetime" "dtype-time" "dtype-i8" "dtype-i16" ];
+          "timezones" = [ "chrono-tz" "polars-time/timezones" "polars-core/timezones" "regex" ];
+          "top_k" = [ "polars-ops/top_k" ];
+          "unique_counts" = [ "polars-ops/unique_counts" ];
+        };
+        resolvedDefaultFeatures = [ "abs" "cross_join" "cum_agg" "dtype-categorical" "dtype-date" "dtype-datetime" "dtype-duration" "dtype-i16" "dtype-i8" "dtype-time" "is_in" "log" "meta" "parquet" "polars-parquet" "polars-time" "regex" "round_series" "strings" "temporal" "trigonometry" ];
+      };
+      "polars-row" = rec {
+        crateName = "polars-row";
+        version = "0.36.2";
+        edition = "2021";
+        sha256 = "0by7x6jlj5dwr3f1gj49v8w65l3kpqhwhzb9qzlv6gdqrdx2ycij";
+        authors = [
+          "Ritchie Vink <ritchie46@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "polars-arrow";
+            packageId = "polars-arrow";
+            rename = "arrow";
+            usesDefaultFeatures = false;
+            features = [ "compute_aggregate" "compute_arithmetics" "compute_boolean" "compute_boolean_kleene" "compute_cast" "compute_comparison" "compute_concatenate" "compute_filter" "compute_if_then_else" ];
+          }
+          {
+            name = "polars-error";
+            packageId = "polars-error";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-utils";
+            packageId = "polars-utils";
+            usesDefaultFeatures = false;
+          }
+        ];
+
+      };
+      "polars-sql" = rec {
+        crateName = "polars-sql";
+        version = "0.36.2";
+        edition = "2021";
+        sha256 = "1dcmm993gycw75a6c5hxcr6h2cy6fa2r3hsbx19h9zgxvxnlq2wz";
+        authors = [
+          "Ritchie Vink <ritchie46@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "polars-arrow";
+            packageId = "polars-arrow";
+            rename = "arrow";
+            usesDefaultFeatures = false;
+            features = [ "compute_aggregate" "compute_arithmetics" "compute_boolean" "compute_boolean_kleene" "compute_cast" "compute_comparison" "compute_concatenate" "compute_filter" "compute_if_then_else" ];
+          }
+          {
+            name = "polars-core";
+            packageId = "polars-core";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-error";
+            packageId = "polars-error";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-lazy";
+            packageId = "polars-lazy";
+            usesDefaultFeatures = false;
+            features = [ "strings" "cross_join" "trigonometry" "abs" "round_series" "log" "regex" "is_in" "meta" "cum_agg" "dtype-date" ];
+          }
+          {
+            name = "polars-plan";
+            packageId = "polars-plan";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "rand";
+            packageId = "rand";
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+          }
+          {
+            name = "serde_json";
+            packageId = "serde_json";
+          }
+          {
+            name = "sqlparser";
+            packageId = "sqlparser";
+          }
+        ];
+        features = {
+          "csv" = [ "polars-lazy/csv" ];
+          "diagonal_concat" = [ "polars-lazy/diagonal_concat" ];
+          "ipc" = [ "polars-lazy/ipc" ];
+          "json" = [ "polars-lazy/json" ];
+          "parquet" = [ "polars-lazy/parquet" ];
+          "semi_anti_join" = [ "polars-lazy/semi_anti_join" ];
+        };
+        resolvedDefaultFeatures = [ "parquet" ];
+      };
+      "polars-time" = rec {
+        crateName = "polars-time";
+        version = "0.36.2";
+        edition = "2021";
+        sha256 = "1l24fmpv5v1qshxfd4592z8x6bnjlwy4njhf9rcbdlbbr6gn9qny";
+        authors = [
+          "Ritchie Vink <ritchie46@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "atoi";
+            packageId = "atoi";
+          }
+          {
+            name = "chrono";
+            packageId = "chrono";
+            usesDefaultFeatures = false;
+            features = [ "std" ];
+          }
+          {
+            name = "now";
+            packageId = "now";
+          }
+          {
+            name = "once_cell";
+            packageId = "once_cell";
+          }
+          {
+            name = "polars-arrow";
+            packageId = "polars-arrow";
+            rename = "arrow";
+            usesDefaultFeatures = false;
+            features = [ "compute_aggregate" "compute_arithmetics" "compute_boolean" "compute_boolean_kleene" "compute_cast" "compute_comparison" "compute_concatenate" "compute_filter" "compute_if_then_else" "compute" "temporal" ];
+          }
+          {
+            name = "polars-core";
+            packageId = "polars-core";
+            usesDefaultFeatures = false;
+            features = [ "dtype-datetime" "dtype-duration" "dtype-time" "dtype-date" ];
+          }
+          {
+            name = "polars-error";
+            packageId = "polars-error";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-ops";
+            packageId = "polars-ops";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-utils";
+            packageId = "polars-utils";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "regex";
+            packageId = "regex";
+          }
+          {
+            name = "smartstring";
+            packageId = "smartstring";
+          }
+        ];
+        features = {
+          "chrono-tz" = [ "dep:chrono-tz" ];
+          "dtype-date" = [ "polars-core/dtype-date" "polars-core/temporal" ];
+          "dtype-datetime" = [ "polars-core/dtype-date" "polars-core/temporal" ];
+          "dtype-duration" = [ "polars-core/dtype-duration" "polars-core/temporal" ];
+          "dtype-time" = [ "polars-core/dtype-time" "polars-core/temporal" ];
+          "fmt" = [ "polars-core/fmt" ];
+          "rolling_window" = [ "polars-core/rolling_window" "dtype-duration" ];
+          "serde" = [ "dep:serde" ];
+          "test" = [ "dtype-date" "dtype-datetime" "polars-core/fmt" ];
+          "timezones" = [ "chrono-tz" "dtype-datetime" "polars-core/timezones" "arrow/timezones" "polars-ops/timezones" ];
+        };
+        resolvedDefaultFeatures = [ "dtype-date" "dtype-datetime" "dtype-duration" "dtype-time" ];
+      };
+      "polars-utils" = rec {
+        crateName = "polars-utils";
+        version = "0.36.2";
+        edition = "2021";
+        sha256 = "1nmvfqwyzbaxcw457amspz479y5vcnpalq043awxfixdfx5clx5i";
+        authors = [
+          "Ritchie Vink <ritchie46@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "ahash";
+            packageId = "ahash";
+          }
+          {
+            name = "bytemuck";
+            packageId = "bytemuck";
+            features = [ "derive" "extern_crate_alloc" ];
+          }
+          {
+            name = "hashbrown";
+            packageId = "hashbrown";
+            features = [ "rayon" "ahash" ];
+          }
+          {
+            name = "indexmap";
+            packageId = "indexmap";
+            features = [ "std" ];
+          }
+          {
+            name = "num-traits";
+            packageId = "num-traits";
+          }
+          {
+            name = "once_cell";
+            packageId = "once_cell";
+          }
+          {
+            name = "polars-error";
+            packageId = "polars-error";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "rayon";
+            packageId = "rayon";
+          }
+          {
+            name = "smartstring";
+            packageId = "smartstring";
+          }
+          {
+            name = "sysinfo";
+            packageId = "sysinfo";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "version_check";
+            packageId = "version_check";
+          }
+        ];
+        features = {
+          "sysinfo" = [ "dep:sysinfo" ];
+        };
+        resolvedDefaultFeatures = [ "sysinfo" ];
+      };
+      "ppv-lite86" = rec {
+        crateName = "ppv-lite86";
+        version = "0.2.17";
+        edition = "2018";
+        sha256 = "1pp6g52aw970adv3x2310n7glqnji96z0a9wiamzw89ibf0ayh2v";
+        authors = [
+          "The CryptoCorrosion Contributors"
+        ];
+        features = {
+          "default" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "simd" "std" ];
+      };
+      "proc-macro2" = rec {
+        crateName = "proc-macro2";
+        version = "1.0.69";
+        edition = "2021";
+        sha256 = "1nljgyllbm3yr3pa081bf83gxh6l4zvjqzaldw7v4mj9xfgihk0k";
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+          "Alex Crichton <alex@alexcrichton.com>"
+        ];
+        dependencies = [
+          {
+            name = "unicode-ident";
+            packageId = "unicode-ident";
+          }
+        ];
+        features = {
+          "default" = [ "proc-macro" ];
+        };
+        resolvedDefaultFeatures = [ "default" "proc-macro" ];
+      };
+      "quote" = rec {
+        crateName = "quote";
+        version = "1.0.33";
+        edition = "2018";
+        sha256 = "1biw54hbbr12wdwjac55z1m2x2rylciw83qnjn564a3096jgqrsj";
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "default" = [ "proc-macro" ];
+          "proc-macro" = [ "proc-macro2/proc-macro" ];
+        };
+        resolvedDefaultFeatures = [ "default" "proc-macro" ];
+      };
+      "rand" = rec {
+        crateName = "rand";
+        version = "0.8.5";
+        edition = "2018";
+        sha256 = "013l6931nn7gkc23jz5mm3qdhf93jjf0fg64nz2lp4i51qd8vbrl";
+        authors = [
+          "The Rand Project Developers"
+          "The Rust Project Developers"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+            optional = true;
+            usesDefaultFeatures = false;
+            target = { target, features }: (target."unix" or false);
+          }
+          {
+            name = "rand_chacha";
+            packageId = "rand_chacha";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "rand_core";
+            packageId = "rand_core";
+          }
+        ];
+        features = {
+          "alloc" = [ "rand_core/alloc" ];
+          "default" = [ "std" "std_rng" ];
+          "getrandom" = [ "rand_core/getrandom" ];
+          "libc" = [ "dep:libc" ];
+          "log" = [ "dep:log" ];
+          "packed_simd" = [ "dep:packed_simd" ];
+          "rand_chacha" = [ "dep:rand_chacha" ];
+          "serde" = [ "dep:serde" ];
+          "serde1" = [ "serde" "rand_core/serde1" ];
+          "simd_support" = [ "packed_simd" ];
+          "std" = [ "rand_core/std" "rand_chacha/std" "alloc" "getrandom" "libc" ];
+          "std_rng" = [ "rand_chacha" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "getrandom" "libc" "rand_chacha" "small_rng" "std" "std_rng" ];
+      };
+      "rand_chacha" = rec {
+        crateName = "rand_chacha";
+        version = "0.3.1";
+        edition = "2018";
+        sha256 = "123x2adin558xbhvqb8w4f6syjsdkmqff8cxwhmjacpsl1ihmhg6";
+        authors = [
+          "The Rand Project Developers"
+          "The Rust Project Developers"
+          "The CryptoCorrosion Contributors"
+        ];
+        dependencies = [
+          {
+            name = "ppv-lite86";
+            packageId = "ppv-lite86";
+            usesDefaultFeatures = false;
+            features = [ "simd" ];
+          }
+          {
+            name = "rand_core";
+            packageId = "rand_core";
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "serde" = [ "dep:serde" ];
+          "serde1" = [ "serde" ];
+          "std" = [ "ppv-lite86/std" ];
+        };
+        resolvedDefaultFeatures = [ "std" ];
+      };
+      "rand_core" = rec {
+        crateName = "rand_core";
+        version = "0.6.4";
+        edition = "2018";
+        sha256 = "0b4j2v4cb5krak1pv6kakv4sz6xcwbrmy2zckc32hsigbrwy82zc";
+        authors = [
+          "The Rand Project Developers"
+          "The Rust Project Developers"
+        ];
+        dependencies = [
+          {
+            name = "getrandom";
+            packageId = "getrandom";
+            optional = true;
+          }
+        ];
+        features = {
+          "getrandom" = [ "dep:getrandom" ];
+          "serde" = [ "dep:serde" ];
+          "serde1" = [ "serde" ];
+          "std" = [ "alloc" "getrandom" "getrandom/std" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "getrandom" "std" ];
+      };
+      "rand_distr" = rec {
+        crateName = "rand_distr";
+        version = "0.4.3";
+        edition = "2018";
+        sha256 = "0cgfwg3z0pkqhrl0x90c77kx70r6g9z4m6fxq9v0h2ibr2dhpjrj";
+        authors = [
+          "The Rand Project Developers"
+        ];
+        dependencies = [
+          {
+            name = "num-traits";
+            packageId = "num-traits";
+            usesDefaultFeatures = false;
+            features = [ "libm" ];
+          }
+          {
+            name = "rand";
+            packageId = "rand";
+            usesDefaultFeatures = false;
+          }
+        ];
+        devDependencies = [
+          {
+            name = "rand";
+            packageId = "rand";
+            usesDefaultFeatures = false;
+            features = [ "std_rng" "std" "small_rng" ];
+          }
+        ];
+        features = {
+          "alloc" = [ "rand/alloc" ];
+          "default" = [ "std" ];
+          "serde" = [ "dep:serde" ];
+          "serde1" = [ "serde" "rand/serde1" ];
+          "std" = [ "alloc" "rand/std" ];
+          "std_math" = [ "num-traits/std" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "std" ];
+      };
+      "rayon" = rec {
+        crateName = "rayon";
+        version = "1.8.0";
+        edition = "2021";
+        sha256 = "1cfdnvchf7j4cpha5jkcrrsr61li9i9lp5ak7xdq6d3pvc1xn9ww";
+        authors = [
+          "Niko Matsakis <niko@alum.mit.edu>"
+          "Josh Stone <cuviper@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "either";
+            packageId = "either";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "rayon-core";
+            packageId = "rayon-core";
+          }
+        ];
+
+      };
+      "rayon-core" = rec {
+        crateName = "rayon-core";
+        version = "1.12.0";
+        edition = "2021";
+        links = "rayon-core";
+        sha256 = "1vaq0q71yfvcwlmia0iqf6ixj2fibjcf2xjy92n1m1izv1mgpqsw";
+        authors = [
+          "Niko Matsakis <niko@alum.mit.edu>"
+          "Josh Stone <cuviper@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "crossbeam-deque";
+            packageId = "crossbeam-deque";
+          }
+          {
+            name = "crossbeam-utils";
+            packageId = "crossbeam-utils";
+          }
+        ];
+
+      };
+      "redox_syscall" = rec {
+        crateName = "redox_syscall";
+        version = "0.4.1";
+        edition = "2018";
+        sha256 = "1aiifyz5dnybfvkk4cdab9p2kmphag1yad6iknc7aszlxxldf8j7";
+        libName = "syscall";
+        authors = [
+          "Jeremy Soller <jackpot51@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "bitflags";
+            packageId = "bitflags 1.3.2";
+          }
+        ];
+        features = {
+          "core" = [ "dep:core" ];
+          "rustc-dep-of-std" = [ "core" "bitflags/rustc-dep-of-std" ];
+        };
+      };
+      "regex" = rec {
+        crateName = "regex";
+        version = "1.10.2";
+        edition = "2021";
+        sha256 = "0hxkd814n4irind8im5c9am221ri6bprx49nc7yxv02ykhd9a2rq";
+        authors = [
+          "The Rust Project Developers"
+          "Andrew Gallant <jamslam@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "aho-corasick";
+            packageId = "aho-corasick";
+            optional = true;
+          }
+          {
+            name = "memchr";
+            packageId = "memchr";
+            optional = true;
+          }
+          {
+            name = "regex-automata";
+            packageId = "regex-automata";
+            usesDefaultFeatures = false;
+            features = [ "alloc" "syntax" "meta" "nfa-pikevm" ];
+          }
+          {
+            name = "regex-syntax";
+            packageId = "regex-syntax";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "default" = [ "std" "perf" "unicode" "regex-syntax/default" ];
+          "logging" = [ "aho-corasick?/logging" "memchr?/logging" "regex-automata/logging" ];
+          "perf" = [ "perf-cache" "perf-dfa" "perf-onepass" "perf-backtrack" "perf-inline" "perf-literal" ];
+          "perf-backtrack" = [ "regex-automata/nfa-backtrack" ];
+          "perf-dfa" = [ "regex-automata/hybrid" ];
+          "perf-dfa-full" = [ "regex-automata/dfa-build" "regex-automata/dfa-search" ];
+          "perf-inline" = [ "regex-automata/perf-inline" ];
+          "perf-literal" = [ "dep:aho-corasick" "dep:memchr" "regex-automata/perf-literal" ];
+          "perf-onepass" = [ "regex-automata/dfa-onepass" ];
+          "std" = [ "aho-corasick?/std" "memchr?/std" "regex-automata/std" "regex-syntax/std" ];
+          "unicode" = [ "unicode-age" "unicode-bool" "unicode-case" "unicode-gencat" "unicode-perl" "unicode-script" "unicode-segment" "regex-automata/unicode" "regex-syntax/unicode" ];
+          "unicode-age" = [ "regex-automata/unicode-age" "regex-syntax/unicode-age" ];
+          "unicode-bool" = [ "regex-automata/unicode-bool" "regex-syntax/unicode-bool" ];
+          "unicode-case" = [ "regex-automata/unicode-case" "regex-syntax/unicode-case" ];
+          "unicode-gencat" = [ "regex-automata/unicode-gencat" "regex-syntax/unicode-gencat" ];
+          "unicode-perl" = [ "regex-automata/unicode-perl" "regex-automata/unicode-word-boundary" "regex-syntax/unicode-perl" ];
+          "unicode-script" = [ "regex-automata/unicode-script" "regex-syntax/unicode-script" ];
+          "unicode-segment" = [ "regex-automata/unicode-segment" "regex-syntax/unicode-segment" ];
+          "unstable" = [ "pattern" ];
+          "use_std" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "perf" "perf-backtrack" "perf-cache" "perf-dfa" "perf-inline" "perf-literal" "perf-onepass" "std" "unicode" "unicode-age" "unicode-bool" "unicode-case" "unicode-gencat" "unicode-perl" "unicode-script" "unicode-segment" ];
+      };
+      "regex-automata" = rec {
+        crateName = "regex-automata";
+        version = "0.4.3";
+        edition = "2021";
+        sha256 = "0gs8q9yhd3kcg4pr00ag4viqxnh5l7jpyb9fsfr8hzh451w4r02z";
+        authors = [
+          "The Rust Project Developers"
+          "Andrew Gallant <jamslam@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "aho-corasick";
+            packageId = "aho-corasick";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "memchr";
+            packageId = "memchr";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "regex-syntax";
+            packageId = "regex-syntax";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "default" = [ "std" "syntax" "perf" "unicode" "meta" "nfa" "dfa" "hybrid" ];
+          "dfa" = [ "dfa-build" "dfa-search" "dfa-onepass" ];
+          "dfa-build" = [ "nfa-thompson" "dfa-search" ];
+          "dfa-onepass" = [ "nfa-thompson" ];
+          "hybrid" = [ "alloc" "nfa-thompson" ];
+          "internal-instrument" = [ "internal-instrument-pikevm" ];
+          "internal-instrument-pikevm" = [ "logging" "std" ];
+          "logging" = [ "dep:log" "aho-corasick?/logging" "memchr?/logging" ];
+          "meta" = [ "syntax" "nfa-pikevm" ];
+          "nfa" = [ "nfa-thompson" "nfa-pikevm" "nfa-backtrack" ];
+          "nfa-backtrack" = [ "nfa-thompson" ];
+          "nfa-pikevm" = [ "nfa-thompson" ];
+          "nfa-thompson" = [ "alloc" ];
+          "perf" = [ "perf-inline" "perf-literal" ];
+          "perf-literal" = [ "perf-literal-substring" "perf-literal-multisubstring" ];
+          "perf-literal-multisubstring" = [ "std" "dep:aho-corasick" ];
+          "perf-literal-substring" = [ "aho-corasick?/perf-literal" "dep:memchr" ];
+          "std" = [ "regex-syntax?/std" "memchr?/std" "aho-corasick?/std" "alloc" ];
+          "syntax" = [ "dep:regex-syntax" "alloc" ];
+          "unicode" = [ "unicode-age" "unicode-bool" "unicode-case" "unicode-gencat" "unicode-perl" "unicode-script" "unicode-segment" "unicode-word-boundary" "regex-syntax?/unicode" ];
+          "unicode-age" = [ "regex-syntax?/unicode-age" ];
+          "unicode-bool" = [ "regex-syntax?/unicode-bool" ];
+          "unicode-case" = [ "regex-syntax?/unicode-case" ];
+          "unicode-gencat" = [ "regex-syntax?/unicode-gencat" ];
+          "unicode-perl" = [ "regex-syntax?/unicode-perl" ];
+          "unicode-script" = [ "regex-syntax?/unicode-script" ];
+          "unicode-segment" = [ "regex-syntax?/unicode-segment" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "dfa-onepass" "dfa-search" "hybrid" "meta" "nfa-backtrack" "nfa-pikevm" "nfa-thompson" "perf-inline" "perf-literal" "perf-literal-multisubstring" "perf-literal-substring" "std" "syntax" "unicode" "unicode-age" "unicode-bool" "unicode-case" "unicode-gencat" "unicode-perl" "unicode-script" "unicode-segment" "unicode-word-boundary" ];
+      };
+      "regex-syntax" = rec {
+        crateName = "regex-syntax";
+        version = "0.8.2";
+        edition = "2021";
+        sha256 = "17rd2s8xbiyf6lb4aj2nfi44zqlj98g2ays8zzj2vfs743k79360";
+        authors = [
+          "The Rust Project Developers"
+          "Andrew Gallant <jamslam@gmail.com>"
+        ];
+        features = {
+          "arbitrary" = [ "dep:arbitrary" ];
+          "default" = [ "std" "unicode" ];
+          "unicode" = [ "unicode-age" "unicode-bool" "unicode-case" "unicode-gencat" "unicode-perl" "unicode-script" "unicode-segment" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" "unicode" "unicode-age" "unicode-bool" "unicode-case" "unicode-gencat" "unicode-perl" "unicode-script" "unicode-segment" ];
+      };
+      "rustc-demangle" = rec {
+        crateName = "rustc-demangle";
+        version = "0.1.23";
+        edition = "2015";
+        sha256 = "0xnbk2bmyzshacjm2g1kd4zzv2y2az14bw3sjccq5qkpmsfvn9nn";
+        authors = [
+          "Alex Crichton <alex@alexcrichton.com>"
+        ];
+        features = {
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "core" = [ "dep:core" ];
+          "rustc-dep-of-std" = [ "core" "compiler_builtins" ];
+        };
+      };
+      "rustc_version" = rec {
+        crateName = "rustc_version";
+        version = "0.4.0";
+        edition = "2018";
+        sha256 = "0rpk9rcdk405xhbmgclsh4pai0svn49x35aggl4nhbkd4a2zb85z";
+        authors = [
+          "Dirkjan Ochtman <dirkjan@ochtman.nl>"
+          "Marvin Löbel <loebel.marvin@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "semver";
+            packageId = "semver";
+          }
+        ];
+
+      };
+      "rustix" = rec {
+        crateName = "rustix";
+        version = "0.38.21";
+        edition = "2021";
+        sha256 = "18q2mx7gnnl1238psb1r0avdw00l8y0jxkxgimyhmmg50q2nnhib";
+        authors = [
+          "Dan Gohman <dev@sunfishcode.online>"
+          "Jakub Konka <kubkon@jakubkonka.com>"
+        ];
+        dependencies = [
+          {
+            name = "bitflags";
+            packageId = "bitflags 2.4.1";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "errno";
+            packageId = "errno";
+            rename = "libc_errno";
+            optional = true;
+            usesDefaultFeatures = false;
+            target = { target, features }: ((!(target."rustix_use_libc" or false)) && (!(target."miri" or false)) && ("linux" == target."os" or null) && ("little" == target."endian" or null) && (("arm" == target."arch" or null) || (("aarch64" == target."arch" or null) && ("64" == target."pointer_width" or null)) || ("riscv64" == target."arch" or null) || ((target."rustix_use_experimental_asm" or false) && ("powerpc64" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips32r6" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips64" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips64r6" == target."arch" or null)) || ("x86" == target."arch" or null) || (("x86_64" == target."arch" or null) && ("64" == target."pointer_width" or null))));
+          }
+          {
+            name = "errno";
+            packageId = "errno";
+            rename = "libc_errno";
+            usesDefaultFeatures = false;
+            target = { target, features }: ((!(target."windows" or false)) && ((target."rustix_use_libc" or false) || (target."miri" or false) || (!(("linux" == target."os" or null) && ("little" == target."endian" or null) && (("arm" == target."arch" or null) || (("aarch64" == target."arch" or null) && ("64" == target."pointer_width" or null)) || ("riscv64" == target."arch" or null) || ((target."rustix_use_experimental_asm" or false) && ("powerpc64" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips32r6" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips64" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips64r6" == target."arch" or null)) || ("x86" == target."arch" or null) || (("x86_64" == target."arch" or null) && ("64" == target."pointer_width" or null)))))));
+          }
+          {
+            name = "errno";
+            packageId = "errno";
+            rename = "libc_errno";
+            usesDefaultFeatures = false;
+            target = { target, features }: (target."windows" or false);
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            optional = true;
+            usesDefaultFeatures = false;
+            target = { target, features }: ((!(target."rustix_use_libc" or false)) && (!(target."miri" or false)) && ("linux" == target."os" or null) && ("little" == target."endian" or null) && (("arm" == target."arch" or null) || (("aarch64" == target."arch" or null) && ("64" == target."pointer_width" or null)) || ("riscv64" == target."arch" or null) || ((target."rustix_use_experimental_asm" or false) && ("powerpc64" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips32r6" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips64" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips64r6" == target."arch" or null)) || ("x86" == target."arch" or null) || (("x86_64" == target."arch" or null) && ("64" == target."pointer_width" or null))));
+            features = [ "extra_traits" ];
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            usesDefaultFeatures = false;
+            target = { target, features }: ((!(target."windows" or false)) && ((target."rustix_use_libc" or false) || (target."miri" or false) || (!(("linux" == target."os" or null) && ("little" == target."endian" or null) && (("arm" == target."arch" or null) || (("aarch64" == target."arch" or null) && ("64" == target."pointer_width" or null)) || ("riscv64" == target."arch" or null) || ((target."rustix_use_experimental_asm" or false) && ("powerpc64" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips32r6" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips64" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips64r6" == target."arch" or null)) || ("x86" == target."arch" or null) || (("x86_64" == target."arch" or null) && ("64" == target."pointer_width" or null)))))));
+            features = [ "extra_traits" ];
+          }
+          {
+            name = "linux-raw-sys";
+            packageId = "linux-raw-sys";
+            usesDefaultFeatures = false;
+            target = { target, features }: ((("android" == target."os" or null) || ("linux" == target."os" or null)) && ((target."rustix_use_libc" or false) || (target."miri" or false) || (!(("linux" == target."os" or null) && ("little" == target."endian" or null) && (("arm" == target."arch" or null) || (("aarch64" == target."arch" or null) && ("64" == target."pointer_width" or null)) || ("riscv64" == target."arch" or null) || ((target."rustix_use_experimental_asm" or false) && ("powerpc64" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips32r6" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips64" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips64r6" == target."arch" or null)) || ("x86" == target."arch" or null) || (("x86_64" == target."arch" or null) && ("64" == target."pointer_width" or null)))))));
+            features = [ "general" "ioctl" "no_std" ];
+          }
+          {
+            name = "linux-raw-sys";
+            packageId = "linux-raw-sys";
+            usesDefaultFeatures = false;
+            target = { target, features }: ((!(target."rustix_use_libc" or false)) && (!(target."miri" or false)) && ("linux" == target."os" or null) && ("little" == target."endian" or null) && (("arm" == target."arch" or null) || (("aarch64" == target."arch" or null) && ("64" == target."pointer_width" or null)) || ("riscv64" == target."arch" or null) || ((target."rustix_use_experimental_asm" or false) && ("powerpc64" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips32r6" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips64" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips64r6" == target."arch" or null)) || ("x86" == target."arch" or null) || (("x86_64" == target."arch" or null) && ("64" == target."pointer_width" or null))));
+            features = [ "general" "errno" "ioctl" "no_std" "elf" ];
+          }
+          {
+            name = "windows-sys";
+            packageId = "windows-sys";
+            target = { target, features }: (target."windows" or false);
+            features = [ "Win32_Foundation" "Win32_Networking_WinSock" "Win32_NetworkManagement_IpHelper" "Win32_System_Threading" ];
+          }
+        ];
+        devDependencies = [
+          {
+            name = "errno";
+            packageId = "errno";
+            rename = "libc_errno";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+          }
+        ];
+        features = {
+          "all-apis" = [ "event" "fs" "io_uring" "mm" "mount" "net" "param" "pipe" "process" "procfs" "pty" "rand" "runtime" "shm" "stdio" "system" "termios" "thread" "time" ];
+          "default" = [ "std" "use-libc-auxv" ];
+          "io_uring" = [ "event" "fs" "net" "linux-raw-sys/io_uring" ];
+          "itoa" = [ "dep:itoa" ];
+          "libc" = [ "dep:libc" ];
+          "libc_errno" = [ "dep:libc_errno" ];
+          "linux_latest" = [ "linux_4_11" ];
+          "net" = [ "linux-raw-sys/net" "linux-raw-sys/netlink" "linux-raw-sys/if_ether" ];
+          "once_cell" = [ "dep:once_cell" ];
+          "param" = [ "fs" ];
+          "process" = [ "linux-raw-sys/prctl" ];
+          "procfs" = [ "once_cell" "itoa" "fs" ];
+          "pty" = [ "itoa" "fs" ];
+          "runtime" = [ "linux-raw-sys/prctl" ];
+          "rustc-dep-of-std" = [ "dep:core" "dep:alloc" "dep:compiler_builtins" "linux-raw-sys/rustc-dep-of-std" "bitflags/rustc-dep-of-std" "compiler_builtins?/rustc-dep-of-std" ];
+          "shm" = [ "fs" ];
+          "std" = [ "bitflags/std" "alloc" "libc?/std" "libc_errno?/std" ];
+          "system" = [ "linux-raw-sys/system" ];
+          "thread" = [ "linux-raw-sys/prctl" ];
+          "use-libc" = [ "libc_errno" "libc" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "fs" "std" "use-libc-auxv" ];
+      };
+      "rustversion" = rec {
+        crateName = "rustversion";
+        version = "1.0.14";
+        edition = "2018";
+        sha256 = "1x1pz1yynk5xzzrazk2svmidj69jhz89dz5vrc28sixl20x1iz3z";
+        procMacro = true;
+        build = "build/build.rs";
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+
+      };
+      "ryu" = rec {
+        crateName = "ryu";
+        version = "1.0.15";
+        edition = "2018";
+        sha256 = "0hfphpn1xnpzxwj8qg916ga1lyc33lc03lnf1gb3wwpglj6wrm0s";
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        features = {
+          "no-panic" = [ "dep:no-panic" ];
+        };
+      };
+      "scopeguard" = rec {
+        crateName = "scopeguard";
+        version = "1.2.0";
+        edition = "2015";
+        sha256 = "0jcz9sd47zlsgcnm1hdw0664krxwb5gczlif4qngj2aif8vky54l";
+        authors = [
+          "bluss"
+        ];
+        features = {
+          "default" = [ "use_std" ];
+        };
+      };
+      "semver" = rec {
+        crateName = "semver";
+        version = "1.0.20";
+        edition = "2018";
+        sha256 = "140hmbfa743hbmah1zjf07s8apavhvn04204qjigjiz5w6iscvw3";
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        features = {
+          "default" = [ "std" ];
+          "serde" = [ "dep:serde" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "seq-macro" = rec {
+        crateName = "seq-macro";
+        version = "0.3.5";
+        edition = "2018";
+        sha256 = "1d50kbaslrrd0374ivx15jg57f03y5xzil1wd2ajlvajzlkbzw53";
+        procMacro = true;
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+
+      };
+      "serde" = rec {
+        crateName = "serde";
+        version = "1.0.192";
+        edition = "2018";
+        sha256 = "00ghhaabyrnr2cn504lckyqzh3fwr8k7pxnhhardr1djhj2a18mw";
+        authors = [
+          "Erick Tryzelaar <erick.tryzelaar@gmail.com>"
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "serde_derive";
+            packageId = "serde_derive";
+            optional = true;
+          }
+          {
+            name = "serde_derive";
+            packageId = "serde_derive";
+            target = { target, features }: false;
+          }
+        ];
+        devDependencies = [
+          {
+            name = "serde_derive";
+            packageId = "serde_derive";
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "derive" = [ "serde_derive" ];
+          "serde_derive" = [ "dep:serde_derive" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "derive" "serde_derive" "std" ];
+      };
+      "serde_derive" = rec {
+        crateName = "serde_derive";
+        version = "1.0.192";
+        edition = "2015";
+        sha256 = "1hgvm47ffd748sx22z1da7mgcfjmpr60gqzkff0a9yn9przj1iyn";
+        procMacro = true;
+        authors = [
+          "Erick Tryzelaar <erick.tryzelaar@gmail.com>"
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn 2.0.39";
+          }
+        ];
+        features = { };
+        resolvedDefaultFeatures = [ "default" ];
+      };
+      "serde_json" = rec {
+        crateName = "serde_json";
+        version = "1.0.108";
+        edition = "2021";
+        sha256 = "0ssj59s7lpzqh1m50kfzlnrip0p0jg9lmhn4098i33a0mhz7w71x";
+        authors = [
+          "Erick Tryzelaar <erick.tryzelaar@gmail.com>"
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "itoa";
+            packageId = "itoa";
+          }
+          {
+            name = "ryu";
+            packageId = "ryu";
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+            usesDefaultFeatures = false;
+          }
+        ];
+        devDependencies = [
+          {
+            name = "serde";
+            packageId = "serde";
+            features = [ "derive" ];
+          }
+        ];
+        features = {
+          "alloc" = [ "serde/alloc" ];
+          "default" = [ "std" ];
+          "indexmap" = [ "dep:indexmap" ];
+          "preserve_order" = [ "indexmap" "std" ];
+          "std" = [ "serde/std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "sha2" = rec {
+        crateName = "sha2";
+        version = "0.10.8";
+        edition = "2018";
+        sha256 = "1j1x78zk9il95w9iv46dh9wm73r6xrgj32y6lzzw7bxws9dbfgbr";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        dependencies = [
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "cpufeatures";
+            packageId = "cpufeatures";
+            target = { target, features }: (("aarch64" == target."arch" or null) || ("x86_64" == target."arch" or null) || ("x86" == target."arch" or null));
+          }
+          {
+            name = "digest";
+            packageId = "digest";
+          }
+        ];
+        devDependencies = [
+          {
+            name = "digest";
+            packageId = "digest";
+            features = [ "dev" ];
+          }
+        ];
+        features = {
+          "asm" = [ "sha2-asm" ];
+          "asm-aarch64" = [ "asm" ];
+          "default" = [ "std" ];
+          "oid" = [ "digest/oid" ];
+          "sha2-asm" = [ "dep:sha2-asm" ];
+          "std" = [ "digest/std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "signature" = rec {
+        crateName = "signature";
+        version = "2.2.0";
+        edition = "2021";
+        sha256 = "1pi9hd5vqfr3q3k49k37z06p7gs5si0in32qia4mmr1dancr6m3p";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        dependencies = [
+          {
+            name = "rand_core";
+            packageId = "rand_core";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "derive" = [ "dep:derive" ];
+          "digest" = [ "dep:digest" ];
+          "rand_core" = [ "dep:rand_core" ];
+          "std" = [ "alloc" "rand_core?/std" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "std" ];
+      };
+      "simdutf8" = rec {
+        crateName = "simdutf8";
+        version = "0.1.4";
+        edition = "2018";
+        sha256 = "0fi6zvnldaw7g726wnm9vvpv4s89s5jsk7fgp3rg2l99amw64zzj";
+        authors = [
+          "Hans Kratz <hans@appfour.com>"
+        ];
+        features = {
+          "default" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "slab" = rec {
+        crateName = "slab";
+        version = "0.4.9";
+        edition = "2018";
+        sha256 = "0rxvsgir0qw5lkycrqgb1cxsvxzjv9bmx73bk5y42svnzfba94lg";
+        authors = [
+          "Carl Lerche <me@carllerche.com>"
+        ];
+        buildDependencies = [
+          {
+            name = "autocfg";
+            packageId = "autocfg";
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "serde" = [ "dep:serde" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "smartstring" = rec {
+        crateName = "smartstring";
+        version = "1.0.1";
+        edition = "2021";
+        sha256 = "0agf4x0jz79r30aqibyfjm1h9hrjdh0harcqcvb2vapv7rijrdrz";
+        authors = [
+          "Bodil Stokke <bodil@bodil.org>"
+        ];
+        dependencies = [
+          {
+            name = "static_assertions";
+            packageId = "static_assertions";
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "autocfg";
+            packageId = "autocfg";
+          }
+          {
+            name = "version_check";
+            packageId = "version_check";
+          }
+        ];
+        features = {
+          "arbitrary" = [ "dep:arbitrary" ];
+          "default" = [ "std" ];
+          "proptest" = [ "dep:proptest" ];
+          "serde" = [ "dep:serde" ];
+          "test" = [ "std" "arbitrary" "arbitrary/derive" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "snap" = rec {
+        crateName = "snap";
+        version = "1.1.0";
+        edition = "2018";
+        sha256 = "0c882cs4wbyi34nw8njpxa729gyi6sj71h8rj4ykbdvyxyv0m7sy";
+        authors = [
+          "Andrew Gallant <jamslam@gmail.com>"
+        ];
+
+      };
+      "socket2" = rec {
+        crateName = "socket2";
+        version = "0.5.5";
+        edition = "2021";
+        sha256 = "1sgq315f1njky114ip7wcy83qlphv9qclprfjwvxcpfblmcsqpvv";
+        authors = [
+          "Alex Crichton <alex@alexcrichton.com>"
+          "Thomas de Zeeuw <thomasdezeeuw@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (target."unix" or false);
+          }
+          {
+            name = "windows-sys";
+            packageId = "windows-sys";
+            target = { target, features }: (target."windows" or false);
+            features = [ "Win32_Foundation" "Win32_Networking_WinSock" "Win32_System_IO" "Win32_System_Threading" "Win32_System_WindowsProgramming" ];
+          }
+        ];
+        features = { };
+        resolvedDefaultFeatures = [ "all" ];
+      };
+      "spki" = rec {
+        crateName = "spki";
+        version = "0.7.2";
+        edition = "2021";
+        sha256 = "0jhq00sv4w3psdi6li3vjjmspc6z2d9b1wc1srbljircy1p9j7lx";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        dependencies = [
+          {
+            name = "base64ct";
+            packageId = "base64ct";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "der";
+            packageId = "der";
+            features = [ "oid" ];
+          }
+        ];
+        features = {
+          "alloc" = [ "base64ct?/alloc" "der/alloc" ];
+          "arbitrary" = [ "std" "dep:arbitrary" "der/arbitrary" ];
+          "base64" = [ "dep:base64ct" ];
+          "fingerprint" = [ "sha2" ];
+          "pem" = [ "alloc" "der/pem" ];
+          "sha2" = [ "dep:sha2" ];
+          "std" = [ "der/std" "alloc" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "std" ];
+      };
+      "sqlparser" = rec {
+        crateName = "sqlparser";
+        version = "0.39.0";
+        edition = "2021";
+        sha256 = "1mrbqjdqr179qnhy43d0dnrl3yipsp4qyji5rc68j4fyrg14sfvl";
+        authors = [
+          "Andy Grove <andygrove73@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "log";
+            packageId = "log";
+          }
+        ];
+        features = {
+          "bigdecimal" = [ "dep:bigdecimal" ];
+          "default" = [ "std" ];
+          "json_example" = [ "serde_json" "serde" ];
+          "serde" = [ "dep:serde" ];
+          "serde_json" = [ "dep:serde_json" ];
+          "sqlparser_derive" = [ "dep:sqlparser_derive" ];
+          "visitor" = [ "sqlparser_derive" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "static_assertions" = rec {
+        crateName = "static_assertions";
+        version = "1.1.0";
+        edition = "2015";
+        sha256 = "0gsl6xmw10gvn3zs1rv99laj5ig7ylffnh71f9l34js4nr4r7sx2";
+        authors = [
+          "Nikolai Vazquez"
+        ];
+        features = { };
+      };
+      "streaming-decompression" = rec {
+        crateName = "streaming-decompression";
+        version = "0.1.2";
+        edition = "2018";
+        sha256 = "1wscqj3s30qknda778wf7z99mknk65p0h9hhs658l4pvkfqw6v5z";
+        dependencies = [
+          {
+            name = "fallible-streaming-iterator";
+            packageId = "fallible-streaming-iterator";
+          }
+        ];
+
+      };
+      "streaming-iterator" = rec {
+        crateName = "streaming-iterator";
+        version = "0.1.9";
+        edition = "2021";
+        sha256 = "0845zdv8qb7zwqzglpqc0830i43xh3fb6vqms155wz85qfvk28ib";
+        authors = [
+          "Steven Fackler <sfackler@gmail.com>"
+        ];
+        features = {
+          "std" = [ "alloc" ];
+        };
+      };
+      "strength_reduce" = rec {
+        crateName = "strength_reduce";
+        version = "0.2.4";
+        edition = "2015";
+        sha256 = "10jdq9dijjdkb20wg1dmwg447rnj37jbq0mwvbadvqi2gys5x2gy";
+        authors = [
+          "Elliott Mahler <join.together@gmail.com>"
+        ];
+
+      };
+      "strum_macros" = rec {
+        crateName = "strum_macros";
+        version = "0.25.3";
+        edition = "2018";
+        sha256 = "184y62g474zqb2f7n16x3ghvlyjbh50viw32p9w9l5lwmjlizp13";
+        procMacro = true;
+        authors = [
+          "Peter Glotfelty <peter.glotfelty@microsoft.com>"
+        ];
+        dependencies = [
+          {
+            name = "heck";
+            packageId = "heck";
+          }
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "rustversion";
+            packageId = "rustversion";
+          }
+          {
+            name = "syn";
+            packageId = "syn 2.0.39";
+            features = [ "parsing" "extra-traits" ];
+          }
+        ];
+
+      };
+      "subtle" = rec {
+        crateName = "subtle";
+        version = "2.5.0";
+        edition = "2018";
+        sha256 = "1g2yjs7gffgmdvkkq0wrrh0pxds3q0dv6dhkw9cdpbib656xdkc1";
+        authors = [
+          "Isis Lovecruft <isis@patternsinthevoid.net>"
+          "Henry de Valence <hdevalence@hdevalence.ca>"
+        ];
+        features = {
+          "default" = [ "std" "i128" ];
+        };
+      };
+      "syn 1.0.109" = rec {
+        crateName = "syn";
+        version = "1.0.109";
+        edition = "2018";
+        sha256 = "0ds2if4600bd59wsv7jjgfkayfzy3hnazs394kz6zdkmna8l3dkj";
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "unicode-ident";
+            packageId = "unicode-ident";
+          }
+        ];
+        features = {
+          "default" = [ "derive" "parsing" "printing" "clone-impls" "proc-macro" ];
+          "printing" = [ "quote" ];
+          "proc-macro" = [ "proc-macro2/proc-macro" "quote/proc-macro" ];
+          "quote" = [ "dep:quote" ];
+          "test" = [ "syn-test-suite/all-features" ];
+        };
+        resolvedDefaultFeatures = [ "clone-impls" "default" "derive" "extra-traits" "full" "parsing" "printing" "proc-macro" "quote" "visit-mut" ];
+      };
+      "syn 2.0.39" = rec {
+        crateName = "syn";
+        version = "2.0.39";
+        edition = "2021";
+        sha256 = "0ymyhxnk1yi4pzf72qk3lrdm9lgjwcrcwci0hhz5vx7wya88prr3";
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "unicode-ident";
+            packageId = "unicode-ident";
+          }
+        ];
+        features = {
+          "default" = [ "derive" "parsing" "printing" "clone-impls" "proc-macro" ];
+          "printing" = [ "quote" ];
+          "proc-macro" = [ "proc-macro2/proc-macro" "quote/proc-macro" ];
+          "quote" = [ "dep:quote" ];
+          "test" = [ "syn-test-suite/all-features" ];
+        };
+        resolvedDefaultFeatures = [ "clone-impls" "default" "derive" "extra-traits" "full" "parsing" "printing" "proc-macro" "quote" "visit" "visit-mut" ];
+      };
+      "sysinfo" = rec {
+        crateName = "sysinfo";
+        version = "0.30.5";
+        edition = "2018";
+        sha256 = "1clba87ndskvxddrmwysnjccm6vyr75j24p6ck48jqwgii1z7d0z";
+        authors = [
+          "Guillaume Gomez <guillaume1.gomez@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "core-foundation-sys";
+            packageId = "core-foundation-sys";
+            target = { target, features }: (("macos" == target."os" or null) || ("ios" == target."os" or null));
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (!(("unknown" == target."os" or null) || ("wasm32" == target."arch" or null)));
+          }
+          {
+            name = "ntapi";
+            packageId = "ntapi";
+            target = { target, features }: (target."windows" or false);
+          }
+          {
+            name = "once_cell";
+            packageId = "once_cell";
+            target = { target, features }: ((target."windows" or false) || ("linux" == target."os" or null) || ("android" == target."os" or null));
+          }
+          {
+            name = "windows";
+            packageId = "windows";
+            target = { target, features }: (target."windows" or false);
+            features = [ "Wdk_System_SystemInformation" "Wdk_System_SystemServices" "Wdk_System_Threading" "Win32_Foundation" "Win32_NetworkManagement_IpHelper" "Win32_NetworkManagement_NetManagement" "Win32_NetworkManagement_Ndis" "Win32_Networking_WinSock" "Win32_Security" "Win32_Security_Authentication_Identity" "Win32_Security_Authorization" "Win32_Storage_FileSystem" "Win32_System_Com" "Win32_System_Diagnostics_Debug" "Win32_System_IO" "Win32_System_Ioctl" "Win32_System_LibraryLoader" "Win32_System_Kernel" "Win32_System_Memory" "Win32_System_Ole" "Win32_System_Performance" "Win32_System_Power" "Win32_System_ProcessStatus" "Win32_System_Registry" "Win32_System_RemoteDesktop" "Win32_System_Rpc" "Win32_System_SystemInformation" "Win32_System_SystemServices" "Win32_System_Threading" "Win32_System_Variant" "Win32_System_WindowsProgramming" "Win32_System_Wmi" "Win32_UI_Shell" ];
+          }
+        ];
+        features = {
+          "apple-app-store" = [ "apple-sandbox" ];
+          "debug" = [ "libc/extra_traits" ];
+          "default" = [ "multithread" ];
+          "multithread" = [ "rayon" ];
+          "rayon" = [ "dep:rayon" ];
+          "serde" = [ "dep:serde" ];
+        };
+      };
+      "target-features" = rec {
+        crateName = "target-features";
+        version = "0.1.5";
+        edition = "2021";
+        sha256 = "1gb974chm9aj8ifkyibylxkyb5an4bf5y8dxb18pqmck698gmdfg";
+        authors = [
+          "Caleb Zulawski <caleb.zulawski@gmail.com>"
+        ];
+
+      };
+      "tempfile" = rec {
+        crateName = "tempfile";
+        version = "3.8.1";
+        edition = "2018";
+        sha256 = "1r88v07zdafzf46y63vs39rmzwl4vqd4g2c5qarz9mqa8nnavwby";
+        authors = [
+          "Steven Allen <steven@stebalien.com>"
+          "The Rust Project Developers"
+          "Ashley Mannix <ashleymannix@live.com.au>"
+          "Jason White <me@jasonwhite.io>"
+        ];
+        dependencies = [
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "fastrand";
+            packageId = "fastrand";
+          }
+          {
+            name = "redox_syscall";
+            packageId = "redox_syscall";
+            target = { target, features }: ("redox" == target."os" or null);
+          }
+          {
+            name = "rustix";
+            packageId = "rustix";
+            target = { target, features }: ((target."unix" or false) || ("wasi" == target."os" or null));
+            features = [ "fs" ];
+          }
+          {
+            name = "windows-sys";
+            packageId = "windows-sys";
+            target = { target, features }: (target."windows" or false);
+            features = [ "Win32_Storage_FileSystem" "Win32_Foundation" ];
+          }
+        ];
+        features = { };
+      };
+      "tempfile-fast" = rec {
+        crateName = "tempfile-fast";
+        version = "0.3.4";
+        edition = "2018";
+        sha256 = "1xksx1l1019k9q0az9mhqsgb14w0vm88yax30iq6178s3d9yhjx7";
+        authors = [
+          "Chris West (Faux) <git@goeswhere.com>"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (target."unix" or false);
+          }
+          {
+            name = "rand";
+            packageId = "rand";
+          }
+          {
+            name = "tempfile";
+            packageId = "tempfile";
+          }
+        ];
+
+      };
+      "thiserror" = rec {
+        crateName = "thiserror";
+        version = "1.0.50";
+        edition = "2021";
+        sha256 = "1ll2sfbrxks8jja161zh1pgm3yssr7aawdmaa2xmcwcsbh7j39zr";
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "thiserror-impl";
+            packageId = "thiserror-impl";
+          }
+        ];
+
+      };
+      "thiserror-impl" = rec {
+        crateName = "thiserror-impl";
+        version = "1.0.50";
+        edition = "2021";
+        sha256 = "1f0lmam4765sfnwr4b1n00y14vxh10g0311mkk0adr80pi02wsr6";
+        procMacro = true;
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn 2.0.39";
+          }
+        ];
+
+      };
+      "tokio" = rec {
+        crateName = "tokio";
+        version = "1.33.0";
+        edition = "2021";
+        sha256 = "0lynj8nfqziviw72qns9mjlhmnm66bsc5bivy5g5x6gp7q720f2g";
+        authors = [
+          "Tokio Contributors <team@tokio.rs>"
+        ];
+        dependencies = [
+          {
+            name = "backtrace";
+            packageId = "backtrace";
+            target = { target, features }: (target."tokio_taskdump" or false);
+          }
+          {
+            name = "bytes";
+            packageId = "bytes";
+            optional = true;
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            optional = true;
+            target = { target, features }: (target."unix" or false);
+          }
+          {
+            name = "mio";
+            packageId = "mio";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "num_cpus";
+            packageId = "num_cpus";
+            optional = true;
+          }
+          {
+            name = "pin-project-lite";
+            packageId = "pin-project-lite";
+          }
+          {
+            name = "socket2";
+            packageId = "socket2";
+            optional = true;
+            target = { target, features }: (!(builtins.elem "wasm" target."family"));
+            features = [ "all" ];
+          }
+          {
+            name = "windows-sys";
+            packageId = "windows-sys";
+            optional = true;
+            target = { target, features }: (target."windows" or false);
+          }
+        ];
+        devDependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (target."unix" or false);
+          }
+          {
+            name = "socket2";
+            packageId = "socket2";
+            target = { target, features }: (!(builtins.elem "wasm" target."family"));
+          }
+          {
+            name = "windows-sys";
+            packageId = "windows-sys";
+            target = { target, features }: (target."windows" or false);
+            features = [ "Win32_Foundation" "Win32_Security_Authorization" ];
+          }
+        ];
+        features = {
+          "bytes" = [ "dep:bytes" ];
+          "full" = [ "fs" "io-util" "io-std" "macros" "net" "parking_lot" "process" "rt" "rt-multi-thread" "signal" "sync" "time" ];
+          "io-util" = [ "bytes" ];
+          "libc" = [ "dep:libc" ];
+          "macros" = [ "tokio-macros" ];
+          "mio" = [ "dep:mio" ];
+          "net" = [ "libc" "mio/os-poll" "mio/os-ext" "mio/net" "socket2" "windows-sys/Win32_Foundation" "windows-sys/Win32_Security" "windows-sys/Win32_Storage_FileSystem" "windows-sys/Win32_System_Pipes" "windows-sys/Win32_System_SystemServices" ];
+          "num_cpus" = [ "dep:num_cpus" ];
+          "parking_lot" = [ "dep:parking_lot" ];
+          "process" = [ "bytes" "libc" "mio/os-poll" "mio/os-ext" "mio/net" "signal-hook-registry" "windows-sys/Win32_Foundation" "windows-sys/Win32_System_Threading" "windows-sys/Win32_System_WindowsProgramming" ];
+          "rt-multi-thread" = [ "num_cpus" "rt" ];
+          "signal" = [ "libc" "mio/os-poll" "mio/net" "mio/os-ext" "signal-hook-registry" "windows-sys/Win32_Foundation" "windows-sys/Win32_System_Console" ];
+          "signal-hook-registry" = [ "dep:signal-hook-registry" ];
+          "socket2" = [ "dep:socket2" ];
+          "test-util" = [ "rt" "sync" "time" ];
+          "tokio-macros" = [ "dep:tokio-macros" ];
+          "tracing" = [ "dep:tracing" ];
+          "windows-sys" = [ "dep:windows-sys" ];
+        };
+        resolvedDefaultFeatures = [ "bytes" "default" "io-util" "libc" "mio" "net" "num_cpus" "rt" "rt-multi-thread" "socket2" "sync" "time" "windows-sys" ];
+      };
+      "tokio-util" = rec {
+        crateName = "tokio-util";
+        version = "0.7.10";
+        edition = "2021";
+        sha256 = "058y6x4mf0fsqji9rfyb77qbfyc50y4pk2spqgj6xsyr693z66al";
+        authors = [
+          "Tokio Contributors <team@tokio.rs>"
+        ];
+        dependencies = [
+          {
+            name = "bytes";
+            packageId = "bytes";
+          }
+          {
+            name = "futures-core";
+            packageId = "futures-core";
+          }
+          {
+            name = "futures-sink";
+            packageId = "futures-sink";
+          }
+          {
+            name = "pin-project-lite";
+            packageId = "pin-project-lite";
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "sync" ];
+          }
+        ];
+        devDependencies = [
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "full" ];
+          }
+        ];
+        features = {
+          "__docs_rs" = [ "futures-util" ];
+          "codec" = [ "tracing" ];
+          "compat" = [ "futures-io" ];
+          "full" = [ "codec" "compat" "io-util" "time" "net" "rt" ];
+          "futures-io" = [ "dep:futures-io" ];
+          "futures-util" = [ "dep:futures-util" ];
+          "hashbrown" = [ "dep:hashbrown" ];
+          "io-util" = [ "io" "tokio/rt" "tokio/io-util" ];
+          "net" = [ "tokio/net" ];
+          "rt" = [ "tokio/rt" "tokio/sync" "futures-util" "hashbrown" ];
+          "slab" = [ "dep:slab" ];
+          "time" = [ "tokio/time" "slab" ];
+          "tracing" = [ "dep:tracing" ];
+        };
+        resolvedDefaultFeatures = [ "default" "io" "io-util" ];
+      };
+      "typenum" = rec {
+        crateName = "typenum";
+        version = "1.17.0";
+        edition = "2018";
+        sha256 = "09dqxv69m9lj9zvv6xw5vxaqx15ps0vxyy5myg33i0kbqvq0pzs2";
+        build = "build/main.rs";
+        authors = [
+          "Paho Lurie-Gregg <paho@paholg.com>"
+          "Andre Bogus <bogusandre@gmail.com>"
+        ];
+        features = {
+          "scale-info" = [ "dep:scale-info" ];
+          "scale_info" = [ "scale-info/derive" ];
+        };
+      };
+      "unicode-ident" = rec {
+        crateName = "unicode-ident";
+        version = "1.0.12";
+        edition = "2018";
+        sha256 = "0jzf1znfpb2gx8nr8mvmyqs1crnv79l57nxnbiszc7xf7ynbjm1k";
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+
+      };
+      "version_check" = rec {
+        crateName = "version_check";
+        version = "0.9.4";
+        edition = "2015";
+        sha256 = "0gs8grwdlgh0xq660d7wr80x14vxbizmd8dbp29p2pdncx8lp1s9";
+        authors = [
+          "Sergio Benitez <sb@sergio.bz>"
+        ];
+
+      };
+      "wasi" = rec {
+        crateName = "wasi";
+        version = "0.11.0+wasi-snapshot-preview1";
+        edition = "2018";
+        sha256 = "08z4hxwkpdpalxjps1ai9y7ihin26y9f476i53dv98v45gkqg3cw";
+        authors = [
+          "The Cranelift Project Developers"
+        ];
+        features = {
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "core" = [ "dep:core" ];
+          "default" = [ "std" ];
+          "rustc-dep-of-std" = [ "compiler_builtins" "core" "rustc-std-workspace-alloc" ];
+          "rustc-std-workspace-alloc" = [ "dep:rustc-std-workspace-alloc" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "wasm-bindgen" = rec {
+        crateName = "wasm-bindgen";
+        version = "0.2.88";
+        edition = "2018";
+        sha256 = "1khgsh4z9bga35mjhg41dl7523i69ffc5m8ckhqaw6ssyabc5bkx";
+        authors = [
+          "The wasm-bindgen Developers"
+        ];
+        dependencies = [
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "wasm-bindgen-macro";
+            packageId = "wasm-bindgen-macro";
+          }
+        ];
+        features = {
+          "default" = [ "spans" "std" ];
+          "enable-interning" = [ "std" ];
+          "gg-alloc" = [ "wasm-bindgen-test/gg-alloc" ];
+          "serde" = [ "dep:serde" ];
+          "serde-serialize" = [ "serde" "serde_json" "std" ];
+          "serde_json" = [ "dep:serde_json" ];
+          "spans" = [ "wasm-bindgen-macro/spans" ];
+          "strict-macro" = [ "wasm-bindgen-macro/strict-macro" ];
+          "xxx_debug_only_print_generated_code" = [ "wasm-bindgen-macro/xxx_debug_only_print_generated_code" ];
+        };
+        resolvedDefaultFeatures = [ "default" "spans" "std" ];
+      };
+      "wasm-bindgen-backend" = rec {
+        crateName = "wasm-bindgen-backend";
+        version = "0.2.88";
+        edition = "2018";
+        sha256 = "05zj8yl243rvs87rhicq2l1d6443lnm6k90khf744khf9ikg95z3";
+        authors = [
+          "The wasm-bindgen Developers"
+        ];
+        dependencies = [
+          {
+            name = "bumpalo";
+            packageId = "bumpalo";
+          }
+          {
+            name = "log";
+            packageId = "log";
+          }
+          {
+            name = "once_cell";
+            packageId = "once_cell";
+          }
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn 2.0.39";
+            features = [ "full" ];
+          }
+          {
+            name = "wasm-bindgen-shared";
+            packageId = "wasm-bindgen-shared";
+          }
+        ];
+        features = {
+          "extra-traits" = [ "syn/extra-traits" ];
+        };
+        resolvedDefaultFeatures = [ "spans" ];
+      };
+      "wasm-bindgen-macro" = rec {
+        crateName = "wasm-bindgen-macro";
+        version = "0.2.88";
+        edition = "2018";
+        sha256 = "1chn3wgw9awmvs0fpmazbqyc5rwfgy3pj7lzwczmzb887dxh2qar";
+        procMacro = true;
+        authors = [
+          "The wasm-bindgen Developers"
+        ];
+        dependencies = [
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "wasm-bindgen-macro-support";
+            packageId = "wasm-bindgen-macro-support";
+          }
+        ];
+        features = {
+          "spans" = [ "wasm-bindgen-macro-support/spans" ];
+          "strict-macro" = [ "wasm-bindgen-macro-support/strict-macro" ];
+        };
+        resolvedDefaultFeatures = [ "spans" ];
+      };
+      "wasm-bindgen-macro-support" = rec {
+        crateName = "wasm-bindgen-macro-support";
+        version = "0.2.88";
+        edition = "2018";
+        sha256 = "01rrzg3y1apqygsjz1jg0n7p831nm4kdyxmxyl85x7v6mf6kndf5";
+        authors = [
+          "The wasm-bindgen Developers"
+        ];
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn 2.0.39";
+            features = [ "visit" "full" ];
+          }
+          {
+            name = "wasm-bindgen-backend";
+            packageId = "wasm-bindgen-backend";
+          }
+          {
+            name = "wasm-bindgen-shared";
+            packageId = "wasm-bindgen-shared";
+          }
+        ];
+        features = {
+          "extra-traits" = [ "syn/extra-traits" ];
+          "spans" = [ "wasm-bindgen-backend/spans" ];
+        };
+        resolvedDefaultFeatures = [ "spans" ];
+      };
+      "wasm-bindgen-shared" = rec {
+        crateName = "wasm-bindgen-shared";
+        version = "0.2.88";
+        edition = "2018";
+        links = "wasm_bindgen";
+        sha256 = "02vmw2rzsla1qm0zgfng4kqz52xn8k54v8ads4g1macv09fnq10d";
+        authors = [
+          "The wasm-bindgen Developers"
+        ];
+
+      };
+      "winapi" = rec {
+        crateName = "winapi";
+        version = "0.3.9";
+        edition = "2015";
+        sha256 = "06gl025x418lchw1wxj64ycr7gha83m44cjr5sarhynd9xkrm0sw";
+        authors = [
+          "Peter Atashian <retep998@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "winapi-i686-pc-windows-gnu";
+            packageId = "winapi-i686-pc-windows-gnu";
+            target = { target, features }: (pkgs.rust.lib.toRustTarget stdenv.hostPlatform == "i686-pc-windows-gnu");
+          }
+          {
+            name = "winapi-x86_64-pc-windows-gnu";
+            packageId = "winapi-x86_64-pc-windows-gnu";
+            target = { target, features }: (pkgs.rust.lib.toRustTarget stdenv.hostPlatform == "x86_64-pc-windows-gnu");
+          }
+        ];
+        features = {
+          "debug" = [ "impl-debug" ];
+        };
+        resolvedDefaultFeatures = [ "cfg" "evntrace" "in6addr" "inaddr" "minwinbase" "ntsecapi" "windef" "winioctl" ];
+      };
+      "winapi-i686-pc-windows-gnu" = rec {
+        crateName = "winapi-i686-pc-windows-gnu";
+        version = "0.4.0";
+        edition = "2015";
+        sha256 = "1dmpa6mvcvzz16zg6d5vrfy4bxgg541wxrcip7cnshi06v38ffxc";
+        authors = [
+          "Peter Atashian <retep998@gmail.com>"
+        ];
+
+      };
+      "winapi-x86_64-pc-windows-gnu" = rec {
+        crateName = "winapi-x86_64-pc-windows-gnu";
+        version = "0.4.0";
+        edition = "2015";
+        sha256 = "0gqq64czqb64kskjryj8isp62m2sgvx25yyj3kpc2myh85w24bki";
+        authors = [
+          "Peter Atashian <retep998@gmail.com>"
+        ];
+
+      };
+      "windows" = rec {
+        crateName = "windows";
+        version = "0.52.0";
+        edition = "2021";
+        sha256 = "1gnh210qjlprpd1szaq04rjm1zqgdm9j7l9absg0kawi2rwm72p4";
+        authors = [
+          "Microsoft"
+        ];
+        dependencies = [
+          {
+            name = "windows-core";
+            packageId = "windows-core 0.52.0";
+          }
+          {
+            name = "windows-targets";
+            packageId = "windows-targets 0.52.0";
+          }
+        ];
+        features = {
+          "AI_MachineLearning" = [ "AI" ];
+          "ApplicationModel_Activation" = [ "ApplicationModel" ];
+          "ApplicationModel_AppExtensions" = [ "ApplicationModel" ];
+          "ApplicationModel_AppService" = [ "ApplicationModel" ];
+          "ApplicationModel_Appointments" = [ "ApplicationModel" ];
+          "ApplicationModel_Appointments_AppointmentsProvider" = [ "ApplicationModel_Appointments" ];
+          "ApplicationModel_Appointments_DataProvider" = [ "ApplicationModel_Appointments" ];
+          "ApplicationModel_Background" = [ "ApplicationModel" ];
+          "ApplicationModel_Calls" = [ "ApplicationModel" ];
+          "ApplicationModel_Calls_Background" = [ "ApplicationModel_Calls" ];
+          "ApplicationModel_Calls_Provider" = [ "ApplicationModel_Calls" ];
+          "ApplicationModel_Chat" = [ "ApplicationModel" ];
+          "ApplicationModel_CommunicationBlocking" = [ "ApplicationModel" ];
+          "ApplicationModel_Contacts" = [ "ApplicationModel" ];
+          "ApplicationModel_Contacts_DataProvider" = [ "ApplicationModel_Contacts" ];
+          "ApplicationModel_Contacts_Provider" = [ "ApplicationModel_Contacts" ];
+          "ApplicationModel_ConversationalAgent" = [ "ApplicationModel" ];
+          "ApplicationModel_Core" = [ "ApplicationModel" ];
+          "ApplicationModel_DataTransfer" = [ "ApplicationModel" ];
+          "ApplicationModel_DataTransfer_DragDrop" = [ "ApplicationModel_DataTransfer" ];
+          "ApplicationModel_DataTransfer_DragDrop_Core" = [ "ApplicationModel_DataTransfer_DragDrop" ];
+          "ApplicationModel_DataTransfer_ShareTarget" = [ "ApplicationModel_DataTransfer" ];
+          "ApplicationModel_Email" = [ "ApplicationModel" ];
+          "ApplicationModel_Email_DataProvider" = [ "ApplicationModel_Email" ];
+          "ApplicationModel_ExtendedExecution" = [ "ApplicationModel" ];
+          "ApplicationModel_ExtendedExecution_Foreground" = [ "ApplicationModel_ExtendedExecution" ];
+          "ApplicationModel_Holographic" = [ "ApplicationModel" ];
+          "ApplicationModel_LockScreen" = [ "ApplicationModel" ];
+          "ApplicationModel_Payments" = [ "ApplicationModel" ];
+          "ApplicationModel_Payments_Provider" = [ "ApplicationModel_Payments" ];
+          "ApplicationModel_Preview" = [ "ApplicationModel" ];
+          "ApplicationModel_Preview_Holographic" = [ "ApplicationModel_Preview" ];
+          "ApplicationModel_Preview_InkWorkspace" = [ "ApplicationModel_Preview" ];
+          "ApplicationModel_Preview_Notes" = [ "ApplicationModel_Preview" ];
+          "ApplicationModel_Resources" = [ "ApplicationModel" ];
+          "ApplicationModel_Resources_Core" = [ "ApplicationModel_Resources" ];
+          "ApplicationModel_Resources_Management" = [ "ApplicationModel_Resources" ];
+          "ApplicationModel_Search" = [ "ApplicationModel" ];
+          "ApplicationModel_Search_Core" = [ "ApplicationModel_Search" ];
+          "ApplicationModel_Store" = [ "ApplicationModel" ];
+          "ApplicationModel_Store_LicenseManagement" = [ "ApplicationModel_Store" ];
+          "ApplicationModel_Store_Preview" = [ "ApplicationModel_Store" ];
+          "ApplicationModel_Store_Preview_InstallControl" = [ "ApplicationModel_Store_Preview" ];
+          "ApplicationModel_UserActivities" = [ "ApplicationModel" ];
+          "ApplicationModel_UserActivities_Core" = [ "ApplicationModel_UserActivities" ];
+          "ApplicationModel_UserDataAccounts" = [ "ApplicationModel" ];
+          "ApplicationModel_UserDataAccounts_Provider" = [ "ApplicationModel_UserDataAccounts" ];
+          "ApplicationModel_UserDataAccounts_SystemAccess" = [ "ApplicationModel_UserDataAccounts" ];
+          "ApplicationModel_UserDataTasks" = [ "ApplicationModel" ];
+          "ApplicationModel_UserDataTasks_DataProvider" = [ "ApplicationModel_UserDataTasks" ];
+          "ApplicationModel_VoiceCommands" = [ "ApplicationModel" ];
+          "ApplicationModel_Wallet" = [ "ApplicationModel" ];
+          "ApplicationModel_Wallet_System" = [ "ApplicationModel_Wallet" ];
+          "Data_Html" = [ "Data" ];
+          "Data_Json" = [ "Data" ];
+          "Data_Pdf" = [ "Data" ];
+          "Data_Text" = [ "Data" ];
+          "Data_Xml" = [ "Data" ];
+          "Data_Xml_Dom" = [ "Data_Xml" ];
+          "Data_Xml_Xsl" = [ "Data_Xml" ];
+          "Devices_Adc" = [ "Devices" ];
+          "Devices_Adc_Provider" = [ "Devices_Adc" ];
+          "Devices_Background" = [ "Devices" ];
+          "Devices_Bluetooth" = [ "Devices" ];
+          "Devices_Bluetooth_Advertisement" = [ "Devices_Bluetooth" ];
+          "Devices_Bluetooth_Background" = [ "Devices_Bluetooth" ];
+          "Devices_Bluetooth_GenericAttributeProfile" = [ "Devices_Bluetooth" ];
+          "Devices_Bluetooth_Rfcomm" = [ "Devices_Bluetooth" ];
+          "Devices_Custom" = [ "Devices" ];
+          "Devices_Display" = [ "Devices" ];
+          "Devices_Display_Core" = [ "Devices_Display" ];
+          "Devices_Enumeration" = [ "Devices" ];
+          "Devices_Enumeration_Pnp" = [ "Devices_Enumeration" ];
+          "Devices_Geolocation" = [ "Devices" ];
+          "Devices_Geolocation_Geofencing" = [ "Devices_Geolocation" ];
+          "Devices_Geolocation_Provider" = [ "Devices_Geolocation" ];
+          "Devices_Gpio" = [ "Devices" ];
+          "Devices_Gpio_Provider" = [ "Devices_Gpio" ];
+          "Devices_Haptics" = [ "Devices" ];
+          "Devices_HumanInterfaceDevice" = [ "Devices" ];
+          "Devices_I2c" = [ "Devices" ];
+          "Devices_I2c_Provider" = [ "Devices_I2c" ];
+          "Devices_Input" = [ "Devices" ];
+          "Devices_Input_Preview" = [ "Devices_Input" ];
+          "Devices_Lights" = [ "Devices" ];
+          "Devices_Lights_Effects" = [ "Devices_Lights" ];
+          "Devices_Midi" = [ "Devices" ];
+          "Devices_PointOfService" = [ "Devices" ];
+          "Devices_PointOfService_Provider" = [ "Devices_PointOfService" ];
+          "Devices_Portable" = [ "Devices" ];
+          "Devices_Power" = [ "Devices" ];
+          "Devices_Printers" = [ "Devices" ];
+          "Devices_Printers_Extensions" = [ "Devices_Printers" ];
+          "Devices_Pwm" = [ "Devices" ];
+          "Devices_Pwm_Provider" = [ "Devices_Pwm" ];
+          "Devices_Radios" = [ "Devices" ];
+          "Devices_Scanners" = [ "Devices" ];
+          "Devices_Sensors" = [ "Devices" ];
+          "Devices_Sensors_Custom" = [ "Devices_Sensors" ];
+          "Devices_SerialCommunication" = [ "Devices" ];
+          "Devices_SmartCards" = [ "Devices" ];
+          "Devices_Sms" = [ "Devices" ];
+          "Devices_Spi" = [ "Devices" ];
+          "Devices_Spi_Provider" = [ "Devices_Spi" ];
+          "Devices_Usb" = [ "Devices" ];
+          "Devices_WiFi" = [ "Devices" ];
+          "Devices_WiFiDirect" = [ "Devices" ];
+          "Devices_WiFiDirect_Services" = [ "Devices_WiFiDirect" ];
+          "Embedded_DeviceLockdown" = [ "Embedded" ];
+          "Foundation_Collections" = [ "Foundation" ];
+          "Foundation_Diagnostics" = [ "Foundation" ];
+          "Foundation_Metadata" = [ "Foundation" ];
+          "Foundation_Numerics" = [ "Foundation" ];
+          "Gaming_Input" = [ "Gaming" ];
+          "Gaming_Input_Custom" = [ "Gaming_Input" ];
+          "Gaming_Input_ForceFeedback" = [ "Gaming_Input" ];
+          "Gaming_Input_Preview" = [ "Gaming_Input" ];
+          "Gaming_Preview" = [ "Gaming" ];
+          "Gaming_Preview_GamesEnumeration" = [ "Gaming_Preview" ];
+          "Gaming_UI" = [ "Gaming" ];
+          "Gaming_XboxLive" = [ "Gaming" ];
+          "Gaming_XboxLive_Storage" = [ "Gaming_XboxLive" ];
+          "Globalization_Collation" = [ "Globalization" ];
+          "Globalization_DateTimeFormatting" = [ "Globalization" ];
+          "Globalization_Fonts" = [ "Globalization" ];
+          "Globalization_NumberFormatting" = [ "Globalization" ];
+          "Globalization_PhoneNumberFormatting" = [ "Globalization" ];
+          "Graphics_Capture" = [ "Graphics" ];
+          "Graphics_DirectX" = [ "Graphics" ];
+          "Graphics_DirectX_Direct3D11" = [ "Graphics_DirectX" ];
+          "Graphics_Display" = [ "Graphics" ];
+          "Graphics_Display_Core" = [ "Graphics_Display" ];
+          "Graphics_Effects" = [ "Graphics" ];
+          "Graphics_Holographic" = [ "Graphics" ];
+          "Graphics_Imaging" = [ "Graphics" ];
+          "Graphics_Printing" = [ "Graphics" ];
+          "Graphics_Printing3D" = [ "Graphics" ];
+          "Graphics_Printing_OptionDetails" = [ "Graphics_Printing" ];
+          "Graphics_Printing_PrintSupport" = [ "Graphics_Printing" ];
+          "Graphics_Printing_PrintTicket" = [ "Graphics_Printing" ];
+          "Graphics_Printing_Workflow" = [ "Graphics_Printing" ];
+          "Management_Core" = [ "Management" ];
+          "Management_Deployment" = [ "Management" ];
+          "Management_Deployment_Preview" = [ "Management_Deployment" ];
+          "Management_Policies" = [ "Management" ];
+          "Management_Update" = [ "Management" ];
+          "Management_Workplace" = [ "Management" ];
+          "Media_AppBroadcasting" = [ "Media" ];
+          "Media_AppRecording" = [ "Media" ];
+          "Media_Audio" = [ "Media" ];
+          "Media_Capture" = [ "Media" ];
+          "Media_Capture_Core" = [ "Media_Capture" ];
+          "Media_Capture_Frames" = [ "Media_Capture" ];
+          "Media_Casting" = [ "Media" ];
+          "Media_ClosedCaptioning" = [ "Media" ];
+          "Media_ContentRestrictions" = [ "Media" ];
+          "Media_Control" = [ "Media" ];
+          "Media_Core" = [ "Media" ];
+          "Media_Core_Preview" = [ "Media_Core" ];
+          "Media_Devices" = [ "Media" ];
+          "Media_Devices_Core" = [ "Media_Devices" ];
+          "Media_DialProtocol" = [ "Media" ];
+          "Media_Editing" = [ "Media" ];
+          "Media_Effects" = [ "Media" ];
+          "Media_FaceAnalysis" = [ "Media" ];
+          "Media_Import" = [ "Media" ];
+          "Media_MediaProperties" = [ "Media" ];
+          "Media_Miracast" = [ "Media" ];
+          "Media_Ocr" = [ "Media" ];
+          "Media_PlayTo" = [ "Media" ];
+          "Media_Playback" = [ "Media" ];
+          "Media_Playlists" = [ "Media" ];
+          "Media_Protection" = [ "Media" ];
+          "Media_Protection_PlayReady" = [ "Media_Protection" ];
+          "Media_Render" = [ "Media" ];
+          "Media_SpeechRecognition" = [ "Media" ];
+          "Media_SpeechSynthesis" = [ "Media" ];
+          "Media_Streaming" = [ "Media" ];
+          "Media_Streaming_Adaptive" = [ "Media_Streaming" ];
+          "Media_Transcoding" = [ "Media" ];
+          "Networking_BackgroundTransfer" = [ "Networking" ];
+          "Networking_Connectivity" = [ "Networking" ];
+          "Networking_NetworkOperators" = [ "Networking" ];
+          "Networking_Proximity" = [ "Networking" ];
+          "Networking_PushNotifications" = [ "Networking" ];
+          "Networking_ServiceDiscovery" = [ "Networking" ];
+          "Networking_ServiceDiscovery_Dnssd" = [ "Networking_ServiceDiscovery" ];
+          "Networking_Sockets" = [ "Networking" ];
+          "Networking_Vpn" = [ "Networking" ];
+          "Networking_XboxLive" = [ "Networking" ];
+          "Perception_Automation" = [ "Perception" ];
+          "Perception_Automation_Core" = [ "Perception_Automation" ];
+          "Perception_People" = [ "Perception" ];
+          "Perception_Spatial" = [ "Perception" ];
+          "Perception_Spatial_Preview" = [ "Perception_Spatial" ];
+          "Perception_Spatial_Surfaces" = [ "Perception_Spatial" ];
+          "Phone_ApplicationModel" = [ "Phone" ];
+          "Phone_Devices" = [ "Phone" ];
+          "Phone_Devices_Notification" = [ "Phone_Devices" ];
+          "Phone_Devices_Power" = [ "Phone_Devices" ];
+          "Phone_Management" = [ "Phone" ];
+          "Phone_Management_Deployment" = [ "Phone_Management" ];
+          "Phone_Media" = [ "Phone" ];
+          "Phone_Media_Devices" = [ "Phone_Media" ];
+          "Phone_Notification" = [ "Phone" ];
+          "Phone_Notification_Management" = [ "Phone_Notification" ];
+          "Phone_PersonalInformation" = [ "Phone" ];
+          "Phone_PersonalInformation_Provisioning" = [ "Phone_PersonalInformation" ];
+          "Phone_Speech" = [ "Phone" ];
+          "Phone_Speech_Recognition" = [ "Phone_Speech" ];
+          "Phone_StartScreen" = [ "Phone" ];
+          "Phone_System" = [ "Phone" ];
+          "Phone_System_Power" = [ "Phone_System" ];
+          "Phone_System_Profile" = [ "Phone_System" ];
+          "Phone_System_UserProfile" = [ "Phone_System" ];
+          "Phone_System_UserProfile_GameServices" = [ "Phone_System_UserProfile" ];
+          "Phone_System_UserProfile_GameServices_Core" = [ "Phone_System_UserProfile_GameServices" ];
+          "Phone_UI" = [ "Phone" ];
+          "Phone_UI_Input" = [ "Phone_UI" ];
+          "Security_Authentication" = [ "Security" ];
+          "Security_Authentication_Identity" = [ "Security_Authentication" ];
+          "Security_Authentication_Identity_Core" = [ "Security_Authentication_Identity" ];
+          "Security_Authentication_OnlineId" = [ "Security_Authentication" ];
+          "Security_Authentication_Web" = [ "Security_Authentication" ];
+          "Security_Authentication_Web_Core" = [ "Security_Authentication_Web" ];
+          "Security_Authentication_Web_Provider" = [ "Security_Authentication_Web" ];
+          "Security_Authorization" = [ "Security" ];
+          "Security_Authorization_AppCapabilityAccess" = [ "Security_Authorization" ];
+          "Security_Credentials" = [ "Security" ];
+          "Security_Credentials_UI" = [ "Security_Credentials" ];
+          "Security_Cryptography" = [ "Security" ];
+          "Security_Cryptography_Certificates" = [ "Security_Cryptography" ];
+          "Security_Cryptography_Core" = [ "Security_Cryptography" ];
+          "Security_Cryptography_DataProtection" = [ "Security_Cryptography" ];
+          "Security_DataProtection" = [ "Security" ];
+          "Security_EnterpriseData" = [ "Security" ];
+          "Security_ExchangeActiveSyncProvisioning" = [ "Security" ];
+          "Security_Isolation" = [ "Security" ];
+          "Services_Maps" = [ "Services" ];
+          "Services_Maps_Guidance" = [ "Services_Maps" ];
+          "Services_Maps_LocalSearch" = [ "Services_Maps" ];
+          "Services_Maps_OfflineMaps" = [ "Services_Maps" ];
+          "Services_Store" = [ "Services" ];
+          "Services_TargetedContent" = [ "Services" ];
+          "Storage_AccessCache" = [ "Storage" ];
+          "Storage_BulkAccess" = [ "Storage" ];
+          "Storage_Compression" = [ "Storage" ];
+          "Storage_FileProperties" = [ "Storage" ];
+          "Storage_Pickers" = [ "Storage" ];
+          "Storage_Pickers_Provider" = [ "Storage_Pickers" ];
+          "Storage_Provider" = [ "Storage" ];
+          "Storage_Search" = [ "Storage" ];
+          "Storage_Streams" = [ "Storage" ];
+          "System_Diagnostics" = [ "System" ];
+          "System_Diagnostics_DevicePortal" = [ "System_Diagnostics" ];
+          "System_Diagnostics_Telemetry" = [ "System_Diagnostics" ];
+          "System_Diagnostics_TraceReporting" = [ "System_Diagnostics" ];
+          "System_Display" = [ "System" ];
+          "System_Implementation" = [ "System" ];
+          "System_Implementation_FileExplorer" = [ "System_Implementation" ];
+          "System_Inventory" = [ "System" ];
+          "System_Power" = [ "System" ];
+          "System_Profile" = [ "System" ];
+          "System_Profile_SystemManufacturers" = [ "System_Profile" ];
+          "System_RemoteDesktop" = [ "System" ];
+          "System_RemoteDesktop_Input" = [ "System_RemoteDesktop" ];
+          "System_RemoteSystems" = [ "System" ];
+          "System_Threading" = [ "System" ];
+          "System_Threading_Core" = [ "System_Threading" ];
+          "System_Update" = [ "System" ];
+          "System_UserProfile" = [ "System" ];
+          "UI_Accessibility" = [ "UI" ];
+          "UI_ApplicationSettings" = [ "UI" ];
+          "UI_Composition" = [ "UI" ];
+          "UI_Composition_Core" = [ "UI_Composition" ];
+          "UI_Composition_Desktop" = [ "UI_Composition" ];
+          "UI_Composition_Diagnostics" = [ "UI_Composition" ];
+          "UI_Composition_Effects" = [ "UI_Composition" ];
+          "UI_Composition_Interactions" = [ "UI_Composition" ];
+          "UI_Composition_Scenes" = [ "UI_Composition" ];
+          "UI_Core" = [ "UI" ];
+          "UI_Core_AnimationMetrics" = [ "UI_Core" ];
+          "UI_Core_Preview" = [ "UI_Core" ];
+          "UI_Input" = [ "UI" ];
+          "UI_Input_Core" = [ "UI_Input" ];
+          "UI_Input_Inking" = [ "UI_Input" ];
+          "UI_Input_Inking_Analysis" = [ "UI_Input_Inking" ];
+          "UI_Input_Inking_Core" = [ "UI_Input_Inking" ];
+          "UI_Input_Inking_Preview" = [ "UI_Input_Inking" ];
+          "UI_Input_Preview" = [ "UI_Input" ];
+          "UI_Input_Preview_Injection" = [ "UI_Input_Preview" ];
+          "UI_Input_Spatial" = [ "UI_Input" ];
+          "UI_Notifications" = [ "UI" ];
+          "UI_Notifications_Management" = [ "UI_Notifications" ];
+          "UI_Popups" = [ "UI" ];
+          "UI_Shell" = [ "UI" ];
+          "UI_StartScreen" = [ "UI" ];
+          "UI_Text" = [ "UI" ];
+          "UI_Text_Core" = [ "UI_Text" ];
+          "UI_UIAutomation" = [ "UI" ];
+          "UI_UIAutomation_Core" = [ "UI_UIAutomation" ];
+          "UI_ViewManagement" = [ "UI" ];
+          "UI_ViewManagement_Core" = [ "UI_ViewManagement" ];
+          "UI_WebUI" = [ "UI" ];
+          "UI_WebUI_Core" = [ "UI_WebUI" ];
+          "UI_WindowManagement" = [ "UI" ];
+          "UI_WindowManagement_Preview" = [ "UI_WindowManagement" ];
+          "Wdk_Foundation" = [ "Wdk" ];
+          "Wdk_Graphics" = [ "Wdk" ];
+          "Wdk_Graphics_Direct3D" = [ "Wdk_Graphics" ];
+          "Wdk_Storage" = [ "Wdk" ];
+          "Wdk_Storage_FileSystem" = [ "Wdk_Storage" ];
+          "Wdk_Storage_FileSystem_Minifilters" = [ "Wdk_Storage_FileSystem" ];
+          "Wdk_System" = [ "Wdk" ];
+          "Wdk_System_IO" = [ "Wdk_System" ];
+          "Wdk_System_OfflineRegistry" = [ "Wdk_System" ];
+          "Wdk_System_Registry" = [ "Wdk_System" ];
+          "Wdk_System_SystemInformation" = [ "Wdk_System" ];
+          "Wdk_System_SystemServices" = [ "Wdk_System" ];
+          "Wdk_System_Threading" = [ "Wdk_System" ];
+          "Web_AtomPub" = [ "Web" ];
+          "Web_Http" = [ "Web" ];
+          "Web_Http_Diagnostics" = [ "Web_Http" ];
+          "Web_Http_Filters" = [ "Web_Http" ];
+          "Web_Http_Headers" = [ "Web_Http" ];
+          "Web_Syndication" = [ "Web" ];
+          "Web_UI" = [ "Web" ];
+          "Web_UI_Interop" = [ "Web_UI" ];
+          "Win32_AI" = [ "Win32" ];
+          "Win32_AI_MachineLearning" = [ "Win32_AI" ];
+          "Win32_AI_MachineLearning_DirectML" = [ "Win32_AI_MachineLearning" ];
+          "Win32_AI_MachineLearning_WinML" = [ "Win32_AI_MachineLearning" ];
+          "Win32_Data" = [ "Win32" ];
+          "Win32_Data_HtmlHelp" = [ "Win32_Data" ];
+          "Win32_Data_RightsManagement" = [ "Win32_Data" ];
+          "Win32_Data_Xml" = [ "Win32_Data" ];
+          "Win32_Data_Xml_MsXml" = [ "Win32_Data_Xml" ];
+          "Win32_Data_Xml_XmlLite" = [ "Win32_Data_Xml" ];
+          "Win32_Devices" = [ "Win32" ];
+          "Win32_Devices_AllJoyn" = [ "Win32_Devices" ];
+          "Win32_Devices_BiometricFramework" = [ "Win32_Devices" ];
+          "Win32_Devices_Bluetooth" = [ "Win32_Devices" ];
+          "Win32_Devices_Communication" = [ "Win32_Devices" ];
+          "Win32_Devices_DeviceAccess" = [ "Win32_Devices" ];
+          "Win32_Devices_DeviceAndDriverInstallation" = [ "Win32_Devices" ];
+          "Win32_Devices_DeviceQuery" = [ "Win32_Devices" ];
+          "Win32_Devices_Display" = [ "Win32_Devices" ];
+          "Win32_Devices_Enumeration" = [ "Win32_Devices" ];
+          "Win32_Devices_Enumeration_Pnp" = [ "Win32_Devices_Enumeration" ];
+          "Win32_Devices_Fax" = [ "Win32_Devices" ];
+          "Win32_Devices_FunctionDiscovery" = [ "Win32_Devices" ];
+          "Win32_Devices_Geolocation" = [ "Win32_Devices" ];
+          "Win32_Devices_HumanInterfaceDevice" = [ "Win32_Devices" ];
+          "Win32_Devices_ImageAcquisition" = [ "Win32_Devices" ];
+          "Win32_Devices_PortableDevices" = [ "Win32_Devices" ];
+          "Win32_Devices_Properties" = [ "Win32_Devices" ];
+          "Win32_Devices_Pwm" = [ "Win32_Devices" ];
+          "Win32_Devices_Sensors" = [ "Win32_Devices" ];
+          "Win32_Devices_SerialCommunication" = [ "Win32_Devices" ];
+          "Win32_Devices_Tapi" = [ "Win32_Devices" ];
+          "Win32_Devices_Usb" = [ "Win32_Devices" ];
+          "Win32_Devices_WebServicesOnDevices" = [ "Win32_Devices" ];
+          "Win32_Foundation" = [ "Win32" ];
+          "Win32_Gaming" = [ "Win32" ];
+          "Win32_Globalization" = [ "Win32" ];
+          "Win32_Graphics" = [ "Win32" ];
+          "Win32_Graphics_CompositionSwapchain" = [ "Win32_Graphics" ];
+          "Win32_Graphics_DXCore" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Direct2D" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Direct2D_Common" = [ "Win32_Graphics_Direct2D" ];
+          "Win32_Graphics_Direct3D" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Direct3D10" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Direct3D11" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Direct3D11on12" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Direct3D12" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Direct3D9" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Direct3D9on12" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Direct3D_Dxc" = [ "Win32_Graphics_Direct3D" ];
+          "Win32_Graphics_Direct3D_Fxc" = [ "Win32_Graphics_Direct3D" ];
+          "Win32_Graphics_DirectComposition" = [ "Win32_Graphics" ];
+          "Win32_Graphics_DirectDraw" = [ "Win32_Graphics" ];
+          "Win32_Graphics_DirectManipulation" = [ "Win32_Graphics" ];
+          "Win32_Graphics_DirectWrite" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Dwm" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Dxgi" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Dxgi_Common" = [ "Win32_Graphics_Dxgi" ];
+          "Win32_Graphics_Gdi" = [ "Win32_Graphics" ];
+          "Win32_Graphics_GdiPlus" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Hlsl" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Imaging" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Imaging_D2D" = [ "Win32_Graphics_Imaging" ];
+          "Win32_Graphics_OpenGL" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Printing" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Printing_PrintTicket" = [ "Win32_Graphics_Printing" ];
+          "Win32_Management" = [ "Win32" ];
+          "Win32_Management_MobileDeviceManagementRegistration" = [ "Win32_Management" ];
+          "Win32_Media" = [ "Win32" ];
+          "Win32_Media_Audio" = [ "Win32_Media" ];
+          "Win32_Media_Audio_Apo" = [ "Win32_Media_Audio" ];
+          "Win32_Media_Audio_DirectMusic" = [ "Win32_Media_Audio" ];
+          "Win32_Media_Audio_DirectSound" = [ "Win32_Media_Audio" ];
+          "Win32_Media_Audio_Endpoints" = [ "Win32_Media_Audio" ];
+          "Win32_Media_Audio_XAudio2" = [ "Win32_Media_Audio" ];
+          "Win32_Media_DeviceManager" = [ "Win32_Media" ];
+          "Win32_Media_DirectShow" = [ "Win32_Media" ];
+          "Win32_Media_DirectShow_Tv" = [ "Win32_Media_DirectShow" ];
+          "Win32_Media_DirectShow_Xml" = [ "Win32_Media_DirectShow" ];
+          "Win32_Media_DxMediaObjects" = [ "Win32_Media" ];
+          "Win32_Media_KernelStreaming" = [ "Win32_Media" ];
+          "Win32_Media_LibrarySharingServices" = [ "Win32_Media" ];
+          "Win32_Media_MediaFoundation" = [ "Win32_Media" ];
+          "Win32_Media_MediaPlayer" = [ "Win32_Media" ];
+          "Win32_Media_Multimedia" = [ "Win32_Media" ];
+          "Win32_Media_PictureAcquisition" = [ "Win32_Media" ];
+          "Win32_Media_Speech" = [ "Win32_Media" ];
+          "Win32_Media_Streaming" = [ "Win32_Media" ];
+          "Win32_Media_WindowsMediaFormat" = [ "Win32_Media" ];
+          "Win32_NetworkManagement" = [ "Win32" ];
+          "Win32_NetworkManagement_Dhcp" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_Dns" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_InternetConnectionWizard" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_IpHelper" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_MobileBroadband" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_Multicast" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_Ndis" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_NetBios" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_NetManagement" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_NetShell" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_NetworkDiagnosticsFramework" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_NetworkPolicyServer" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_P2P" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_QoS" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_Rras" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_Snmp" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WNet" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WebDav" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WiFi" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WindowsConnectNow" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WindowsConnectionManager" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WindowsFilteringPlatform" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WindowsFirewall" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WindowsNetworkVirtualization" = [ "Win32_NetworkManagement" ];
+          "Win32_Networking" = [ "Win32" ];
+          "Win32_Networking_ActiveDirectory" = [ "Win32_Networking" ];
+          "Win32_Networking_BackgroundIntelligentTransferService" = [ "Win32_Networking" ];
+          "Win32_Networking_Clustering" = [ "Win32_Networking" ];
+          "Win32_Networking_HttpServer" = [ "Win32_Networking" ];
+          "Win32_Networking_Ldap" = [ "Win32_Networking" ];
+          "Win32_Networking_NetworkListManager" = [ "Win32_Networking" ];
+          "Win32_Networking_RemoteDifferentialCompression" = [ "Win32_Networking" ];
+          "Win32_Networking_WebSocket" = [ "Win32_Networking" ];
+          "Win32_Networking_WinHttp" = [ "Win32_Networking" ];
+          "Win32_Networking_WinInet" = [ "Win32_Networking" ];
+          "Win32_Networking_WinSock" = [ "Win32_Networking" ];
+          "Win32_Networking_WindowsWebServices" = [ "Win32_Networking" ];
+          "Win32_Security" = [ "Win32" ];
+          "Win32_Security_AppLocker" = [ "Win32_Security" ];
+          "Win32_Security_Authentication" = [ "Win32_Security" ];
+          "Win32_Security_Authentication_Identity" = [ "Win32_Security_Authentication" ];
+          "Win32_Security_Authentication_Identity_Provider" = [ "Win32_Security_Authentication_Identity" ];
+          "Win32_Security_Authorization" = [ "Win32_Security" ];
+          "Win32_Security_Authorization_UI" = [ "Win32_Security_Authorization" ];
+          "Win32_Security_ConfigurationSnapin" = [ "Win32_Security" ];
+          "Win32_Security_Credentials" = [ "Win32_Security" ];
+          "Win32_Security_Cryptography" = [ "Win32_Security" ];
+          "Win32_Security_Cryptography_Catalog" = [ "Win32_Security_Cryptography" ];
+          "Win32_Security_Cryptography_Certificates" = [ "Win32_Security_Cryptography" ];
+          "Win32_Security_Cryptography_Sip" = [ "Win32_Security_Cryptography" ];
+          "Win32_Security_Cryptography_UI" = [ "Win32_Security_Cryptography" ];
+          "Win32_Security_DiagnosticDataQuery" = [ "Win32_Security" ];
+          "Win32_Security_DirectoryServices" = [ "Win32_Security" ];
+          "Win32_Security_EnterpriseData" = [ "Win32_Security" ];
+          "Win32_Security_ExtensibleAuthenticationProtocol" = [ "Win32_Security" ];
+          "Win32_Security_Isolation" = [ "Win32_Security" ];
+          "Win32_Security_LicenseProtection" = [ "Win32_Security" ];
+          "Win32_Security_NetworkAccessProtection" = [ "Win32_Security" ];
+          "Win32_Security_Tpm" = [ "Win32_Security" ];
+          "Win32_Security_WinTrust" = [ "Win32_Security" ];
+          "Win32_Security_WinWlx" = [ "Win32_Security" ];
+          "Win32_Storage" = [ "Win32" ];
+          "Win32_Storage_Cabinets" = [ "Win32_Storage" ];
+          "Win32_Storage_CloudFilters" = [ "Win32_Storage" ];
+          "Win32_Storage_Compression" = [ "Win32_Storage" ];
+          "Win32_Storage_DataDeduplication" = [ "Win32_Storage" ];
+          "Win32_Storage_DistributedFileSystem" = [ "Win32_Storage" ];
+          "Win32_Storage_EnhancedStorage" = [ "Win32_Storage" ];
+          "Win32_Storage_FileHistory" = [ "Win32_Storage" ];
+          "Win32_Storage_FileServerResourceManager" = [ "Win32_Storage" ];
+          "Win32_Storage_FileSystem" = [ "Win32_Storage" ];
+          "Win32_Storage_Imapi" = [ "Win32_Storage" ];
+          "Win32_Storage_IndexServer" = [ "Win32_Storage" ];
+          "Win32_Storage_InstallableFileSystems" = [ "Win32_Storage" ];
+          "Win32_Storage_IscsiDisc" = [ "Win32_Storage" ];
+          "Win32_Storage_Jet" = [ "Win32_Storage" ];
+          "Win32_Storage_Nvme" = [ "Win32_Storage" ];
+          "Win32_Storage_OfflineFiles" = [ "Win32_Storage" ];
+          "Win32_Storage_OperationRecorder" = [ "Win32_Storage" ];
+          "Win32_Storage_Packaging" = [ "Win32_Storage" ];
+          "Win32_Storage_Packaging_Appx" = [ "Win32_Storage_Packaging" ];
+          "Win32_Storage_Packaging_Opc" = [ "Win32_Storage_Packaging" ];
+          "Win32_Storage_ProjectedFileSystem" = [ "Win32_Storage" ];
+          "Win32_Storage_StructuredStorage" = [ "Win32_Storage" ];
+          "Win32_Storage_Vhd" = [ "Win32_Storage" ];
+          "Win32_Storage_VirtualDiskService" = [ "Win32_Storage" ];
+          "Win32_Storage_Vss" = [ "Win32_Storage" ];
+          "Win32_Storage_Xps" = [ "Win32_Storage" ];
+          "Win32_Storage_Xps_Printing" = [ "Win32_Storage_Xps" ];
+          "Win32_System" = [ "Win32" ];
+          "Win32_System_AddressBook" = [ "Win32_System" ];
+          "Win32_System_Antimalware" = [ "Win32_System" ];
+          "Win32_System_ApplicationInstallationAndServicing" = [ "Win32_System" ];
+          "Win32_System_ApplicationVerifier" = [ "Win32_System" ];
+          "Win32_System_AssessmentTool" = [ "Win32_System" ];
+          "Win32_System_ClrHosting" = [ "Win32_System" ];
+          "Win32_System_Com" = [ "Win32_System" ];
+          "Win32_System_Com_CallObj" = [ "Win32_System_Com" ];
+          "Win32_System_Com_ChannelCredentials" = [ "Win32_System_Com" ];
+          "Win32_System_Com_Events" = [ "Win32_System_Com" ];
+          "Win32_System_Com_Marshal" = [ "Win32_System_Com" ];
+          "Win32_System_Com_StructuredStorage" = [ "Win32_System_Com" ];
+          "Win32_System_Com_UI" = [ "Win32_System_Com" ];
+          "Win32_System_Com_Urlmon" = [ "Win32_System_Com" ];
+          "Win32_System_ComponentServices" = [ "Win32_System" ];
+          "Win32_System_Console" = [ "Win32_System" ];
+          "Win32_System_Contacts" = [ "Win32_System" ];
+          "Win32_System_CorrelationVector" = [ "Win32_System" ];
+          "Win32_System_DataExchange" = [ "Win32_System" ];
+          "Win32_System_DeploymentServices" = [ "Win32_System" ];
+          "Win32_System_DesktopSharing" = [ "Win32_System" ];
+          "Win32_System_DeveloperLicensing" = [ "Win32_System" ];
+          "Win32_System_Diagnostics" = [ "Win32_System" ];
+          "Win32_System_Diagnostics_Ceip" = [ "Win32_System_Diagnostics" ];
+          "Win32_System_Diagnostics_ClrProfiling" = [ "Win32_System_Diagnostics" ];
+          "Win32_System_Diagnostics_Debug" = [ "Win32_System_Diagnostics" ];
+          "Win32_System_Diagnostics_Debug_ActiveScript" = [ "Win32_System_Diagnostics_Debug" ];
+          "Win32_System_Diagnostics_Debug_Extensions" = [ "Win32_System_Diagnostics_Debug" ];
+          "Win32_System_Diagnostics_Etw" = [ "Win32_System_Diagnostics" ];
+          "Win32_System_Diagnostics_ProcessSnapshotting" = [ "Win32_System_Diagnostics" ];
+          "Win32_System_Diagnostics_ToolHelp" = [ "Win32_System_Diagnostics" ];
+          "Win32_System_DistributedTransactionCoordinator" = [ "Win32_System" ];
+          "Win32_System_Environment" = [ "Win32_System" ];
+          "Win32_System_ErrorReporting" = [ "Win32_System" ];
+          "Win32_System_EventCollector" = [ "Win32_System" ];
+          "Win32_System_EventLog" = [ "Win32_System" ];
+          "Win32_System_EventNotificationService" = [ "Win32_System" ];
+          "Win32_System_GroupPolicy" = [ "Win32_System" ];
+          "Win32_System_HostCompute" = [ "Win32_System" ];
+          "Win32_System_HostComputeNetwork" = [ "Win32_System" ];
+          "Win32_System_HostComputeSystem" = [ "Win32_System" ];
+          "Win32_System_Hypervisor" = [ "Win32_System" ];
+          "Win32_System_IO" = [ "Win32_System" ];
+          "Win32_System_Iis" = [ "Win32_System" ];
+          "Win32_System_Ioctl" = [ "Win32_System" ];
+          "Win32_System_JobObjects" = [ "Win32_System" ];
+          "Win32_System_Js" = [ "Win32_System" ];
+          "Win32_System_Kernel" = [ "Win32_System" ];
+          "Win32_System_LibraryLoader" = [ "Win32_System" ];
+          "Win32_System_Mailslots" = [ "Win32_System" ];
+          "Win32_System_Mapi" = [ "Win32_System" ];
+          "Win32_System_Memory" = [ "Win32_System" ];
+          "Win32_System_Memory_NonVolatile" = [ "Win32_System_Memory" ];
+          "Win32_System_MessageQueuing" = [ "Win32_System" ];
+          "Win32_System_MixedReality" = [ "Win32_System" ];
+          "Win32_System_Mmc" = [ "Win32_System" ];
+          "Win32_System_Ole" = [ "Win32_System" ];
+          "Win32_System_ParentalControls" = [ "Win32_System" ];
+          "Win32_System_PasswordManagement" = [ "Win32_System" ];
+          "Win32_System_Performance" = [ "Win32_System" ];
+          "Win32_System_Performance_HardwareCounterProfiling" = [ "Win32_System_Performance" ];
+          "Win32_System_Pipes" = [ "Win32_System" ];
+          "Win32_System_Power" = [ "Win32_System" ];
+          "Win32_System_ProcessStatus" = [ "Win32_System" ];
+          "Win32_System_RealTimeCommunications" = [ "Win32_System" ];
+          "Win32_System_Recovery" = [ "Win32_System" ];
+          "Win32_System_Registry" = [ "Win32_System" ];
+          "Win32_System_RemoteAssistance" = [ "Win32_System" ];
+          "Win32_System_RemoteDesktop" = [ "Win32_System" ];
+          "Win32_System_RemoteManagement" = [ "Win32_System" ];
+          "Win32_System_RestartManager" = [ "Win32_System" ];
+          "Win32_System_Restore" = [ "Win32_System" ];
+          "Win32_System_Rpc" = [ "Win32_System" ];
+          "Win32_System_Search" = [ "Win32_System" ];
+          "Win32_System_Search_Common" = [ "Win32_System_Search" ];
+          "Win32_System_SecurityCenter" = [ "Win32_System" ];
+          "Win32_System_ServerBackup" = [ "Win32_System" ];
+          "Win32_System_Services" = [ "Win32_System" ];
+          "Win32_System_SettingsManagementInfrastructure" = [ "Win32_System" ];
+          "Win32_System_SetupAndMigration" = [ "Win32_System" ];
+          "Win32_System_Shutdown" = [ "Win32_System" ];
+          "Win32_System_SideShow" = [ "Win32_System" ];
+          "Win32_System_StationsAndDesktops" = [ "Win32_System" ];
+          "Win32_System_SubsystemForLinux" = [ "Win32_System" ];
+          "Win32_System_SystemInformation" = [ "Win32_System" ];
+          "Win32_System_SystemServices" = [ "Win32_System" ];
+          "Win32_System_TaskScheduler" = [ "Win32_System" ];
+          "Win32_System_Threading" = [ "Win32_System" ];
+          "Win32_System_Time" = [ "Win32_System" ];
+          "Win32_System_TpmBaseServices" = [ "Win32_System" ];
+          "Win32_System_TransactionServer" = [ "Win32_System" ];
+          "Win32_System_UpdateAgent" = [ "Win32_System" ];
+          "Win32_System_UpdateAssessment" = [ "Win32_System" ];
+          "Win32_System_UserAccessLogging" = [ "Win32_System" ];
+          "Win32_System_Variant" = [ "Win32_System" ];
+          "Win32_System_VirtualDosMachines" = [ "Win32_System" ];
+          "Win32_System_WinRT" = [ "Win32_System" ];
+          "Win32_System_WinRT_AllJoyn" = [ "Win32_System_WinRT" ];
+          "Win32_System_WinRT_Composition" = [ "Win32_System_WinRT" ];
+          "Win32_System_WinRT_CoreInputView" = [ "Win32_System_WinRT" ];
+          "Win32_System_WinRT_Direct3D11" = [ "Win32_System_WinRT" ];
+          "Win32_System_WinRT_Display" = [ "Win32_System_WinRT" ];
+          "Win32_System_WinRT_Graphics" = [ "Win32_System_WinRT" ];
+          "Win32_System_WinRT_Graphics_Capture" = [ "Win32_System_WinRT_Graphics" ];
+          "Win32_System_WinRT_Graphics_Direct2D" = [ "Win32_System_WinRT_Graphics" ];
+          "Win32_System_WinRT_Graphics_Imaging" = [ "Win32_System_WinRT_Graphics" ];
+          "Win32_System_WinRT_Holographic" = [ "Win32_System_WinRT" ];
+          "Win32_System_WinRT_Isolation" = [ "Win32_System_WinRT" ];
+          "Win32_System_WinRT_ML" = [ "Win32_System_WinRT" ];
+          "Win32_System_WinRT_Media" = [ "Win32_System_WinRT" ];
+          "Win32_System_WinRT_Metadata" = [ "Win32_System_WinRT" ];
+          "Win32_System_WinRT_Pdf" = [ "Win32_System_WinRT" ];
+          "Win32_System_WinRT_Printing" = [ "Win32_System_WinRT" ];
+          "Win32_System_WinRT_Shell" = [ "Win32_System_WinRT" ];
+          "Win32_System_WinRT_Storage" = [ "Win32_System_WinRT" ];
+          "Win32_System_WindowsProgramming" = [ "Win32_System" ];
+          "Win32_System_WindowsSync" = [ "Win32_System" ];
+          "Win32_System_Wmi" = [ "Win32_System" ];
+          "Win32_UI" = [ "Win32" ];
+          "Win32_UI_Accessibility" = [ "Win32_UI" ];
+          "Win32_UI_Animation" = [ "Win32_UI" ];
+          "Win32_UI_ColorSystem" = [ "Win32_UI" ];
+          "Win32_UI_Controls" = [ "Win32_UI" ];
+          "Win32_UI_Controls_Dialogs" = [ "Win32_UI_Controls" ];
+          "Win32_UI_Controls_RichEdit" = [ "Win32_UI_Controls" ];
+          "Win32_UI_HiDpi" = [ "Win32_UI" ];
+          "Win32_UI_Input" = [ "Win32_UI" ];
+          "Win32_UI_Input_Ime" = [ "Win32_UI_Input" ];
+          "Win32_UI_Input_Ink" = [ "Win32_UI_Input" ];
+          "Win32_UI_Input_KeyboardAndMouse" = [ "Win32_UI_Input" ];
+          "Win32_UI_Input_Pointer" = [ "Win32_UI_Input" ];
+          "Win32_UI_Input_Radial" = [ "Win32_UI_Input" ];
+          "Win32_UI_Input_Touch" = [ "Win32_UI_Input" ];
+          "Win32_UI_Input_XboxController" = [ "Win32_UI_Input" ];
+          "Win32_UI_InteractionContext" = [ "Win32_UI" ];
+          "Win32_UI_LegacyWindowsEnvironmentFeatures" = [ "Win32_UI" ];
+          "Win32_UI_Magnification" = [ "Win32_UI" ];
+          "Win32_UI_Notifications" = [ "Win32_UI" ];
+          "Win32_UI_Ribbon" = [ "Win32_UI" ];
+          "Win32_UI_Shell" = [ "Win32_UI" ];
+          "Win32_UI_Shell_Common" = [ "Win32_UI_Shell" ];
+          "Win32_UI_Shell_PropertiesSystem" = [ "Win32_UI_Shell" ];
+          "Win32_UI_TabletPC" = [ "Win32_UI" ];
+          "Win32_UI_TextServices" = [ "Win32_UI" ];
+          "Win32_UI_WindowsAndMessaging" = [ "Win32_UI" ];
+          "Win32_UI_Wpf" = [ "Win32_UI" ];
+          "Win32_Web" = [ "Win32" ];
+          "Win32_Web_InternetExplorer" = [ "Win32_Web" ];
+          "implement" = [ "windows-implement" "windows-interface" "windows-core/implement" ];
+          "windows-implement" = [ "dep:windows-implement" ];
+          "windows-interface" = [ "dep:windows-interface" ];
+        };
+        resolvedDefaultFeatures = [ "Wdk" "Wdk_System" "Wdk_System_SystemInformation" "Wdk_System_SystemServices" "Wdk_System_Threading" "Win32" "Win32_Foundation" "Win32_NetworkManagement" "Win32_NetworkManagement_IpHelper" "Win32_NetworkManagement_Ndis" "Win32_NetworkManagement_NetManagement" "Win32_Networking" "Win32_Networking_WinSock" "Win32_Security" "Win32_Security_Authentication" "Win32_Security_Authentication_Identity" "Win32_Security_Authorization" "Win32_Storage" "Win32_Storage_FileSystem" "Win32_System" "Win32_System_Com" "Win32_System_Diagnostics" "Win32_System_Diagnostics_Debug" "Win32_System_IO" "Win32_System_Ioctl" "Win32_System_Kernel" "Win32_System_LibraryLoader" "Win32_System_Memory" "Win32_System_Ole" "Win32_System_Performance" "Win32_System_Power" "Win32_System_ProcessStatus" "Win32_System_Registry" "Win32_System_RemoteDesktop" "Win32_System_Rpc" "Win32_System_SystemInformation" "Win32_System_SystemServices" "Win32_System_Threading" "Win32_System_Variant" "Win32_System_WindowsProgramming" "Win32_System_Wmi" "Win32_UI" "Win32_UI_Shell" "default" ];
+      };
+      "windows-core 0.51.1" = rec {
+        crateName = "windows-core";
+        version = "0.51.1";
+        edition = "2021";
+        sha256 = "0r1f57hsshsghjyc7ypp2s0i78f7b1vr93w68sdb8baxyf2czy7i";
+        authors = [
+          "Microsoft"
+        ];
+        dependencies = [
+          {
+            name = "windows-targets";
+            packageId = "windows-targets 0.48.5";
+          }
+        ];
+        features = { };
+        resolvedDefaultFeatures = [ "default" ];
+      };
+      "windows-core 0.52.0" = rec {
+        crateName = "windows-core";
+        version = "0.52.0";
+        edition = "2021";
+        sha256 = "1nc3qv7sy24x0nlnb32f7alzpd6f72l4p24vl65vydbyil669ark";
+        authors = [
+          "Microsoft"
+        ];
+        dependencies = [
+          {
+            name = "windows-targets";
+            packageId = "windows-targets 0.52.0";
+          }
+        ];
+        features = { };
+        resolvedDefaultFeatures = [ "default" ];
+      };
+      "windows-sys" = rec {
+        crateName = "windows-sys";
+        version = "0.48.0";
+        edition = "2018";
+        sha256 = "1aan23v5gs7gya1lc46hqn9mdh8yph3fhxmhxlw36pn6pqc28zb7";
+        authors = [
+          "Microsoft"
+        ];
+        dependencies = [
+          {
+            name = "windows-targets";
+            packageId = "windows-targets 0.48.5";
+          }
+        ];
+        features = {
+          "Wdk_System" = [ "Wdk" ];
+          "Wdk_System_OfflineRegistry" = [ "Wdk_System" ];
+          "Win32_Data" = [ "Win32" ];
+          "Win32_Data_HtmlHelp" = [ "Win32_Data" ];
+          "Win32_Data_RightsManagement" = [ "Win32_Data" ];
+          "Win32_Data_Xml" = [ "Win32_Data" ];
+          "Win32_Data_Xml_MsXml" = [ "Win32_Data_Xml" ];
+          "Win32_Data_Xml_XmlLite" = [ "Win32_Data_Xml" ];
+          "Win32_Devices" = [ "Win32" ];
+          "Win32_Devices_AllJoyn" = [ "Win32_Devices" ];
+          "Win32_Devices_BiometricFramework" = [ "Win32_Devices" ];
+          "Win32_Devices_Bluetooth" = [ "Win32_Devices" ];
+          "Win32_Devices_Communication" = [ "Win32_Devices" ];
+          "Win32_Devices_DeviceAccess" = [ "Win32_Devices" ];
+          "Win32_Devices_DeviceAndDriverInstallation" = [ "Win32_Devices" ];
+          "Win32_Devices_DeviceQuery" = [ "Win32_Devices" ];
+          "Win32_Devices_Display" = [ "Win32_Devices" ];
+          "Win32_Devices_Enumeration" = [ "Win32_Devices" ];
+          "Win32_Devices_Enumeration_Pnp" = [ "Win32_Devices_Enumeration" ];
+          "Win32_Devices_Fax" = [ "Win32_Devices" ];
+          "Win32_Devices_FunctionDiscovery" = [ "Win32_Devices" ];
+          "Win32_Devices_Geolocation" = [ "Win32_Devices" ];
+          "Win32_Devices_HumanInterfaceDevice" = [ "Win32_Devices" ];
+          "Win32_Devices_ImageAcquisition" = [ "Win32_Devices" ];
+          "Win32_Devices_PortableDevices" = [ "Win32_Devices" ];
+          "Win32_Devices_Properties" = [ "Win32_Devices" ];
+          "Win32_Devices_Pwm" = [ "Win32_Devices" ];
+          "Win32_Devices_Sensors" = [ "Win32_Devices" ];
+          "Win32_Devices_SerialCommunication" = [ "Win32_Devices" ];
+          "Win32_Devices_Tapi" = [ "Win32_Devices" ];
+          "Win32_Devices_Usb" = [ "Win32_Devices" ];
+          "Win32_Devices_WebServicesOnDevices" = [ "Win32_Devices" ];
+          "Win32_Foundation" = [ "Win32" ];
+          "Win32_Gaming" = [ "Win32" ];
+          "Win32_Globalization" = [ "Win32" ];
+          "Win32_Graphics" = [ "Win32" ];
+          "Win32_Graphics_Dwm" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Gdi" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Hlsl" = [ "Win32_Graphics" ];
+          "Win32_Graphics_OpenGL" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Printing" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Printing_PrintTicket" = [ "Win32_Graphics_Printing" ];
+          "Win32_Management" = [ "Win32" ];
+          "Win32_Management_MobileDeviceManagementRegistration" = [ "Win32_Management" ];
+          "Win32_Media" = [ "Win32" ];
+          "Win32_Media_Audio" = [ "Win32_Media" ];
+          "Win32_Media_Audio_Apo" = [ "Win32_Media_Audio" ];
+          "Win32_Media_Audio_DirectMusic" = [ "Win32_Media_Audio" ];
+          "Win32_Media_Audio_Endpoints" = [ "Win32_Media_Audio" ];
+          "Win32_Media_Audio_XAudio2" = [ "Win32_Media_Audio" ];
+          "Win32_Media_DeviceManager" = [ "Win32_Media" ];
+          "Win32_Media_DxMediaObjects" = [ "Win32_Media" ];
+          "Win32_Media_KernelStreaming" = [ "Win32_Media" ];
+          "Win32_Media_LibrarySharingServices" = [ "Win32_Media" ];
+          "Win32_Media_MediaPlayer" = [ "Win32_Media" ];
+          "Win32_Media_Multimedia" = [ "Win32_Media" ];
+          "Win32_Media_Speech" = [ "Win32_Media" ];
+          "Win32_Media_Streaming" = [ "Win32_Media" ];
+          "Win32_Media_WindowsMediaFormat" = [ "Win32_Media" ];
+          "Win32_NetworkManagement" = [ "Win32" ];
+          "Win32_NetworkManagement_Dhcp" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_Dns" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_InternetConnectionWizard" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_IpHelper" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_MobileBroadband" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_Multicast" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_Ndis" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_NetBios" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_NetManagement" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_NetShell" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_NetworkDiagnosticsFramework" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_NetworkPolicyServer" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_P2P" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_QoS" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_Rras" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_Snmp" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WNet" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WebDav" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WiFi" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WindowsConnectNow" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WindowsConnectionManager" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WindowsFilteringPlatform" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WindowsFirewall" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WindowsNetworkVirtualization" = [ "Win32_NetworkManagement" ];
+          "Win32_Networking" = [ "Win32" ];
+          "Win32_Networking_ActiveDirectory" = [ "Win32_Networking" ];
+          "Win32_Networking_BackgroundIntelligentTransferService" = [ "Win32_Networking" ];
+          "Win32_Networking_Clustering" = [ "Win32_Networking" ];
+          "Win32_Networking_HttpServer" = [ "Win32_Networking" ];
+          "Win32_Networking_Ldap" = [ "Win32_Networking" ];
+          "Win32_Networking_NetworkListManager" = [ "Win32_Networking" ];
+          "Win32_Networking_RemoteDifferentialCompression" = [ "Win32_Networking" ];
+          "Win32_Networking_WebSocket" = [ "Win32_Networking" ];
+          "Win32_Networking_WinHttp" = [ "Win32_Networking" ];
+          "Win32_Networking_WinInet" = [ "Win32_Networking" ];
+          "Win32_Networking_WinSock" = [ "Win32_Networking" ];
+          "Win32_Networking_WindowsWebServices" = [ "Win32_Networking" ];
+          "Win32_Security" = [ "Win32" ];
+          "Win32_Security_AppLocker" = [ "Win32_Security" ];
+          "Win32_Security_Authentication" = [ "Win32_Security" ];
+          "Win32_Security_Authentication_Identity" = [ "Win32_Security_Authentication" ];
+          "Win32_Security_Authentication_Identity_Provider" = [ "Win32_Security_Authentication_Identity" ];
+          "Win32_Security_Authorization" = [ "Win32_Security" ];
+          "Win32_Security_Authorization_UI" = [ "Win32_Security_Authorization" ];
+          "Win32_Security_ConfigurationSnapin" = [ "Win32_Security" ];
+          "Win32_Security_Credentials" = [ "Win32_Security" ];
+          "Win32_Security_Cryptography" = [ "Win32_Security" ];
+          "Win32_Security_Cryptography_Catalog" = [ "Win32_Security_Cryptography" ];
+          "Win32_Security_Cryptography_Certificates" = [ "Win32_Security_Cryptography" ];
+          "Win32_Security_Cryptography_Sip" = [ "Win32_Security_Cryptography" ];
+          "Win32_Security_Cryptography_UI" = [ "Win32_Security_Cryptography" ];
+          "Win32_Security_DiagnosticDataQuery" = [ "Win32_Security" ];
+          "Win32_Security_DirectoryServices" = [ "Win32_Security" ];
+          "Win32_Security_EnterpriseData" = [ "Win32_Security" ];
+          "Win32_Security_ExtensibleAuthenticationProtocol" = [ "Win32_Security" ];
+          "Win32_Security_Isolation" = [ "Win32_Security" ];
+          "Win32_Security_LicenseProtection" = [ "Win32_Security" ];
+          "Win32_Security_NetworkAccessProtection" = [ "Win32_Security" ];
+          "Win32_Security_Tpm" = [ "Win32_Security" ];
+          "Win32_Security_WinTrust" = [ "Win32_Security" ];
+          "Win32_Security_WinWlx" = [ "Win32_Security" ];
+          "Win32_Storage" = [ "Win32" ];
+          "Win32_Storage_Cabinets" = [ "Win32_Storage" ];
+          "Win32_Storage_CloudFilters" = [ "Win32_Storage" ];
+          "Win32_Storage_Compression" = [ "Win32_Storage" ];
+          "Win32_Storage_DataDeduplication" = [ "Win32_Storage" ];
+          "Win32_Storage_DistributedFileSystem" = [ "Win32_Storage" ];
+          "Win32_Storage_EnhancedStorage" = [ "Win32_Storage" ];
+          "Win32_Storage_FileHistory" = [ "Win32_Storage" ];
+          "Win32_Storage_FileServerResourceManager" = [ "Win32_Storage" ];
+          "Win32_Storage_FileSystem" = [ "Win32_Storage" ];
+          "Win32_Storage_Imapi" = [ "Win32_Storage" ];
+          "Win32_Storage_IndexServer" = [ "Win32_Storage" ];
+          "Win32_Storage_InstallableFileSystems" = [ "Win32_Storage" ];
+          "Win32_Storage_IscsiDisc" = [ "Win32_Storage" ];
+          "Win32_Storage_Jet" = [ "Win32_Storage" ];
+          "Win32_Storage_OfflineFiles" = [ "Win32_Storage" ];
+          "Win32_Storage_OperationRecorder" = [ "Win32_Storage" ];
+          "Win32_Storage_Packaging" = [ "Win32_Storage" ];
+          "Win32_Storage_Packaging_Appx" = [ "Win32_Storage_Packaging" ];
+          "Win32_Storage_Packaging_Opc" = [ "Win32_Storage_Packaging" ];
+          "Win32_Storage_ProjectedFileSystem" = [ "Win32_Storage" ];
+          "Win32_Storage_StructuredStorage" = [ "Win32_Storage" ];
+          "Win32_Storage_Vhd" = [ "Win32_Storage" ];
+          "Win32_Storage_VirtualDiskService" = [ "Win32_Storage" ];
+          "Win32_Storage_Vss" = [ "Win32_Storage" ];
+          "Win32_Storage_Xps" = [ "Win32_Storage" ];
+          "Win32_Storage_Xps_Printing" = [ "Win32_Storage_Xps" ];
+          "Win32_System" = [ "Win32" ];
+          "Win32_System_AddressBook" = [ "Win32_System" ];
+          "Win32_System_Antimalware" = [ "Win32_System" ];
+          "Win32_System_ApplicationInstallationAndServicing" = [ "Win32_System" ];
+          "Win32_System_ApplicationVerifier" = [ "Win32_System" ];
+          "Win32_System_AssessmentTool" = [ "Win32_System" ];
+          "Win32_System_ClrHosting" = [ "Win32_System" ];
+          "Win32_System_Com" = [ "Win32_System" ];
+          "Win32_System_Com_CallObj" = [ "Win32_System_Com" ];
+          "Win32_System_Com_ChannelCredentials" = [ "Win32_System_Com" ];
+          "Win32_System_Com_Events" = [ "Win32_System_Com" ];
+          "Win32_System_Com_Marshal" = [ "Win32_System_Com" ];
+          "Win32_System_Com_StructuredStorage" = [ "Win32_System_Com" ];
+          "Win32_System_Com_UI" = [ "Win32_System_Com" ];
+          "Win32_System_Com_Urlmon" = [ "Win32_System_Com" ];
+          "Win32_System_ComponentServices" = [ "Win32_System" ];
+          "Win32_System_Console" = [ "Win32_System" ];
+          "Win32_System_Contacts" = [ "Win32_System" ];
+          "Win32_System_CorrelationVector" = [ "Win32_System" ];
+          "Win32_System_DataExchange" = [ "Win32_System" ];
+          "Win32_System_DeploymentServices" = [ "Win32_System" ];
+          "Win32_System_DesktopSharing" = [ "Win32_System" ];
+          "Win32_System_DeveloperLicensing" = [ "Win32_System" ];
+          "Win32_System_Diagnostics" = [ "Win32_System" ];
+          "Win32_System_Diagnostics_Ceip" = [ "Win32_System_Diagnostics" ];
+          "Win32_System_Diagnostics_ClrProfiling" = [ "Win32_System_Diagnostics" ];
+          "Win32_System_Diagnostics_Debug" = [ "Win32_System_Diagnostics" ];
+          "Win32_System_Diagnostics_Debug_ActiveScript" = [ "Win32_System_Diagnostics_Debug" ];
+          "Win32_System_Diagnostics_Debug_Extensions" = [ "Win32_System_Diagnostics_Debug" ];
+          "Win32_System_Diagnostics_Etw" = [ "Win32_System_Diagnostics" ];
+          "Win32_System_Diagnostics_ProcessSnapshotting" = [ "Win32_System_Diagnostics" ];
+          "Win32_System_Diagnostics_ToolHelp" = [ "Win32_System_Diagnostics" ];
+          "Win32_System_DistributedTransactionCoordinator" = [ "Win32_System" ];
+          "Win32_System_Environment" = [ "Win32_System" ];
+          "Win32_System_ErrorReporting" = [ "Win32_System" ];
+          "Win32_System_EventCollector" = [ "Win32_System" ];
+          "Win32_System_EventLog" = [ "Win32_System" ];
+          "Win32_System_EventNotificationService" = [ "Win32_System" ];
+          "Win32_System_GroupPolicy" = [ "Win32_System" ];
+          "Win32_System_HostCompute" = [ "Win32_System" ];
+          "Win32_System_HostComputeNetwork" = [ "Win32_System" ];
+          "Win32_System_HostComputeSystem" = [ "Win32_System" ];
+          "Win32_System_Hypervisor" = [ "Win32_System" ];
+          "Win32_System_IO" = [ "Win32_System" ];
+          "Win32_System_Iis" = [ "Win32_System" ];
+          "Win32_System_Ioctl" = [ "Win32_System" ];
+          "Win32_System_JobObjects" = [ "Win32_System" ];
+          "Win32_System_Js" = [ "Win32_System" ];
+          "Win32_System_Kernel" = [ "Win32_System" ];
+          "Win32_System_LibraryLoader" = [ "Win32_System" ];
+          "Win32_System_Mailslots" = [ "Win32_System" ];
+          "Win32_System_Mapi" = [ "Win32_System" ];
+          "Win32_System_Memory" = [ "Win32_System" ];
+          "Win32_System_Memory_NonVolatile" = [ "Win32_System_Memory" ];
+          "Win32_System_MessageQueuing" = [ "Win32_System" ];
+          "Win32_System_MixedReality" = [ "Win32_System" ];
+          "Win32_System_Mmc" = [ "Win32_System" ];
+          "Win32_System_Ole" = [ "Win32_System" ];
+          "Win32_System_ParentalControls" = [ "Win32_System" ];
+          "Win32_System_PasswordManagement" = [ "Win32_System" ];
+          "Win32_System_Performance" = [ "Win32_System" ];
+          "Win32_System_Performance_HardwareCounterProfiling" = [ "Win32_System_Performance" ];
+          "Win32_System_Pipes" = [ "Win32_System" ];
+          "Win32_System_Power" = [ "Win32_System" ];
+          "Win32_System_ProcessStatus" = [ "Win32_System" ];
+          "Win32_System_RealTimeCommunications" = [ "Win32_System" ];
+          "Win32_System_Recovery" = [ "Win32_System" ];
+          "Win32_System_Registry" = [ "Win32_System" ];
+          "Win32_System_RemoteAssistance" = [ "Win32_System" ];
+          "Win32_System_RemoteDesktop" = [ "Win32_System" ];
+          "Win32_System_RemoteManagement" = [ "Win32_System" ];
+          "Win32_System_RestartManager" = [ "Win32_System" ];
+          "Win32_System_Restore" = [ "Win32_System" ];
+          "Win32_System_Rpc" = [ "Win32_System" ];
+          "Win32_System_Search" = [ "Win32_System" ];
+          "Win32_System_Search_Common" = [ "Win32_System_Search" ];
+          "Win32_System_SecurityCenter" = [ "Win32_System" ];
+          "Win32_System_ServerBackup" = [ "Win32_System" ];
+          "Win32_System_Services" = [ "Win32_System" ];
+          "Win32_System_SettingsManagementInfrastructure" = [ "Win32_System" ];
+          "Win32_System_SetupAndMigration" = [ "Win32_System" ];
+          "Win32_System_Shutdown" = [ "Win32_System" ];
+          "Win32_System_StationsAndDesktops" = [ "Win32_System" ];
+          "Win32_System_SubsystemForLinux" = [ "Win32_System" ];
+          "Win32_System_SystemInformation" = [ "Win32_System" ];
+          "Win32_System_SystemServices" = [ "Win32_System" ];
+          "Win32_System_TaskScheduler" = [ "Win32_System" ];
+          "Win32_System_Threading" = [ "Win32_System" ];
+          "Win32_System_Time" = [ "Win32_System" ];
+          "Win32_System_TpmBaseServices" = [ "Win32_System" ];
+          "Win32_System_UpdateAgent" = [ "Win32_System" ];
+          "Win32_System_UpdateAssessment" = [ "Win32_System" ];
+          "Win32_System_UserAccessLogging" = [ "Win32_System" ];
+          "Win32_System_VirtualDosMachines" = [ "Win32_System" ];
+          "Win32_System_WindowsProgramming" = [ "Win32_System" ];
+          "Win32_System_WindowsSync" = [ "Win32_System" ];
+          "Win32_System_Wmi" = [ "Win32_System" ];
+          "Win32_UI" = [ "Win32" ];
+          "Win32_UI_Accessibility" = [ "Win32_UI" ];
+          "Win32_UI_Animation" = [ "Win32_UI" ];
+          "Win32_UI_ColorSystem" = [ "Win32_UI" ];
+          "Win32_UI_Controls" = [ "Win32_UI" ];
+          "Win32_UI_Controls_Dialogs" = [ "Win32_UI_Controls" ];
+          "Win32_UI_Controls_RichEdit" = [ "Win32_UI_Controls" ];
+          "Win32_UI_HiDpi" = [ "Win32_UI" ];
+          "Win32_UI_Input" = [ "Win32_UI" ];
+          "Win32_UI_Input_Ime" = [ "Win32_UI_Input" ];
+          "Win32_UI_Input_Ink" = [ "Win32_UI_Input" ];
+          "Win32_UI_Input_KeyboardAndMouse" = [ "Win32_UI_Input" ];
+          "Win32_UI_Input_Pointer" = [ "Win32_UI_Input" ];
+          "Win32_UI_Input_Radial" = [ "Win32_UI_Input" ];
+          "Win32_UI_Input_Touch" = [ "Win32_UI_Input" ];
+          "Win32_UI_Input_XboxController" = [ "Win32_UI_Input" ];
+          "Win32_UI_InteractionContext" = [ "Win32_UI" ];
+          "Win32_UI_LegacyWindowsEnvironmentFeatures" = [ "Win32_UI" ];
+          "Win32_UI_Magnification" = [ "Win32_UI" ];
+          "Win32_UI_Notifications" = [ "Win32_UI" ];
+          "Win32_UI_Ribbon" = [ "Win32_UI" ];
+          "Win32_UI_Shell" = [ "Win32_UI" ];
+          "Win32_UI_Shell_Common" = [ "Win32_UI_Shell" ];
+          "Win32_UI_Shell_PropertiesSystem" = [ "Win32_UI_Shell" ];
+          "Win32_UI_TabletPC" = [ "Win32_UI" ];
+          "Win32_UI_TextServices" = [ "Win32_UI" ];
+          "Win32_UI_WindowsAndMessaging" = [ "Win32_UI" ];
+          "Win32_UI_Wpf" = [ "Win32_UI" ];
+          "Win32_Web" = [ "Win32" ];
+          "Win32_Web_InternetExplorer" = [ "Win32_Web" ];
+        };
+        resolvedDefaultFeatures = [ "Win32" "Win32_Foundation" "Win32_NetworkManagement" "Win32_NetworkManagement_IpHelper" "Win32_Networking" "Win32_Networking_WinSock" "Win32_Security" "Win32_Storage" "Win32_Storage_FileSystem" "Win32_System" "Win32_System_Diagnostics" "Win32_System_Diagnostics_Debug" "Win32_System_IO" "Win32_System_Pipes" "Win32_System_SystemServices" "Win32_System_Threading" "Win32_System_WindowsProgramming" "Win32_UI" "Win32_UI_Shell" "default" ];
+      };
+      "windows-targets 0.48.5" = rec {
+        crateName = "windows-targets";
+        version = "0.48.5";
+        edition = "2018";
+        sha256 = "034ljxqshifs1lan89xwpcy1hp0lhdh4b5n0d2z4fwjx2piacbws";
+        authors = [
+          "Microsoft"
+        ];
+        dependencies = [
+          {
+            name = "windows_aarch64_gnullvm";
+            packageId = "windows_aarch64_gnullvm 0.48.5";
+            target = { target, features }: (pkgs.rust.lib.toRustTarget stdenv.hostPlatform == "aarch64-pc-windows-gnullvm");
+          }
+          {
+            name = "windows_aarch64_msvc";
+            packageId = "windows_aarch64_msvc 0.48.5";
+            target = { target, features }: (("aarch64" == target."arch" or null) && ("msvc" == target."env" or null) && (!(target."windows_raw_dylib" or false)));
+          }
+          {
+            name = "windows_i686_gnu";
+            packageId = "windows_i686_gnu 0.48.5";
+            target = { target, features }: (("x86" == target."arch" or null) && ("gnu" == target."env" or null) && (!(target."windows_raw_dylib" or false)));
+          }
+          {
+            name = "windows_i686_msvc";
+            packageId = "windows_i686_msvc 0.48.5";
+            target = { target, features }: (("x86" == target."arch" or null) && ("msvc" == target."env" or null) && (!(target."windows_raw_dylib" or false)));
+          }
+          {
+            name = "windows_x86_64_gnu";
+            packageId = "windows_x86_64_gnu 0.48.5";
+            target = { target, features }: (("x86_64" == target."arch" or null) && ("gnu" == target."env" or null) && (!("llvm" == target."abi" or null)) && (!(target."windows_raw_dylib" or false)));
+          }
+          {
+            name = "windows_x86_64_gnullvm";
+            packageId = "windows_x86_64_gnullvm 0.48.5";
+            target = { target, features }: (pkgs.rust.lib.toRustTarget stdenv.hostPlatform == "x86_64-pc-windows-gnullvm");
+          }
+          {
+            name = "windows_x86_64_msvc";
+            packageId = "windows_x86_64_msvc 0.48.5";
+            target = { target, features }: (("x86_64" == target."arch" or null) && ("msvc" == target."env" or null) && (!(target."windows_raw_dylib" or false)));
+          }
+        ];
+
+      };
+      "windows-targets 0.52.0" = rec {
+        crateName = "windows-targets";
+        version = "0.52.0";
+        edition = "2021";
+        sha256 = "1kg7a27ynzw8zz3krdgy6w5gbqcji27j1sz4p7xk2j5j8082064a";
+        authors = [
+          "Microsoft"
+        ];
+        dependencies = [
+          {
+            name = "windows_aarch64_gnullvm";
+            packageId = "windows_aarch64_gnullvm 0.52.0";
+            target = { target, features }: (pkgs.rust.lib.toRustTarget stdenv.hostPlatform == "aarch64-pc-windows-gnullvm");
+          }
+          {
+            name = "windows_aarch64_msvc";
+            packageId = "windows_aarch64_msvc 0.52.0";
+            target = { target, features }: (("aarch64" == target."arch" or null) && ("msvc" == target."env" or null) && (!(target."windows_raw_dylib" or false)));
+          }
+          {
+            name = "windows_i686_gnu";
+            packageId = "windows_i686_gnu 0.52.0";
+            target = { target, features }: (("x86" == target."arch" or null) && ("gnu" == target."env" or null) && (!(target."windows_raw_dylib" or false)));
+          }
+          {
+            name = "windows_i686_msvc";
+            packageId = "windows_i686_msvc 0.52.0";
+            target = { target, features }: (("x86" == target."arch" or null) && ("msvc" == target."env" or null) && (!(target."windows_raw_dylib" or false)));
+          }
+          {
+            name = "windows_x86_64_gnu";
+            packageId = "windows_x86_64_gnu 0.52.0";
+            target = { target, features }: (("x86_64" == target."arch" or null) && ("gnu" == target."env" or null) && (!("llvm" == target."abi" or null)) && (!(target."windows_raw_dylib" or false)));
+          }
+          {
+            name = "windows_x86_64_gnullvm";
+            packageId = "windows_x86_64_gnullvm 0.52.0";
+            target = { target, features }: (pkgs.rust.lib.toRustTarget stdenv.hostPlatform == "x86_64-pc-windows-gnullvm");
+          }
+          {
+            name = "windows_x86_64_msvc";
+            packageId = "windows_x86_64_msvc 0.52.0";
+            target = { target, features }: (("x86_64" == target."arch" or null) && ("msvc" == target."env" or null) && (!(target."windows_raw_dylib" or false)));
+          }
+        ];
+
+      };
+      "windows_aarch64_gnullvm 0.48.5" = rec {
+        crateName = "windows_aarch64_gnullvm";
+        version = "0.48.5";
+        edition = "2018";
+        sha256 = "1n05v7qblg1ci3i567inc7xrkmywczxrs1z3lj3rkkxw18py6f1b";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "windows_aarch64_gnullvm 0.52.0" = rec {
+        crateName = "windows_aarch64_gnullvm";
+        version = "0.52.0";
+        edition = "2021";
+        sha256 = "1shmn1kbdc0bpphcxz0vlph96bxz0h1jlmh93s9agf2dbpin8xyb";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "windows_aarch64_msvc 0.48.5" = rec {
+        crateName = "windows_aarch64_msvc";
+        version = "0.48.5";
+        edition = "2018";
+        sha256 = "1g5l4ry968p73g6bg6jgyvy9lb8fyhcs54067yzxpcpkf44k2dfw";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "windows_aarch64_msvc 0.52.0" = rec {
+        crateName = "windows_aarch64_msvc";
+        version = "0.52.0";
+        edition = "2021";
+        sha256 = "1vvmy1ypvzdvxn9yf0b8ygfl85gl2gpcyvsvqppsmlpisil07amv";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "windows_i686_gnu 0.48.5" = rec {
+        crateName = "windows_i686_gnu";
+        version = "0.48.5";
+        edition = "2018";
+        sha256 = "0gklnglwd9ilqx7ac3cn8hbhkraqisd0n83jxzf9837nvvkiand7";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "windows_i686_gnu 0.52.0" = rec {
+        crateName = "windows_i686_gnu";
+        version = "0.52.0";
+        edition = "2021";
+        sha256 = "04zkglz4p3pjsns5gbz85v4s5aw102raz4spj4b0lmm33z5kg1m2";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "windows_i686_msvc 0.48.5" = rec {
+        crateName = "windows_i686_msvc";
+        version = "0.48.5";
+        edition = "2018";
+        sha256 = "01m4rik437dl9rdf0ndnm2syh10hizvq0dajdkv2fjqcywrw4mcg";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "windows_i686_msvc 0.52.0" = rec {
+        crateName = "windows_i686_msvc";
+        version = "0.52.0";
+        edition = "2021";
+        sha256 = "16kvmbvx0vr0zbgnaz6nsks9ycvfh5xp05bjrhq65kj623iyirgz";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "windows_x86_64_gnu 0.48.5" = rec {
+        crateName = "windows_x86_64_gnu";
+        version = "0.48.5";
+        edition = "2018";
+        sha256 = "13kiqqcvz2vnyxzydjh73hwgigsdr2z1xpzx313kxll34nyhmm2k";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "windows_x86_64_gnu 0.52.0" = rec {
+        crateName = "windows_x86_64_gnu";
+        version = "0.52.0";
+        edition = "2021";
+        sha256 = "1zdy4qn178sil5sdm63lm7f0kkcjg6gvdwmcprd2yjmwn8ns6vrx";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "windows_x86_64_gnullvm 0.48.5" = rec {
+        crateName = "windows_x86_64_gnullvm";
+        version = "0.48.5";
+        edition = "2018";
+        sha256 = "1k24810wfbgz8k48c2yknqjmiigmql6kk3knmddkv8k8g1v54yqb";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "windows_x86_64_gnullvm 0.52.0" = rec {
+        crateName = "windows_x86_64_gnullvm";
+        version = "0.52.0";
+        edition = "2021";
+        sha256 = "17lllq4l2k1lqgcnw1cccphxp9vs7inq99kjlm2lfl9zklg7wr8s";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "windows_x86_64_msvc 0.48.5" = rec {
+        crateName = "windows_x86_64_msvc";
+        version = "0.48.5";
+        edition = "2018";
+        sha256 = "0f4mdp895kkjh9zv8dxvn4pc10xr7839lf5pa9l0193i2pkgr57d";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "windows_x86_64_msvc 0.52.0" = rec {
+        crateName = "windows_x86_64_msvc";
+        version = "0.52.0";
+        edition = "2021";
+        sha256 = "012wfq37f18c09ij5m6rniw7xxn5fcvrxbqd0wd8vgnl3hfn9yfz";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "xxhash-rust" = rec {
+        crateName = "xxhash-rust";
+        version = "0.8.7";
+        edition = "2018";
+        sha256 = "0yz037yrkn0qa0g0r6733ynd1xbw7zvx58v6qylhyi2kv9wb2a4q";
+        authors = [
+          "Douman <douman@gmx.se>"
+        ];
+        features = { };
+        resolvedDefaultFeatures = [ "xxh3" ];
+      };
+      "zerocopy" = rec {
+        crateName = "zerocopy";
+        version = "0.7.25";
+        edition = "2018";
+        sha256 = "0mv5w4fq1kcpw1ydcb5cvr8zdms5pqy0r60g04ayzpqfgjk6klwc";
+        authors = [
+          "Joshua Liebow-Feeser <joshlf@google.com>"
+        ];
+        dependencies = [
+          {
+            name = "zerocopy-derive";
+            packageId = "zerocopy-derive";
+            optional = true;
+          }
+          {
+            name = "zerocopy-derive";
+            packageId = "zerocopy-derive";
+            target = { target, features }: false;
+          }
+        ];
+        devDependencies = [
+          {
+            name = "zerocopy-derive";
+            packageId = "zerocopy-derive";
+          }
+        ];
+        features = {
+          "__internal_use_only_features_that_work_on_stable" = [ "alloc" "derive" "simd" ];
+          "byteorder" = [ "dep:byteorder" ];
+          "default" = [ "byteorder" ];
+          "derive" = [ "zerocopy-derive" ];
+          "simd-nightly" = [ "simd" ];
+          "zerocopy-derive" = [ "dep:zerocopy-derive" ];
+        };
+        resolvedDefaultFeatures = [ "simd" ];
+      };
+      "zerocopy-derive" = rec {
+        crateName = "zerocopy-derive";
+        version = "0.7.25";
+        edition = "2018";
+        sha256 = "0svxr32pp4lav1vjar127g2r09gpiajxn0yv1k66r8hrlayl1wf2";
+        procMacro = true;
+        authors = [
+          "Joshua Liebow-Feeser <joshlf@google.com>"
+        ];
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn 2.0.39";
+          }
+        ];
+
+      };
+      "zeroize" = rec {
+        crateName = "zeroize";
+        version = "1.7.0";
+        edition = "2021";
+        sha256 = "0bfvby7k9pdp6623p98yz2irqnamcyzpn7zh20nqmdn68b0lwnsj";
+        authors = [
+          "The RustCrypto Project Developers"
+        ];
+        features = {
+          "default" = [ "alloc" ];
+          "derive" = [ "zeroize_derive" ];
+          "serde" = [ "dep:serde" ];
+          "std" = [ "alloc" ];
+          "zeroize_derive" = [ "dep:zeroize_derive" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" ];
+      };
+      "zstd" = rec {
+        crateName = "zstd";
+        version = "0.13.0";
+        edition = "2018";
+        sha256 = "0401q54s9r35x2i7m1kwppgkj79g0pb6xz3xpby7qlkdb44k7yxz";
+        authors = [
+          "Alexandre Bury <alexandre.bury@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "zstd-safe";
+            packageId = "zstd-safe";
+            usesDefaultFeatures = false;
+            features = [ "std" ];
+          }
+        ];
+        features = {
+          "arrays" = [ "zstd-safe/arrays" ];
+          "bindgen" = [ "zstd-safe/bindgen" ];
+          "debug" = [ "zstd-safe/debug" ];
+          "default" = [ "legacy" "arrays" "zdict_builder" ];
+          "experimental" = [ "zstd-safe/experimental" ];
+          "fat-lto" = [ "zstd-safe/fat-lto" ];
+          "legacy" = [ "zstd-safe/legacy" ];
+          "no_asm" = [ "zstd-safe/no_asm" ];
+          "pkg-config" = [ "zstd-safe/pkg-config" ];
+          "thin" = [ "zstd-safe/thin" ];
+          "thin-lto" = [ "zstd-safe/thin-lto" ];
+          "zdict_builder" = [ "zstd-safe/zdict_builder" ];
+          "zstdmt" = [ "zstd-safe/zstdmt" ];
+        };
+        resolvedDefaultFeatures = [ "arrays" "default" "legacy" "zdict_builder" ];
+      };
+      "zstd-safe" = rec {
+        crateName = "zstd-safe";
+        version = "7.0.0";
+        edition = "2018";
+        sha256 = "0gpav2lcibrpmyslmjkcn3w0w64qif3jjljd2h8lr4p249s7qx23";
+        authors = [
+          "Alexandre Bury <alexandre.bury@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "zstd-sys";
+            packageId = "zstd-sys";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "bindgen" = [ "zstd-sys/bindgen" ];
+          "debug" = [ "zstd-sys/debug" ];
+          "default" = [ "legacy" "arrays" "zdict_builder" ];
+          "experimental" = [ "zstd-sys/experimental" ];
+          "fat-lto" = [ "zstd-sys/fat-lto" ];
+          "legacy" = [ "zstd-sys/legacy" ];
+          "no_asm" = [ "zstd-sys/no_asm" ];
+          "pkg-config" = [ "zstd-sys/pkg-config" ];
+          "std" = [ "zstd-sys/std" ];
+          "thin" = [ "zstd-sys/thin" ];
+          "thin-lto" = [ "zstd-sys/thin-lto" ];
+          "zdict_builder" = [ "zstd-sys/zdict_builder" ];
+          "zstdmt" = [ "zstd-sys/zstdmt" ];
+        };
+        resolvedDefaultFeatures = [ "arrays" "legacy" "std" "zdict_builder" ];
+      };
+      "zstd-sys" = rec {
+        crateName = "zstd-sys";
+        version = "2.0.9+zstd.1.5.5";
+        edition = "2018";
+        links = "zstd";
+        sha256 = "0mk6a2367swdi22zg03lcackpnvgq96d7120awd4i83lm2lfy5ly";
+        authors = [
+          "Alexandre Bury <alexandre.bury@gmail.com>"
+        ];
+        buildDependencies = [
+          {
+            name = "cc";
+            packageId = "cc";
+            features = [ "parallel" ];
+          }
+          {
+            name = "pkg-config";
+            packageId = "pkg-config";
+          }
+        ];
+        features = {
+          "bindgen" = [ "dep:bindgen" ];
+          "default" = [ "legacy" "zdict_builder" ];
+        };
+        resolvedDefaultFeatures = [ "legacy" "std" "zdict_builder" ];
+      };
+    };
+
+    #
+    # crate2nix/default.nix (excerpt start)
+    #
+
+    /* Target (platform) data for conditional dependencies.
+      This corresponds roughly to what buildRustCrate is setting.
+    */
+    makeDefaultTarget = platform: {
+      unix = platform.isUnix;
+      windows = platform.isWindows;
+      fuchsia = true;
+      test = false;
+
+      /* We are choosing an arbitrary rust version to grab `lib` from,
+      which is unfortunate, but `lib` has been version-agnostic the
+      whole time so this is good enough for now.
+      */
+      os = pkgs.rust.lib.toTargetOs platform;
+      arch = pkgs.rust.lib.toTargetArch platform;
+      family = pkgs.rust.lib.toTargetFamily platform;
+      vendor = pkgs.rust.lib.toTargetVendor platform;
+      env = "gnu";
+      endian =
+        if platform.parsed.cpu.significantByte.name == "littleEndian"
+        then "little" else "big";
+      pointer_width = toString platform.parsed.cpu.bits;
+      debug_assertions = false;
+    };
+
+    /* Filters common temp files and build files. */
+    # TODO(pkolloch): Substitute with gitignore filter
+    sourceFilter = name: type:
+      let
+        baseName = builtins.baseNameOf (builtins.toString name);
+      in
+        ! (
+          # Filter out git
+          baseName == ".gitignore"
+          || (type == "directory" && baseName == ".git")
+
+          # Filter out build results
+          || (
+            type == "directory" && (
+              baseName == "target"
+              || baseName == "_site"
+              || baseName == ".sass-cache"
+              || baseName == ".jekyll-metadata"
+              || baseName == "build-artifacts"
+            )
+          )
+
+          # Filter out nix-build result symlinks
+          || (
+            type == "symlink" && lib.hasPrefix "result" baseName
+          )
+
+          # Filter out IDE config
+          || (
+            type == "directory" && (
+              baseName == ".idea" || baseName == ".vscode"
+            )
+          ) || lib.hasSuffix ".iml" baseName
+
+          # Filter out nix build files
+          || baseName == "Cargo.nix"
+
+          # Filter out editor backup / swap files.
+          || lib.hasSuffix "~" baseName
+          || builtins.match "^\\.sw[a-z]$$" baseName != null
+          || builtins.match "^\\..*\\.sw[a-z]$$" baseName != null
+          || lib.hasSuffix ".tmp" baseName
+          || lib.hasSuffix ".bak" baseName
+          || baseName == "tests.nix"
+        );
+
+    /* Returns a crate which depends on successful test execution
+      of crate given as the second argument.
+
+      testCrateFlags: list of flags to pass to the test exectuable
+      testInputs: list of packages that should be available during test execution
+    */
+    crateWithTest = { crate, testCrate, testCrateFlags, testInputs, testPreRun, testPostRun }:
+      assert builtins.typeOf testCrateFlags == "list";
+      assert builtins.typeOf testInputs == "list";
+      assert builtins.typeOf testPreRun == "string";
+      assert builtins.typeOf testPostRun == "string";
+      let
+        # override the `crate` so that it will build and execute tests instead of
+        # building the actual lib and bin targets We just have to pass `--test`
+        # to rustc and it will do the right thing.  We execute the tests and copy
+        # their log and the test executables to $out for later inspection.
+        test =
+          let
+            drv = testCrate.override
+              (
+                _: {
+                  buildTests = true;
+                  release = false;
+                }
+              );
+            # If the user hasn't set any pre/post commands, we don't want to
+            # insert empty lines. This means that any existing users of crate2nix
+            # don't get a spurious rebuild unless they set these explicitly.
+            testCommand = pkgs.lib.concatStringsSep "\n"
+              (pkgs.lib.filter (s: s != "") [
+                testPreRun
+                "$f $testCrateFlags 2>&1 | tee -a $out"
+                testPostRun
+              ]);
+          in
+          pkgs.runCommand "run-tests-${testCrate.name}"
+            {
+              inherit testCrateFlags;
+              buildInputs = testInputs;
+            } ''
+            set -e
+
+            export RUST_BACKTRACE=1
+
+            # recreate a file hierarchy as when running tests with cargo
+
+            # the source for test data
+            # It's necessary to locate the source in $NIX_BUILD_TOP/source/
+            # instead of $NIX_BUILD_TOP/
+            # because we compiled those test binaries in the former and not the latter.
+            # So all paths will expect source tree to be there and not in the build top directly.
+            # For example: $NIX_BUILD_TOP := /build in general, if you ask yourself.
+            # TODO(raitobezarius): I believe there could be more edge cases if `crate.sourceRoot`
+            # do exist but it's very hard to reason about them, so let's wait until the first bug report.
+            mkdir -p source/
+            cd source/
+
+            ${pkgs.buildPackages.xorg.lndir}/bin/lndir ${crate.src}
+
+            # build outputs
+            testRoot=target/debug
+            mkdir -p $testRoot
+
+            # executables of the crate
+            # we copy to prevent std::env::current_exe() to resolve to a store location
+            for i in ${crate}/bin/*; do
+              cp "$i" "$testRoot"
+            done
+            chmod +w -R .
+
+            # test harness executables are suffixed with a hash, like cargo does
+            # this allows to prevent name collision with the main
+            # executables of the crate
+            hash=$(basename $out)
+            for file in ${drv}/tests/*; do
+              f=$testRoot/$(basename $file)-$hash
+              cp $file $f
+              ${testCommand}
+            done
+          '';
+      in
+      pkgs.runCommand "${crate.name}-linked"
+        {
+          inherit (crate) outputs crateName;
+          passthru = (crate.passthru or { }) // {
+            inherit test;
+          };
+        }
+        (lib.optionalString (stdenv.buildPlatform.canExecute stdenv.hostPlatform) ''
+          echo tested by ${test}
+        '' + ''
+          ${lib.concatMapStringsSep "\n" (output: "ln -s ${crate.${output}} ${"$"}${output}") crate.outputs}
+        '');
+
+    /* A restricted overridable version of builtRustCratesWithFeatures. */
+    buildRustCrateWithFeatures =
+      { packageId
+      , features ? rootFeatures
+      , crateOverrides ? defaultCrateOverrides
+      , buildRustCrateForPkgsFunc ? null
+      , runTests ? false
+      , testCrateFlags ? [ ]
+      , testInputs ? [ ]
+        # Any command to run immediatelly before a test is executed.
+      , testPreRun ? ""
+        # Any command run immediatelly after a test is executed.
+      , testPostRun ? ""
+      }:
+      lib.makeOverridable
+        (
+          { features
+          , crateOverrides
+          , runTests
+          , testCrateFlags
+          , testInputs
+          , testPreRun
+          , testPostRun
+          }:
+          let
+            buildRustCrateForPkgsFuncOverriden =
+              if buildRustCrateForPkgsFunc != null
+              then buildRustCrateForPkgsFunc
+              else
+                (
+                  if crateOverrides == pkgs.defaultCrateOverrides
+                  then buildRustCrateForPkgs
+                  else
+                    pkgs: (buildRustCrateForPkgs pkgs).override {
+                      defaultCrateOverrides = crateOverrides;
+                    }
+                );
+            builtRustCrates = builtRustCratesWithFeatures {
+              inherit packageId features;
+              buildRustCrateForPkgsFunc = buildRustCrateForPkgsFuncOverriden;
+              runTests = false;
+            };
+            builtTestRustCrates = builtRustCratesWithFeatures {
+              inherit packageId features;
+              buildRustCrateForPkgsFunc = buildRustCrateForPkgsFuncOverriden;
+              runTests = true;
+            };
+            drv = builtRustCrates.crates.${packageId};
+            testDrv = builtTestRustCrates.crates.${packageId};
+            derivation =
+              if runTests then
+                crateWithTest
+                  {
+                    crate = drv;
+                    testCrate = testDrv;
+                    inherit testCrateFlags testInputs testPreRun testPostRun;
+                  }
+              else drv;
+          in
+          derivation
+        )
+        { inherit features crateOverrides runTests testCrateFlags testInputs testPreRun testPostRun; };
+
+    /* Returns an attr set with packageId mapped to the result of buildRustCrateForPkgsFunc
+      for the corresponding crate.
+    */
+    builtRustCratesWithFeatures =
+      { packageId
+      , features
+      , crateConfigs ? crates
+      , buildRustCrateForPkgsFunc
+      , runTests
+      , makeTarget ? makeDefaultTarget
+      } @ args:
+        assert (builtins.isAttrs crateConfigs);
+        assert (builtins.isString packageId);
+        assert (builtins.isList features);
+        assert (builtins.isAttrs (makeTarget stdenv.hostPlatform));
+        assert (builtins.isBool runTests);
+        let
+          rootPackageId = packageId;
+          mergedFeatures = mergePackageFeatures
+            (
+              args // {
+                inherit rootPackageId;
+                target = makeTarget stdenv.hostPlatform // { test = runTests; };
+              }
+            );
+          # Memoize built packages so that reappearing packages are only built once.
+          builtByPackageIdByPkgs = mkBuiltByPackageIdByPkgs pkgs;
+          mkBuiltByPackageIdByPkgs = pkgs:
+            let
+              self = {
+                crates = lib.mapAttrs (packageId: value: buildByPackageIdForPkgsImpl self pkgs packageId) crateConfigs;
+                target = makeTarget pkgs.stdenv.hostPlatform;
+                build = mkBuiltByPackageIdByPkgs pkgs.buildPackages;
+              };
+            in
+            self;
+          buildByPackageIdForPkgsImpl = self: pkgs: packageId:
+            let
+              features = mergedFeatures."${packageId}" or [ ];
+              crateConfig' = crateConfigs."${packageId}";
+              crateConfig =
+                builtins.removeAttrs crateConfig' [ "resolvedDefaultFeatures" "devDependencies" ];
+              devDependencies =
+                lib.optionals
+                  (runTests && packageId == rootPackageId)
+                  (crateConfig'.devDependencies or [ ]);
+              dependencies =
+                dependencyDerivations {
+                  inherit features;
+                  inherit (self) target;
+                  buildByPackageId = depPackageId:
+                    # proc_macro crates must be compiled for the build architecture
+                    if crateConfigs.${depPackageId}.procMacro or false
+                    then self.build.crates.${depPackageId}
+                    else self.crates.${depPackageId};
+                  dependencies =
+                    (crateConfig.dependencies or [ ])
+                    ++ devDependencies;
+                };
+              buildDependencies =
+                dependencyDerivations {
+                  inherit features;
+                  inherit (self.build) target;
+                  buildByPackageId = depPackageId:
+                    self.build.crates.${depPackageId};
+                  dependencies = crateConfig.buildDependencies or [ ];
+                };
+              dependenciesWithRenames =
+                let
+                  buildDeps = filterEnabledDependencies {
+                    inherit features;
+                    inherit (self) target;
+                    dependencies = crateConfig.dependencies or [ ] ++ devDependencies;
+                  };
+                  hostDeps = filterEnabledDependencies {
+                    inherit features;
+                    inherit (self.build) target;
+                    dependencies = crateConfig.buildDependencies or [ ];
+                  };
+                in
+                lib.filter (d: d ? "rename") (hostDeps ++ buildDeps);
+              # Crate renames have the form:
+              #
+              # {
+              #    crate_name = [
+              #       { version = "1.2.3"; rename = "crate_name01"; }
+              #    ];
+              #    # ...
+              # }
+              crateRenames =
+                let
+                  grouped =
+                    lib.groupBy
+                      (dependency: dependency.name)
+                      dependenciesWithRenames;
+                  versionAndRename = dep:
+                    let
+                      package = crateConfigs."${dep.packageId}";
+                    in
+                    { inherit (dep) rename; inherit (package) version; };
+                in
+                lib.mapAttrs (name: builtins.map versionAndRename) grouped;
+            in
+            buildRustCrateForPkgsFunc pkgs
+              (
+                crateConfig // {
+                  # https://github.com/NixOS/nixpkgs/issues/218712
+                  dontStrip = stdenv.hostPlatform.isDarwin;
+                  src = crateConfig.src or (
+                    pkgs.fetchurl rec {
+                      name = "${crateConfig.crateName}-${crateConfig.version}.tar.gz";
+                      # https://www.pietroalbini.org/blog/downloading-crates-io/
+                      # Not rate-limited, CDN URL.
+                      url = "https://static.crates.io/crates/${crateConfig.crateName}/${crateConfig.crateName}-${crateConfig.version}.crate";
+                      sha256 =
+                        assert (lib.assertMsg (crateConfig ? sha256) "Missing sha256 for ${name}");
+                        crateConfig.sha256;
+                    }
+                  );
+                  extraRustcOpts = lib.lists.optional (targetFeatures != [ ]) "-C target-feature=${lib.concatMapStringsSep "," (x: "+${x}") targetFeatures}";
+                  inherit features dependencies buildDependencies crateRenames release;
+                }
+              );
+        in
+        builtByPackageIdByPkgs;
+
+    /* Returns the actual derivations for the given dependencies. */
+    dependencyDerivations =
+      { buildByPackageId
+      , features
+      , dependencies
+      , target
+      }:
+        assert (builtins.isList features);
+        assert (builtins.isList dependencies);
+        assert (builtins.isAttrs target);
+        let
+          enabledDependencies = filterEnabledDependencies {
+            inherit dependencies features target;
+          };
+          depDerivation = dependency: buildByPackageId dependency.packageId;
+        in
+        map depDerivation enabledDependencies;
+
+    /* Returns a sanitized version of val with all values substituted that cannot
+      be serialized as JSON.
+    */
+    sanitizeForJson = val:
+      if builtins.isAttrs val
+      then lib.mapAttrs (n: sanitizeForJson) val
+      else if builtins.isList val
+      then builtins.map sanitizeForJson val
+      else if builtins.isFunction val
+      then "function"
+      else val;
+
+    /* Returns various tools to debug a crate. */
+    debugCrate = { packageId, target ? makeDefaultTarget stdenv.hostPlatform }:
+      assert (builtins.isString packageId);
+      let
+        debug = rec {
+          # The built tree as passed to buildRustCrate.
+          buildTree = buildRustCrateWithFeatures {
+            buildRustCrateForPkgsFunc = _: lib.id;
+            inherit packageId;
+          };
+          sanitizedBuildTree = sanitizeForJson buildTree;
+          dependencyTree = sanitizeForJson
+            (
+              buildRustCrateWithFeatures {
+                buildRustCrateForPkgsFunc = _: crate: {
+                  "01_crateName" = crate.crateName or false;
+                  "02_features" = crate.features or [ ];
+                  "03_dependencies" = crate.dependencies or [ ];
+                };
+                inherit packageId;
+              }
+            );
+          mergedPackageFeatures = mergePackageFeatures {
+            features = rootFeatures;
+            inherit packageId target;
+          };
+          diffedDefaultPackageFeatures = diffDefaultPackageFeatures {
+            inherit packageId target;
+          };
+        };
+      in
+      { internal = debug; };
+
+    /* Returns differences between cargo default features and crate2nix default
+      features.
+
+      This is useful for verifying the feature resolution in crate2nix.
+    */
+    diffDefaultPackageFeatures =
+      { crateConfigs ? crates
+      , packageId
+      , target
+      }:
+        assert (builtins.isAttrs crateConfigs);
+        let
+          prefixValues = prefix: lib.mapAttrs (n: v: { "${prefix}" = v; });
+          mergedFeatures =
+            prefixValues
+              "crate2nix"
+              (mergePackageFeatures { inherit crateConfigs packageId target; features = [ "default" ]; });
+          configs = prefixValues "cargo" crateConfigs;
+          combined = lib.foldAttrs (a: b: a // b) { } [ mergedFeatures configs ];
+          onlyInCargo =
+            builtins.attrNames
+              (lib.filterAttrs (n: v: !(v ? "crate2nix") && (v ? "cargo")) combined);
+          onlyInCrate2Nix =
+            builtins.attrNames
+              (lib.filterAttrs (n: v: (v ? "crate2nix") && !(v ? "cargo")) combined);
+          differentFeatures = lib.filterAttrs
+            (
+              n: v:
+                (v ? "crate2nix")
+                && (v ? "cargo")
+                && (v.crate2nix.features or [ ]) != (v."cargo".resolved_default_features or [ ])
+            )
+            combined;
+        in
+        builtins.toJSON {
+          inherit onlyInCargo onlyInCrate2Nix differentFeatures;
+        };
+
+    /* Returns an attrset mapping packageId to the list of enabled features.
+
+      If multiple paths to a dependency enable different features, the
+      corresponding feature sets are merged. Features in rust are additive.
+    */
+    mergePackageFeatures =
+      { crateConfigs ? crates
+      , packageId
+      , rootPackageId ? packageId
+      , features ? rootFeatures
+      , dependencyPath ? [ crates.${packageId}.crateName ]
+      , featuresByPackageId ? { }
+      , target
+        # Adds devDependencies to the crate with rootPackageId.
+      , runTests ? false
+      , ...
+      } @ args:
+        assert (builtins.isAttrs crateConfigs);
+        assert (builtins.isString packageId);
+        assert (builtins.isString rootPackageId);
+        assert (builtins.isList features);
+        assert (builtins.isList dependencyPath);
+        assert (builtins.isAttrs featuresByPackageId);
+        assert (builtins.isAttrs target);
+        assert (builtins.isBool runTests);
+        let
+          crateConfig = crateConfigs."${packageId}" or (builtins.throw "Package not found: ${packageId}");
+          expandedFeatures = expandFeatures (crateConfig.features or { }) features;
+          enabledFeatures = enableFeatures (crateConfig.dependencies or [ ]) expandedFeatures;
+          depWithResolvedFeatures = dependency:
+            let
+              inherit (dependency) packageId;
+              features = dependencyFeatures enabledFeatures dependency;
+            in
+            { inherit packageId features; };
+          resolveDependencies = cache: path: dependencies:
+            assert (builtins.isAttrs cache);
+            assert (builtins.isList dependencies);
+            let
+              enabledDependencies = filterEnabledDependencies {
+                inherit dependencies target;
+                features = enabledFeatures;
+              };
+              directDependencies = map depWithResolvedFeatures enabledDependencies;
+              foldOverCache = op: lib.foldl op cache directDependencies;
+            in
+            foldOverCache
+              (
+                cache: { packageId, features }:
+                  let
+                    cacheFeatures = cache.${packageId} or [ ];
+                    combinedFeatures = sortedUnique (cacheFeatures ++ features);
+                  in
+                  if cache ? ${packageId} && cache.${packageId} == combinedFeatures
+                  then cache
+                  else
+                    mergePackageFeatures {
+                      features = combinedFeatures;
+                      featuresByPackageId = cache;
+                      inherit crateConfigs packageId target runTests rootPackageId;
+                    }
+              );
+          cacheWithSelf =
+            let
+              cacheFeatures = featuresByPackageId.${packageId} or [ ];
+              combinedFeatures = sortedUnique (cacheFeatures ++ enabledFeatures);
+            in
+            featuresByPackageId // {
+              "${packageId}" = combinedFeatures;
+            };
+          cacheWithDependencies =
+            resolveDependencies cacheWithSelf "dep"
+              (
+                crateConfig.dependencies or [ ]
+                ++ lib.optionals
+                  (runTests && packageId == rootPackageId)
+                  (crateConfig.devDependencies or [ ])
+              );
+          cacheWithAll =
+            resolveDependencies
+              cacheWithDependencies "build"
+              (crateConfig.buildDependencies or [ ]);
+        in
+        cacheWithAll;
+
+    /* Returns the enabled dependencies given the enabled features. */
+    filterEnabledDependencies = { dependencies, features, target }:
+      assert (builtins.isList dependencies);
+      assert (builtins.isList features);
+      assert (builtins.isAttrs target);
+
+      lib.filter
+        (
+          dep:
+          let
+            targetFunc = dep.target or (features: true);
+          in
+          targetFunc { inherit features target; }
+          && (
+            !(dep.optional or false)
+            || builtins.any (doesFeatureEnableDependency dep) features
+          )
+        )
+        dependencies;
+
+    /* Returns whether the given feature should enable the given dependency. */
+    doesFeatureEnableDependency = dependency: feature:
+      let
+        name = dependency.rename or dependency.name;
+        prefix = "${name}/";
+        len = builtins.stringLength prefix;
+        startsWithPrefix = builtins.substring 0 len feature == prefix;
+      in
+      feature == name || feature == "dep:" + name || startsWithPrefix;
+
+    /* Returns the expanded features for the given inputFeatures by applying the
+      rules in featureMap.
+
+      featureMap is an attribute set which maps feature names to lists of further
+      feature names to enable in case this feature is selected.
+    */
+    expandFeatures = featureMap: inputFeatures:
+      assert (builtins.isAttrs featureMap);
+      assert (builtins.isList inputFeatures);
+      let
+        expandFeaturesNoCycle = oldSeen: inputFeatures:
+          if inputFeatures != [ ]
+          then
+            let
+              # The feature we're currently expanding.
+              feature = builtins.head inputFeatures;
+              # All the features we've seen/expanded so far, including the one
+              # we're currently processing.
+              seen = oldSeen // { ${feature} = 1; };
+              # Expand the feature but be careful to not re-introduce a feature
+              # that we've already seen: this can easily cause a cycle, see issue
+              # #209.
+              enables = builtins.filter (f: !(seen ? "${f}")) (featureMap."${feature}" or [ ]);
+            in
+            [ feature ] ++ (expandFeaturesNoCycle seen (builtins.tail inputFeatures ++ enables))
+          # No more features left, nothing to expand to.
+          else [ ];
+        outFeatures = expandFeaturesNoCycle { } inputFeatures;
+      in
+      sortedUnique outFeatures;
+
+    /* This function adds optional dependencies as features if they are enabled
+      indirectly by dependency features. This function mimics Cargo's behavior
+      described in a note at:
+      https://doc.rust-lang.org/nightly/cargo/reference/features.html#dependency-features
+    */
+    enableFeatures = dependencies: features:
+      assert (builtins.isList features);
+      assert (builtins.isList dependencies);
+      let
+        additionalFeatures = lib.concatMap
+          (
+            dependency:
+              assert (builtins.isAttrs dependency);
+              let
+                enabled = builtins.any (doesFeatureEnableDependency dependency) features;
+              in
+              if (dependency.optional or false) && enabled
+              then [ (dependency.rename or dependency.name) ]
+              else [ ]
+          )
+          dependencies;
+      in
+      sortedUnique (features ++ additionalFeatures);
+
+    /*
+      Returns the actual features for the given dependency.
+
+      features: The features of the crate that refers this dependency.
+    */
+    dependencyFeatures = features: dependency:
+      assert (builtins.isList features);
+      assert (builtins.isAttrs dependency);
+      let
+        defaultOrNil =
+          if dependency.usesDefaultFeatures or true
+          then [ "default" ]
+          else [ ];
+        explicitFeatures = dependency.features or [ ];
+        additionalDependencyFeatures =
+          let
+            name = dependency.rename or dependency.name;
+            stripPrefixMatch = prefix: s:
+              if lib.hasPrefix prefix s
+              then lib.removePrefix prefix s
+              else null;
+            extractFeature = feature: lib.findFirst
+              (f: f != null)
+              null
+              (map (prefix: stripPrefixMatch prefix feature) [
+                (name + "/")
+                (name + "?/")
+              ]);
+            dependencyFeatures = lib.filter (f: f != null) (map extractFeature features);
+          in
+          dependencyFeatures;
+      in
+      defaultOrNil ++ explicitFeatures ++ additionalDependencyFeatures;
+
+    /* Sorts and removes duplicates from a list of strings. */
+    sortedUnique = features:
+      assert (builtins.isList features);
+      assert (builtins.all builtins.isString features);
+      let
+        outFeaturesSet = lib.foldl (set: feature: set // { "${feature}" = 1; }) { } features;
+        outFeaturesUnique = builtins.attrNames outFeaturesSet;
+      in
+      builtins.sort (a: b: a < b) outFeaturesUnique;
+
+    deprecationWarning = message: value:
+      if strictDeprecation
+      then builtins.throw "strictDeprecation enabled, aborting: ${message}"
+      else builtins.trace message value;
+
+    #
+    # crate2nix/default.nix (excerpt end)
+    #
+  };
+}
+
diff --git a/tvix/tools/narinfo2parquet/Cargo.toml b/tvix/tools/narinfo2parquet/Cargo.toml
new file mode 100644
index 0000000000..3efa9d1df9
--- /dev/null
+++ b/tvix/tools/narinfo2parquet/Cargo.toml
@@ -0,0 +1,25 @@
+[package]
+name = "narinfo2parquet"
+version = "0.1.0"
+edition = "2021"
+
+# We can't join the //tvix workspace, because that locks zstd
+# at an ancient version, which is incompatible with polars
+[workspace]
+members = ["."]
+
+[dependencies]
+anyhow = { version = "1.0.75", features = ["backtrace"] }
+jemallocator = "0.5.4"
+nix-compat = { version = "0.1.0", path = "../../nix-compat" }
+tempfile-fast = "0.3.4"
+zstd = "0.13.0"
+
+[dependencies.polars]
+version = "0.36.2"
+default-features = false
+features = [
+    "parquet",
+    "polars-io",
+    "dtype-categorical"
+]
diff --git a/tvix/tools/narinfo2parquet/OWNERS b/tvix/tools/narinfo2parquet/OWNERS
new file mode 100644
index 0000000000..b9bc074a80
--- /dev/null
+++ b/tvix/tools/narinfo2parquet/OWNERS
@@ -0,0 +1 @@
+edef
diff --git a/tvix/tools/narinfo2parquet/default.nix b/tvix/tools/narinfo2parquet/default.nix
new file mode 100644
index 0000000000..5a84d7f642
--- /dev/null
+++ b/tvix/tools/narinfo2parquet/default.nix
@@ -0,0 +1,3 @@
+{ pkgs, ... }:
+
+(pkgs.callPackage ./Cargo.nix { }).rootCrate.build
diff --git a/tvix/tools/narinfo2parquet/src/main.rs b/tvix/tools/narinfo2parquet/src/main.rs
new file mode 100644
index 0000000000..3b793e02b1
--- /dev/null
+++ b/tvix/tools/narinfo2parquet/src/main.rs
@@ -0,0 +1,264 @@
+//! narinfo2parquet operates on a narinfo.zst directory produced by turbofetch.
+//! It takes the name of a segment file in `narinfo.zst` and writes a Parquet file
+//! with the same name into the `narinfo.pq` directory.
+//!
+//! Run it under GNU Parallel for parallelism:
+//! ```shell
+//! mkdir narinfo.pq && ls narinfo.zst | parallel --bar 'narinfo2parquet {}'
+//! ```
+
+use anyhow::{bail, Context, Result};
+use jemallocator::Jemalloc;
+use nix_compat::{
+    narinfo::{self, NarInfo},
+    nixbase32,
+    nixhash::{CAHash, NixHash},
+};
+use polars::{io::parquet::ParquetWriter, prelude::*};
+use std::{
+    fs::{self, File},
+    io::{self, BufRead, BufReader, Read},
+    path::Path,
+};
+use tempfile_fast::PersistableTempFile;
+
+#[global_allocator]
+static GLOBAL: Jemalloc = Jemalloc;
+
+fn main() -> Result<()> {
+    let file_name = std::env::args().nth(1).expect("file name missing");
+    let input_path = Path::new("narinfo.zst").join(&file_name);
+    let output_path = Path::new("narinfo.pq").join(&file_name);
+
+    match fs::metadata(&output_path) {
+        Err(e) if e.kind() == io::ErrorKind::NotFound => {}
+        Err(e) => bail!(e),
+        Ok(_) => bail!("output path already exists: {output_path:?}"),
+    }
+
+    let reader = File::open(input_path).and_then(zstd::Decoder::new)?;
+    let mut frame = FrameBuilder::default();
+
+    for_each(reader, |s| {
+        let entry = NarInfo::parse(&s).context("couldn't parse entry:\n{s}")?;
+        frame.push(&entry);
+        Ok(())
+    })?;
+
+    let mut frame = frame.finish();
+    let mut writer = PersistableTempFile::new_in(output_path.parent().unwrap())?;
+
+    ParquetWriter::new(&mut writer)
+        .with_compression(ParquetCompression::Gzip(None))
+        .with_statistics(true)
+        .finish(frame.align_chunks())?;
+
+    writer
+        .persist_noclobber(output_path)
+        .map_err(|e| e.error)
+        .context("couldn't commit output file")?;
+
+    Ok(())
+}
+
+fn for_each(reader: impl Read, mut f: impl FnMut(&str) -> Result<()>) -> Result<()> {
+    let mut reader = BufReader::new(reader);
+    let mut group = String::new();
+    loop {
+        let prev_len = group.len();
+
+        if prev_len > 1024 * 1024 {
+            bail!("excessively large segment");
+        }
+
+        reader.read_line(&mut group)?;
+        let (prev, line) = group.split_at(prev_len);
+
+        // EOF
+        if line.is_empty() {
+            break;
+        }
+
+        // skip empty line
+        if line == "\n" {
+            group.pop().unwrap();
+            continue;
+        }
+
+        if !prev.is_empty() && line.starts_with("StorePath:") {
+            f(prev)?;
+            group.drain(..prev_len);
+        }
+    }
+
+    if !group.is_empty() {
+        f(&group)?;
+    }
+
+    Ok(())
+}
+
+/// [FrameBuilder] builds a [DataFrame] out of [NarInfo]s.
+/// The exact format is still in flux.
+///
+/// # Example
+///
+/// ```no_run
+/// |narinfos: &[NarInfo]| -> DataFrame {
+///     let frame_builder = FrameBuilder::default();
+///     narinfos.for_each(|n| frame_builder.push(n));
+///     frame_builder.finish()
+/// }
+/// ```
+struct FrameBuilder {
+    store_path_hash_str: StringChunkedBuilder,
+    store_path_hash: BinaryChunkedBuilder,
+    store_path_name: StringChunkedBuilder,
+    nar_hash: BinaryChunkedBuilder,
+    nar_size: PrimitiveChunkedBuilder<UInt64Type>,
+    references: ListBinaryChunkedBuilder,
+    ca_algo: CategoricalChunkedBuilder<'static>,
+    ca_hash: BinaryChunkedBuilder,
+    signature: BinaryChunkedBuilder,
+    file_hash: BinaryChunkedBuilder,
+    file_size: PrimitiveChunkedBuilder<UInt64Type>,
+    compression: CategoricalChunkedBuilder<'static>,
+    quirk_references_out_of_order: BooleanChunkedBuilder,
+    quirk_nar_hash_hex: BooleanChunkedBuilder,
+}
+
+impl Default for FrameBuilder {
+    fn default() -> Self {
+        Self {
+            store_path_hash_str: StringChunkedBuilder::new("store_path_hash_str", 0, 0),
+            store_path_hash: BinaryChunkedBuilder::new("store_path_hash", 0, 0),
+            store_path_name: StringChunkedBuilder::new("store_path_name", 0, 0),
+            nar_hash: BinaryChunkedBuilder::new("nar_hash", 0, 0),
+            nar_size: PrimitiveChunkedBuilder::new("nar_size", 0),
+            references: ListBinaryChunkedBuilder::new("references", 0, 0),
+            signature: BinaryChunkedBuilder::new("signature", 0, 0),
+            ca_algo: CategoricalChunkedBuilder::new("ca_algo", 0, CategoricalOrdering::Lexical),
+            ca_hash: BinaryChunkedBuilder::new("ca_hash", 0, 0),
+            file_hash: BinaryChunkedBuilder::new("file_hash", 0, 0),
+            file_size: PrimitiveChunkedBuilder::new("file_size", 0),
+            compression: CategoricalChunkedBuilder::new(
+                "compression",
+                0,
+                CategoricalOrdering::Lexical,
+            ),
+            quirk_references_out_of_order: BooleanChunkedBuilder::new(
+                "quirk_references_out_of_order",
+                0,
+            ),
+            quirk_nar_hash_hex: BooleanChunkedBuilder::new("quirk_nar_hash_hex", 0),
+        }
+    }
+}
+
+impl FrameBuilder {
+    fn push(&mut self, entry: &NarInfo) {
+        self.store_path_hash_str
+            .append_value(nixbase32::encode(entry.store_path.digest()));
+
+        self.store_path_hash.append_value(entry.store_path.digest());
+        self.store_path_name.append_value(entry.store_path.name());
+
+        self.nar_hash.append_value(&entry.nar_hash);
+        self.nar_size.append_value(entry.nar_size);
+
+        self.references
+            .append_values_iter(entry.references.iter().map(|r| r.digest().as_slice()));
+
+        assert!(entry.signatures.len() <= 1);
+        self.signature
+            .append_option(entry.signatures.get(0).map(|sig| {
+                assert_eq!(sig.name(), "cache.nixos.org-1");
+                sig.bytes()
+            }));
+
+        if let Some(ca) = &entry.ca {
+            // decompose the CAHash into algo and hash parts
+            // TODO(edef): move this into CAHash
+            let (algo, hash) = match ca {
+                CAHash::Flat(h) => match h {
+                    NixHash::Md5(h) => ("fixed:md5", &h[..]),
+                    NixHash::Sha1(h) => ("fixed:sha1", &h[..]),
+                    NixHash::Sha256(h) => ("fixed:sha256", &h[..]),
+                    NixHash::Sha512(h) => ("fixed:sha512", &h[..]),
+                },
+                CAHash::Nar(h) => match h {
+                    NixHash::Md5(h) => ("fixed:r:md5", &h[..]),
+                    NixHash::Sha1(h) => ("fixed:r:sha1", &h[..]),
+                    NixHash::Sha256(h) => ("fixed:r:sha256", &h[..]),
+                    NixHash::Sha512(h) => ("fixed:r:sha512", &h[..]),
+                },
+                CAHash::Text(h) => ("text:sha256", &h[..]),
+            };
+
+            self.ca_algo.append_value(algo);
+            self.ca_hash.append_value(hash);
+        } else {
+            self.ca_algo.append_null();
+            self.ca_hash.append_null();
+        }
+
+        let file_hash = entry.file_hash.as_ref().unwrap();
+        let file_size = entry.file_size.unwrap();
+
+        self.file_hash.append_value(file_hash);
+        self.file_size.append_value(file_size);
+
+        let (compression, extension) = match entry.compression {
+            Some("bzip2") => ("bzip2", "bz2"),
+            Some("xz") => ("xz", "xz"),
+            Some("zstd") => ("zstd", "zst"),
+            x => panic!("unknown compression algorithm: {x:?}"),
+        };
+
+        self.compression.append_value(compression);
+
+        let mut file_name = nixbase32::encode(file_hash);
+        file_name.push_str(".nar.");
+        file_name.push_str(extension);
+
+        assert_eq!(entry.url.strip_prefix("nar/").unwrap(), file_name);
+
+        {
+            use narinfo::Flags;
+
+            self.quirk_references_out_of_order
+                .append_value(entry.flags.contains(Flags::REFERENCES_OUT_OF_ORDER));
+
+            self.quirk_nar_hash_hex
+                .append_value(entry.flags.contains(Flags::NAR_HASH_HEX));
+
+            let quirks = Flags::REFERENCES_OUT_OF_ORDER | Flags::NAR_HASH_HEX;
+            let unknown_flags = entry.flags.difference(quirks);
+
+            assert!(
+                unknown_flags.is_empty(),
+                "rejecting flags: {unknown_flags:?}"
+            );
+        }
+    }
+
+    fn finish(mut self) -> DataFrame {
+        df! {
+            "store_path_hash_str" => self.store_path_hash_str.finish().into_series(),
+            "store_path_hash" => self.store_path_hash.finish().into_series(),
+            "store_path_name" => self.store_path_name.finish().into_series(),
+            "nar_hash" => self.nar_hash.finish().into_series(),
+            "nar_size" => self.nar_size.finish().into_series(),
+            "references" => self.references.finish().into_series(),
+            "signature" => self.signature.finish().into_series(),
+            "ca_algo" => self.ca_algo.finish().into_series(),
+            "ca_hash" => self.ca_hash.finish().into_series(),
+            "file_hash" => self.file_hash.finish().into_series(),
+            "file_size" => self.file_size.finish().into_series(),
+            "compression" => self.compression.finish().into_series(),
+            "quirk_references_out_of_order" => self.quirk_references_out_of_order.finish().into_series(),
+            "quirk_nar_hash_hex" => self.quirk_nar_hash_hex.finish().into_series()
+        }
+        .unwrap()
+    }
+}
diff --git a/tvix/tools/turbofetch/Cargo.lock b/tvix/tools/turbofetch/Cargo.lock
new file mode 100644
index 0000000000..4d65fc4063
--- /dev/null
+++ b/tvix/tools/turbofetch/Cargo.lock
@@ -0,0 +1,1715 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "addr2line"
+version = "0.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb"
+dependencies = [
+ "gimli",
+]
+
+[[package]]
+name = "adler"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+
+[[package]]
+name = "android-tzdata"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
+
+[[package]]
+name = "android_system_properties"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "async-stream"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51"
+dependencies = [
+ "async-stream-impl",
+ "futures-core",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "async-stream-impl"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "async-trait"
+version = "0.1.74"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+
+[[package]]
+name = "aws_lambda_events"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "771c11de66cef31b3f49f0b38f06d94f0af424f60381409fc21972ff9c8f835b"
+dependencies = [
+ "base64 0.21.5",
+ "bytes",
+ "http",
+ "http-body",
+ "http-serde",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "backtrace"
+version = "0.3.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837"
+dependencies = [
+ "addr2line",
+ "cc",
+ "cfg-if",
+ "libc",
+ "miniz_oxide",
+ "object",
+ "rustc-demangle",
+]
+
+[[package]]
+name = "base64"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
+
+[[package]]
+name = "base64"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ea22880d78093b0cbe17c89f64a7d457941e65759157ec6cb31a31d652b05e5"
+
+[[package]]
+name = "base64"
+version = "0.21.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "bitflags"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
+
+[[package]]
+name = "block-buffer"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"
+
+[[package]]
+name = "bytes"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "cc"
+version = "1.0.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
+dependencies = [
+ "jobserver",
+ "libc",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "chrono"
+version = "0.4.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38"
+dependencies = [
+ "android-tzdata",
+ "iana-time-zone",
+ "num-traits",
+ "serde",
+ "windows-targets",
+]
+
+[[package]]
+name = "core-foundation"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
+
+[[package]]
+name = "cpufeatures"
+version = "0.2.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crc32fast"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "crypto-mac"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714"
+dependencies = [
+ "generic-array",
+ "subtle",
+]
+
+[[package]]
+name = "data-encoding"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"
+
+[[package]]
+name = "digest"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "dirs-next"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
+dependencies = [
+ "cfg-if",
+ "dirs-sys-next",
+]
+
+[[package]]
+name = "dirs-sys-next"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
+dependencies = [
+ "libc",
+ "redox_users",
+ "winapi",
+]
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "futures"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-io",
+ "futures-sink",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
+
+[[package]]
+name = "futures-executor"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-io"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
+
+[[package]]
+name = "futures-macro"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "futures-sink"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5"
+
+[[package]]
+name = "futures-task"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
+
+[[package]]
+name = "futures-util"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-macro",
+ "futures-sink",
+ "futures-task",
+ "memchr",
+ "pin-project-lite",
+ "pin-utils",
+ "slab",
+]
+
+[[package]]
+name = "generic-array"
+version = "0.14.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "gimli"
+version = "0.28.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0"
+
+[[package]]
+name = "h2"
+version = "0.3.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833"
+dependencies = [
+ "bytes",
+ "fnv",
+ "futures-core",
+ "futures-sink",
+ "futures-util",
+ "http",
+ "indexmap",
+ "slab",
+ "tokio",
+ "tokio-util",
+ "tracing",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
+
+[[package]]
+name = "hermit-abi"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
+
+[[package]]
+name = "hex"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+
+[[package]]
+name = "hmac"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b"
+dependencies = [
+ "crypto-mac",
+ "digest",
+]
+
+[[package]]
+name = "http"
+version = "0.2.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f95b9abcae896730d42b78e09c155ed4ddf82c07b4de772c64aee5b2d8b7c150"
+dependencies = [
+ "bytes",
+ "fnv",
+ "itoa",
+]
+
+[[package]]
+name = "http-body"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1"
+dependencies = [
+ "bytes",
+ "http",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "http-serde"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f560b665ad9f1572cfcaf034f7fb84338a7ce945216d64a90fd81f046a3caee"
+dependencies = [
+ "http",
+ "serde",
+]
+
+[[package]]
+name = "httparse"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
+
+[[package]]
+name = "httpdate"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
+
+[[package]]
+name = "hyper"
+version = "0.14.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468"
+dependencies = [
+ "bytes",
+ "futures-channel",
+ "futures-core",
+ "futures-util",
+ "h2",
+ "http",
+ "http-body",
+ "httparse",
+ "httpdate",
+ "itoa",
+ "pin-project-lite",
+ "socket2 0.4.10",
+ "tokio",
+ "tower-service",
+ "tracing",
+ "want",
+]
+
+[[package]]
+name = "hyper-rustls"
+version = "0.23.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1788965e61b367cd03a62950836d5cd41560c3577d90e40e0819373194d1661c"
+dependencies = [
+ "http",
+ "hyper",
+ "log",
+ "rustls",
+ "rustls-native-certs",
+ "tokio",
+ "tokio-rustls",
+]
+
+[[package]]
+name = "iana-time-zone"
+version = "0.1.58"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "iana-time-zone-haiku",
+ "js-sys",
+ "wasm-bindgen",
+ "windows-core",
+]
+
+[[package]]
+name = "iana-time-zone-haiku"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "indexmap"
+version = "1.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
+dependencies = [
+ "autocfg",
+ "hashbrown",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
+
+[[package]]
+name = "jobserver"
+version = "0.1.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "js-sys"
+version = "0.3.65"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "54c0c35952f67de54bb584e9fd912b3023117cbafc0a77d8f3dee1fb5f572fe8"
+dependencies = [
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "lambda_runtime"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "deca8f65d7ce9a8bfddebb49d7d91b22e788a59ca0c5190f26794ab80ed7a702"
+dependencies = [
+ "async-stream",
+ "base64 0.20.0",
+ "bytes",
+ "futures",
+ "http",
+ "http-body",
+ "http-serde",
+ "hyper",
+ "lambda_runtime_api_client",
+ "serde",
+ "serde_json",
+ "serde_path_to_error",
+ "tokio",
+ "tokio-stream",
+ "tower",
+ "tracing",
+]
+
+[[package]]
+name = "lambda_runtime_api_client"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "690c5ae01f3acac8c9c3348b556fc443054e9b7f1deaf53e9ebab716282bf0ed"
+dependencies = [
+ "http",
+ "hyper",
+ "tokio",
+ "tower-service",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "libc"
+version = "0.2.150"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c"
+
+[[package]]
+name = "libredox"
+version = "0.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8"
+dependencies = [
+ "bitflags 2.4.1",
+ "libc",
+ "redox_syscall",
+]
+
+[[package]]
+name = "lock_api"
+version = "0.4.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45"
+dependencies = [
+ "autocfg",
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
+
+[[package]]
+name = "mach2"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d0d1830bcd151a6fc4aea1369af235b36c1528fe976b8ff678683c9995eade8"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "magic-buffer"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "003aed0f6b361330d1c549e8ae765758cb9d46f7bace57112e8c25847966ff2e"
+dependencies = [
+ "libc",
+ "mach2",
+ "thiserror",
+ "windows-sys",
+]
+
+[[package]]
+name = "md-5"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b5a279bb9607f9f53c22d496eade00d138d1bdcccd07d74650387cf94942a15"
+dependencies = [
+ "block-buffer",
+ "digest",
+ "opaque-debug",
+]
+
+[[package]]
+name = "memchr"
+version = "2.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
+
+[[package]]
+name = "miniz_oxide"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
+dependencies = [
+ "adler",
+]
+
+[[package]]
+name = "mio"
+version = "0.8.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0"
+dependencies = [
+ "libc",
+ "wasi",
+ "windows-sys",
+]
+
+[[package]]
+name = "nu-ansi-term"
+version = "0.46.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
+dependencies = [
+ "overload",
+ "winapi",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "num_cpus"
+version = "1.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
+dependencies = [
+ "hermit-abi",
+ "libc",
+]
+
+[[package]]
+name = "object"
+version = "0.32.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
+
+[[package]]
+name = "opaque-debug"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
+
+[[package]]
+name = "openssl-probe"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
+
+[[package]]
+name = "overload"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
+
+[[package]]
+name = "parking_lot"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
+dependencies = [
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "windows-targets",
+]
+
+[[package]]
+name = "percent-encoding"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
+
+[[package]]
+name = "pin-project"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422"
+dependencies = [
+ "pin-project-internal",
+]
+
+[[package]]
+name = "pin-project-internal"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.33"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
+dependencies = [
+ "bitflags 1.3.2",
+]
+
+[[package]]
+name = "redox_users"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4"
+dependencies = [
+ "getrandom",
+ "libredox",
+ "thiserror",
+]
+
+[[package]]
+name = "ring"
+version = "0.16.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
+dependencies = [
+ "cc",
+ "libc",
+ "once_cell",
+ "spin 0.5.2",
+ "untrusted 0.7.1",
+ "web-sys",
+ "winapi",
+]
+
+[[package]]
+name = "ring"
+version = "0.17.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb0205304757e5d899b9c2e448b867ffd03ae7f988002e47cd24954391394d0b"
+dependencies = [
+ "cc",
+ "getrandom",
+ "libc",
+ "spin 0.9.8",
+ "untrusted 0.9.0",
+ "windows-sys",
+]
+
+[[package]]
+name = "rusoto_core"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1db30db44ea73551326269adcf7a2169428a054f14faf9e1768f2163494f2fa2"
+dependencies = [
+ "async-trait",
+ "base64 0.13.1",
+ "bytes",
+ "crc32fast",
+ "futures",
+ "http",
+ "hyper",
+ "hyper-rustls",
+ "lazy_static",
+ "log",
+ "rusoto_credential",
+ "rusoto_signature",
+ "rustc_version",
+ "serde",
+ "serde_json",
+ "tokio",
+ "xml-rs",
+]
+
+[[package]]
+name = "rusoto_credential"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee0a6c13db5aad6047b6a44ef023dbbc21a056b6dab5be3b79ce4283d5c02d05"
+dependencies = [
+ "async-trait",
+ "chrono",
+ "dirs-next",
+ "futures",
+ "hyper",
+ "serde",
+ "serde_json",
+ "shlex",
+ "tokio",
+ "zeroize",
+]
+
+[[package]]
+name = "rusoto_s3"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7aae4677183411f6b0b412d66194ef5403293917d66e70ab118f07cc24c5b14d"
+dependencies = [
+ "async-trait",
+ "bytes",
+ "futures",
+ "rusoto_core",
+ "xml-rs",
+]
+
+[[package]]
+name = "rusoto_signature"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a5ae95491c8b4847931e291b151127eccd6ff8ca13f33603eb3d0035ecb05272"
+dependencies = [
+ "base64 0.13.1",
+ "bytes",
+ "chrono",
+ "digest",
+ "futures",
+ "hex",
+ "hmac",
+ "http",
+ "hyper",
+ "log",
+ "md-5",
+ "percent-encoding",
+ "pin-project-lite",
+ "rusoto_credential",
+ "rustc_version",
+ "serde",
+ "sha2",
+ "tokio",
+]
+
+[[package]]
+name = "rustc-demangle"
+version = "0.1.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
+
+[[package]]
+name = "rustc_version"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
+dependencies = [
+ "semver",
+]
+
+[[package]]
+name = "rustls"
+version = "0.20.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b80e3dec595989ea8510028f30c408a4630db12c9cbb8de34203b89d6577e99"
+dependencies = [
+ "log",
+ "ring 0.16.20",
+ "sct",
+ "webpki",
+]
+
+[[package]]
+name = "rustls-native-certs"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00"
+dependencies = [
+ "openssl-probe",
+ "rustls-pemfile",
+ "schannel",
+ "security-framework",
+]
+
+[[package]]
+name = "rustls-pemfile"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c"
+dependencies = [
+ "base64 0.21.5",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
+
+[[package]]
+name = "schannel"
+version = "0.1.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88"
+dependencies = [
+ "windows-sys",
+]
+
+[[package]]
+name = "scopeguard"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+
+[[package]]
+name = "sct"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414"
+dependencies = [
+ "ring 0.17.5",
+ "untrusted 0.9.0",
+]
+
+[[package]]
+name = "security-framework"
+version = "2.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de"
+dependencies = [
+ "bitflags 1.3.2",
+ "core-foundation",
+ "core-foundation-sys",
+ "libc",
+ "security-framework-sys",
+]
+
+[[package]]
+name = "security-framework-sys"
+version = "2.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "semver"
+version = "1.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090"
+
+[[package]]
+name = "serde"
+version = "1.0.192"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.192"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.108"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "serde_path_to_error"
+version = "0.1.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4beec8bce849d58d06238cb50db2e1c417cfeafa4c63f692b15c82b7c80f8335"
+dependencies = [
+ "itoa",
+ "serde",
+]
+
+[[package]]
+name = "sha2"
+version = "0.9.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800"
+dependencies = [
+ "block-buffer",
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+ "opaque-debug",
+]
+
+[[package]]
+name = "sharded-slab"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
+dependencies = [
+ "lazy_static",
+]
+
+[[package]]
+name = "shlex"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380"
+
+[[package]]
+name = "signal-hook-registry"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "slab"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "smallvec"
+version = "1.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970"
+
+[[package]]
+name = "socket2"
+version = "0.4.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "socket2"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9"
+dependencies = [
+ "libc",
+ "windows-sys",
+]
+
+[[package]]
+name = "spin"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
+
+[[package]]
+name = "spin"
+version = "0.9.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
+
+[[package]]
+name = "subtle"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
+
+[[package]]
+name = "syn"
+version = "2.0.39"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.50"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.50"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "thread_local"
+version = "1.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+]
+
+[[package]]
+name = "tokio"
+version = "1.34.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9"
+dependencies = [
+ "backtrace",
+ "bytes",
+ "libc",
+ "mio",
+ "num_cpus",
+ "parking_lot",
+ "pin-project-lite",
+ "signal-hook-registry",
+ "socket2 0.5.5",
+ "tokio-macros",
+ "windows-sys",
+]
+
+[[package]]
+name = "tokio-macros"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tokio-rustls"
+version = "0.23.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59"
+dependencies = [
+ "rustls",
+ "tokio",
+ "webpki",
+]
+
+[[package]]
+name = "tokio-stream"
+version = "0.1.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842"
+dependencies = [
+ "futures-core",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-util"
+version = "0.7.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "futures-sink",
+ "pin-project-lite",
+ "tokio",
+ "tracing",
+]
+
+[[package]]
+name = "tower"
+version = "0.4.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c"
+dependencies = [
+ "futures-core",
+ "futures-util",
+ "pin-project",
+ "pin-project-lite",
+ "tower-layer",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "tower-layer"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0"
+
+[[package]]
+name = "tower-service"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52"
+
+[[package]]
+name = "tracing"
+version = "0.1.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
+dependencies = [
+ "log",
+ "pin-project-lite",
+ "tracing-attributes",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-attributes"
+version = "0.1.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
+dependencies = [
+ "once_cell",
+ "valuable",
+]
+
+[[package]]
+name = "tracing-log"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f751112709b4e791d8ce53e32c4ed2d353565a795ce84da2285393f41557bdf2"
+dependencies = [
+ "log",
+ "once_cell",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-serde"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1"
+dependencies = [
+ "serde",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-subscriber"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77"
+dependencies = [
+ "nu-ansi-term",
+ "serde",
+ "serde_json",
+ "sharded-slab",
+ "smallvec",
+ "thread_local",
+ "tracing-core",
+ "tracing-log",
+ "tracing-serde",
+]
+
+[[package]]
+name = "try-lock"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
+
+[[package]]
+name = "turbofetch"
+version = "0.1.0"
+dependencies = [
+ "aws_lambda_events",
+ "bytes",
+ "data-encoding",
+ "futures",
+ "httparse",
+ "hyper",
+ "lambda_runtime",
+ "magic-buffer",
+ "rusoto_core",
+ "rusoto_s3",
+ "serde",
+ "serde_json",
+ "tokio",
+ "tower",
+ "tracing",
+ "tracing-subscriber",
+ "zstd",
+]
+
+[[package]]
+name = "typenum"
+version = "1.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+
+[[package]]
+name = "untrusted"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
+
+[[package]]
+name = "untrusted"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
+
+[[package]]
+name = "valuable"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
+
+[[package]]
+name = "version_check"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
+
+[[package]]
+name = "want"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
+dependencies = [
+ "try-lock",
+]
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.88"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce"
+dependencies = [
+ "cfg-if",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.88"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e397f4664c0e4e428e8313a469aaa58310d302159845980fd23b0f22a847f217"
+dependencies = [
+ "bumpalo",
+ "log",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.88"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5961017b3b08ad5f3fe39f1e79877f8ee7c23c5e5fd5eb80de95abc41f1f16b2"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.88"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.88"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b"
+
+[[package]]
+name = "web-sys"
+version = "0.3.65"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5db499c5f66323272151db0e666cd34f78617522fb0c1604d31a27c50c206a85"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "webpki"
+version = "0.22.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53"
+dependencies = [
+ "ring 0.17.5",
+ "untrusted 0.9.0",
+]
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "windows-core"
+version = "0.51.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
+
+[[package]]
+name = "xml-rs"
+version = "0.8.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fcb9cbac069e033553e8bb871be2fbdffcab578eb25bd0f7c508cedc6dcd75a"
+
+[[package]]
+name = "zeroize"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9"
+
+[[package]]
+name = "zstd"
+version = "0.9.2+zstd.1.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2390ea1bf6c038c39674f22d95f0564725fc06034a47129179810b2fc58caa54"
+dependencies = [
+ "zstd-safe",
+]
+
+[[package]]
+name = "zstd-safe"
+version = "4.1.3+zstd.1.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e99d81b99fb3c2c2c794e3fe56c305c63d5173a16a46b5850b07c935ffc7db79"
+dependencies = [
+ "libc",
+ "zstd-sys",
+]
+
+[[package]]
+name = "zstd-sys"
+version = "1.6.2+zstd.1.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2daf2f248d9ea44454bfcb2516534e8b8ad2fc91bf818a1885495fc42bc8ac9f"
+dependencies = [
+ "cc",
+ "libc",
+]
diff --git a/tvix/tools/turbofetch/Cargo.nix b/tvix/tools/turbofetch/Cargo.nix
new file mode 100644
index 0000000000..d7263727e2
--- /dev/null
+++ b/tvix/tools/turbofetch/Cargo.nix
@@ -0,0 +1,6466 @@
+# This file was @generated by crate2nix 0.13.0 with the command:
+#   "generate" "--all-features"
+# See https://github.com/kolloch/crate2nix for more info.
+
+{ nixpkgs ? <nixpkgs>
+, pkgs ? import nixpkgs { config = { }; }
+, lib ? pkgs.lib
+, stdenv ? pkgs.stdenv
+, buildRustCrateForPkgs ? pkgs: pkgs.buildRustCrate
+  # This is used as the `crateOverrides` argument for `buildRustCrate`.
+, defaultCrateOverrides ? pkgs.defaultCrateOverrides
+  # The features to enable for the root_crate or the workspace_members.
+, rootFeatures ? [ "default" ]
+  # If true, throw errors instead of issueing deprecation warnings.
+, strictDeprecation ? false
+  # Used for conditional compilation based on CPU feature detection.
+, targetFeatures ? [ ]
+  # Whether to perform release builds: longer compile times, faster binaries.
+, release ? true
+  # Additional crate2nix configuration if it exists.
+, crateConfig ? if builtins.pathExists ./crate-config.nix
+  then pkgs.callPackage ./crate-config.nix { }
+  else { }
+}:
+
+rec {
+  #
+  # "public" attributes that we attempt to keep stable with new versions of crate2nix.
+  #
+
+  rootCrate = rec {
+    packageId = "turbofetch";
+
+    # Use this attribute to refer to the derivation building your root crate package.
+    # You can override the features with rootCrate.build.override { features = [ "default" "feature1" ... ]; }.
+    build = internal.buildRustCrateWithFeatures {
+      inherit packageId;
+    };
+
+    # Debug support which might change between releases.
+    # File a bug if you depend on any for non-debug work!
+    debug = internal.debugCrate { inherit packageId; };
+  };
+  # Refer your crate build derivation by name here.
+  # You can override the features with
+  # workspaceMembers."${crateName}".build.override { features = [ "default" "feature1" ... ]; }.
+  workspaceMembers = {
+    "turbofetch" = rec {
+      packageId = "turbofetch";
+      build = internal.buildRustCrateWithFeatures {
+        packageId = "turbofetch";
+      };
+
+      # Debug support which might change between releases.
+      # File a bug if you depend on any for non-debug work!
+      debug = internal.debugCrate { inherit packageId; };
+    };
+  };
+
+  # A derivation that joins the outputs of all workspace members together.
+  allWorkspaceMembers = pkgs.symlinkJoin {
+    name = "all-workspace-members";
+    paths =
+      let members = builtins.attrValues workspaceMembers;
+      in builtins.map (m: m.build) members;
+  };
+
+  #
+  # "internal" ("private") attributes that may change in every new version of crate2nix.
+  #
+
+  internal = rec {
+    # Build and dependency information for crates.
+    # Many of the fields are passed one-to-one to buildRustCrate.
+    #
+    # Noteworthy:
+    # * `dependencies`/`buildDependencies`: similar to the corresponding fields for buildRustCrate.
+    #   but with additional information which is used during dependency/feature resolution.
+    # * `resolvedDependencies`: the selected default features reported by cargo - only included for debugging.
+    # * `devDependencies` as of now not used by `buildRustCrate` but used to
+    #   inject test dependencies into the build
+
+    crates = {
+      "addr2line" = rec {
+        crateName = "addr2line";
+        version = "0.21.0";
+        edition = "2018";
+        sha256 = "1jx0k3iwyqr8klqbzk6kjvr496yd94aspis10vwsj5wy7gib4c4a";
+        dependencies = [
+          {
+            name = "gimli";
+            packageId = "gimli";
+            usesDefaultFeatures = false;
+            features = [ "read" ];
+          }
+        ];
+        features = {
+          "alloc" = [ "dep:alloc" ];
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "core" = [ "dep:core" ];
+          "cpp_demangle" = [ "dep:cpp_demangle" ];
+          "default" = [ "rustc-demangle" "cpp_demangle" "std-object" "fallible-iterator" "smallvec" "memmap2" ];
+          "fallible-iterator" = [ "dep:fallible-iterator" ];
+          "memmap2" = [ "dep:memmap2" ];
+          "object" = [ "dep:object" ];
+          "rustc-demangle" = [ "dep:rustc-demangle" ];
+          "rustc-dep-of-std" = [ "core" "alloc" "compiler_builtins" "gimli/rustc-dep-of-std" ];
+          "smallvec" = [ "dep:smallvec" ];
+          "std" = [ "gimli/std" ];
+          "std-object" = [ "std" "object" "object/std" "object/compression" "gimli/endian-reader" ];
+        };
+      };
+      "adler" = rec {
+        crateName = "adler";
+        version = "1.0.2";
+        edition = "2015";
+        sha256 = "1zim79cvzd5yrkzl3nyfx0avijwgk9fqv3yrscdy1cc79ih02qpj";
+        authors = [
+          "Jonas Schievink <jonasschievink@gmail.com>"
+        ];
+        features = {
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "core" = [ "dep:core" ];
+          "default" = [ "std" ];
+          "rustc-dep-of-std" = [ "core" "compiler_builtins" ];
+        };
+      };
+      "android-tzdata" = rec {
+        crateName = "android-tzdata";
+        version = "0.1.1";
+        edition = "2018";
+        sha256 = "1w7ynjxrfs97xg3qlcdns4kgfpwcdv824g611fq32cag4cdr96g9";
+        authors = [
+          "RumovZ"
+        ];
+
+      };
+      "android_system_properties" = rec {
+        crateName = "android_system_properties";
+        version = "0.1.5";
+        edition = "2018";
+        sha256 = "04b3wrz12837j7mdczqd95b732gw5q7q66cv4yn4646lvccp57l1";
+        authors = [
+          "Nicolas Silva <nical@fastmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+          }
+        ];
+
+      };
+      "async-stream" = rec {
+        crateName = "async-stream";
+        version = "0.3.5";
+        edition = "2018";
+        sha256 = "0l8sjq1rylkb1ak0pdyjn83b3k6x36j22myngl4sqqgg7whdsmnd";
+        authors = [
+          "Carl Lerche <me@carllerche.com>"
+        ];
+        dependencies = [
+          {
+            name = "async-stream-impl";
+            packageId = "async-stream-impl";
+          }
+          {
+            name = "futures-core";
+            packageId = "futures-core";
+          }
+          {
+            name = "pin-project-lite";
+            packageId = "pin-project-lite";
+          }
+        ];
+
+      };
+      "async-stream-impl" = rec {
+        crateName = "async-stream-impl";
+        version = "0.3.5";
+        edition = "2018";
+        sha256 = "14q179j4y8p2z1d0ic6aqgy9fhwz8p9cai1ia8kpw4bw7q12mrhn";
+        procMacro = true;
+        authors = [
+          "Carl Lerche <me@carllerche.com>"
+        ];
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn";
+            features = [ "full" "visit-mut" ];
+          }
+        ];
+
+      };
+      "async-trait" = rec {
+        crateName = "async-trait";
+        version = "0.1.74";
+        edition = "2021";
+        sha256 = "1ydhbsqjqqa6bxbv0kgys2wq2vi3jpwjy57dk162ajwppgqkfrd6";
+        procMacro = true;
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn";
+            features = [ "full" "visit-mut" ];
+          }
+        ];
+
+      };
+      "autocfg" = rec {
+        crateName = "autocfg";
+        version = "1.1.0";
+        edition = "2015";
+        sha256 = "1ylp3cb47ylzabimazvbz9ms6ap784zhb6syaz6c1jqpmcmq0s6l";
+        authors = [
+          "Josh Stone <cuviper@gmail.com>"
+        ];
+
+      };
+      "aws_lambda_events" = rec {
+        crateName = "aws_lambda_events";
+        version = "0.11.1";
+        edition = "2021";
+        sha256 = "0nw3iyfgywhrqagl1083yqjg82jgv438zczh94zipwyfcvg1273p";
+        authors = [
+          "Christian Legnitto <christian@legnitto.com>"
+          "Sam Rijs <srijs@airpost.net>"
+          "David Calavera <dcalaver@amazon.com>"
+        ];
+        dependencies = [
+          {
+            name = "base64";
+            packageId = "base64 0.21.5";
+          }
+          {
+            name = "bytes";
+            packageId = "bytes";
+            optional = true;
+            features = [ "serde" ];
+          }
+          {
+            name = "http";
+            packageId = "http";
+            optional = true;
+          }
+          {
+            name = "http-body";
+            packageId = "http-body";
+            optional = true;
+          }
+          {
+            name = "http-serde";
+            packageId = "http-serde";
+            optional = true;
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+            features = [ "derive" ];
+          }
+          {
+            name = "serde_json";
+            packageId = "serde_json";
+          }
+        ];
+        features = {
+          "alb" = [ "bytes" "http" "http-body" "http-serde" "query_map" ];
+          "apigw" = [ "bytes" "http" "http-body" "http-serde" "query_map" ];
+          "autoscaling" = [ "chrono" ];
+          "bytes" = [ "dep:bytes" ];
+          "chime_bot" = [ "chrono" ];
+          "chrono" = [ "dep:chrono" ];
+          "cloudwatch_events" = [ "chrono" ];
+          "cloudwatch_logs" = [ "flate2" ];
+          "code_commit" = [ "chrono" ];
+          "codebuild" = [ "chrono" ];
+          "codedeploy" = [ "chrono" ];
+          "codepipeline_cloudwatch" = [ "chrono" ];
+          "default" = [ "activemq" "alb" "apigw" "appsync" "autoscaling" "chime_bot" "clientvpn" "cloudwatch_events" "cloudwatch_logs" "code_commit" "codebuild" "codedeploy" "codepipeline_cloudwatch" "codepipeline_job" "cognito" "config" "connect" "dynamodb" "ecr_scan" "firehose" "iam" "iot" "iot_1_click" "iot_button" "iot_deprecated" "kafka" "kinesis" "kinesis_analytics" "lambda_function_urls" "lex" "rabbitmq" "s3" "s3_batch_job" "ses" "sns" "sqs" "streams" ];
+          "dynamodb" = [ "chrono" "serde_dynamo" "streams" ];
+          "firehose" = [ "chrono" ];
+          "flate2" = [ "dep:flate2" ];
+          "http" = [ "dep:http" ];
+          "http-body" = [ "dep:http-body" ];
+          "http-serde" = [ "dep:http-serde" ];
+          "iot" = [ "bytes" "http" "http-body" "http-serde" "iam" ];
+          "iot_deprecated" = [ "iot" ];
+          "kafka" = [ "chrono" ];
+          "kinesis" = [ "chrono" ];
+          "kinesis_analytics" = [ "kinesis" ];
+          "lambda_function_urls" = [ "bytes" "http" "http-body" "http-serde" ];
+          "query_map" = [ "dep:query_map" ];
+          "s3" = [ "bytes" "chrono" "http" "http-body" "http-serde" ];
+          "s3_batch_job" = [ "s3" ];
+          "serde_dynamo" = [ "dep:serde_dynamo" ];
+          "serde_with" = [ "dep:serde_with" ];
+          "ses" = [ "chrono" ];
+          "sns" = [ "chrono" "serde_with" ];
+          "sqs" = [ "serde_with" ];
+        };
+        resolvedDefaultFeatures = [ "bytes" "http" "http-body" "http-serde" "lambda_function_urls" ];
+      };
+      "backtrace" = rec {
+        crateName = "backtrace";
+        version = "0.3.69";
+        edition = "2018";
+        sha256 = "0dsq23dhw4pfndkx2nsa1ml2g31idm7ss7ljxp8d57avygivg290";
+        authors = [
+          "The Rust Project Developers"
+        ];
+        dependencies = [
+          {
+            name = "addr2line";
+            packageId = "addr2line";
+            usesDefaultFeatures = false;
+            target = { target, features }: (!((target."windows" or false) && ("msvc" == target."env" or null) && (!("uwp" == target."vendor" or null))));
+          }
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            usesDefaultFeatures = false;
+            target = { target, features }: (!((target."windows" or false) && ("msvc" == target."env" or null) && (!("uwp" == target."vendor" or null))));
+          }
+          {
+            name = "miniz_oxide";
+            packageId = "miniz_oxide";
+            usesDefaultFeatures = false;
+            target = { target, features }: (!((target."windows" or false) && ("msvc" == target."env" or null) && (!("uwp" == target."vendor" or null))));
+          }
+          {
+            name = "object";
+            packageId = "object";
+            usesDefaultFeatures = false;
+            target = { target, features }: (!((target."windows" or false) && ("msvc" == target."env" or null) && (!("uwp" == target."vendor" or null))));
+            features = [ "read_core" "elf" "macho" "pe" "unaligned" "archive" ];
+          }
+          {
+            name = "rustc-demangle";
+            packageId = "rustc-demangle";
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "cc";
+            packageId = "cc";
+          }
+        ];
+        features = {
+          "cpp_demangle" = [ "dep:cpp_demangle" ];
+          "default" = [ "std" ];
+          "rustc-serialize" = [ "dep:rustc-serialize" ];
+          "serde" = [ "dep:serde" ];
+          "serialize-rustc" = [ "rustc-serialize" ];
+          "serialize-serde" = [ "serde" ];
+          "verify-winapi" = [ "winapi/dbghelp" "winapi/handleapi" "winapi/libloaderapi" "winapi/memoryapi" "winapi/minwindef" "winapi/processthreadsapi" "winapi/synchapi" "winapi/tlhelp32" "winapi/winbase" "winapi/winnt" ];
+          "winapi" = [ "dep:winapi" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "base64 0.13.1" = rec {
+        crateName = "base64";
+        version = "0.13.1";
+        edition = "2018";
+        sha256 = "1s494mqmzjb766fy1kqlccgfg2sdcjb6hzbvzqv2jw65fdi5h6wy";
+        authors = [
+          "Alice Maz <alice@alicemaz.com>"
+          "Marshall Pierce <marshall@mpierce.org>"
+        ];
+        features = {
+          "default" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "base64 0.20.0" = rec {
+        crateName = "base64";
+        version = "0.20.0";
+        edition = "2021";
+        sha256 = "1r855djiv8rirg37w5arazk42ya5gm5gd2bww75v14w0sy02i8hf";
+        authors = [
+          "Alice Maz <alice@alicemaz.com>"
+          "Marshall Pierce <marshall@mpierce.org>"
+        ];
+        features = {
+          "default" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "base64 0.21.5" = rec {
+        crateName = "base64";
+        version = "0.21.5";
+        edition = "2018";
+        sha256 = "1y8x2xs9nszj5ix7gg4ycn5a6wy7ca74zxwqri3bdqzdjha6lqrm";
+        authors = [
+          "Alice Maz <alice@alicemaz.com>"
+          "Marshall Pierce <marshall@mpierce.org>"
+        ];
+        features = {
+          "default" = [ "std" ];
+          "std" = [ "alloc" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "std" ];
+      };
+      "bitflags 1.3.2" = rec {
+        crateName = "bitflags";
+        version = "1.3.2";
+        edition = "2018";
+        sha256 = "12ki6w8gn1ldq7yz9y680llwk5gmrhrzszaa17g1sbrw2r2qvwxy";
+        authors = [
+          "The Rust Project Developers"
+        ];
+        features = {
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "core" = [ "dep:core" ];
+          "rustc-dep-of-std" = [ "core" "compiler_builtins" ];
+        };
+        resolvedDefaultFeatures = [ "default" ];
+      };
+      "bitflags 2.4.1" = rec {
+        crateName = "bitflags";
+        version = "2.4.1";
+        edition = "2021";
+        sha256 = "01ryy3kd671b0ll4bhdvhsz67vwz1lz53fz504injrd7wpv64xrj";
+        authors = [
+          "The Rust Project Developers"
+        ];
+        features = {
+          "arbitrary" = [ "dep:arbitrary" ];
+          "bytemuck" = [ "dep:bytemuck" ];
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "core" = [ "dep:core" ];
+          "rustc-dep-of-std" = [ "core" "compiler_builtins" ];
+          "serde" = [ "dep:serde" ];
+        };
+      };
+      "block-buffer" = rec {
+        crateName = "block-buffer";
+        version = "0.9.0";
+        edition = "2018";
+        sha256 = "1r4pf90s7d7lj1wdjhlnqa26vvbm6pnc33z138lxpnp9srpi2lj1";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        dependencies = [
+          {
+            name = "generic-array";
+            packageId = "generic-array";
+          }
+        ];
+        features = {
+          "block-padding" = [ "dep:block-padding" ];
+        };
+      };
+      "bumpalo" = rec {
+        crateName = "bumpalo";
+        version = "3.14.0";
+        edition = "2021";
+        sha256 = "1v4arnv9kwk54v5d0qqpv4vyw2sgr660nk0w3apzixi1cm3yfc3z";
+        authors = [
+          "Nick Fitzgerald <fitzgen@gmail.com>"
+        ];
+        features = {
+          "allocator-api2" = [ "dep:allocator-api2" ];
+        };
+        resolvedDefaultFeatures = [ "default" ];
+      };
+      "bytes" = rec {
+        crateName = "bytes";
+        version = "1.5.0";
+        edition = "2018";
+        sha256 = "08w2i8ac912l8vlvkv3q51cd4gr09pwlg3sjsjffcizlrb0i5gd2";
+        authors = [
+          "Carl Lerche <me@carllerche.com>"
+          "Sean McArthur <sean@seanmonstar.com>"
+        ];
+        dependencies = [
+          {
+            name = "serde";
+            packageId = "serde";
+            optional = true;
+            usesDefaultFeatures = false;
+            features = [ "alloc" ];
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "serde" = [ "dep:serde" ];
+        };
+        resolvedDefaultFeatures = [ "default" "serde" "std" ];
+      };
+      "cc" = rec {
+        crateName = "cc";
+        version = "1.0.83";
+        edition = "2018";
+        crateBin = [ ];
+        sha256 = "1l643zidlb5iy1dskc5ggqs4wqa29a02f44piczqc8zcnsq4y5zi";
+        authors = [
+          "Alex Crichton <alex@alexcrichton.com>"
+        ];
+        dependencies = [
+          {
+            name = "jobserver";
+            packageId = "jobserver";
+            optional = true;
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            usesDefaultFeatures = false;
+            target = { target, features }: (target."unix" or false);
+          }
+        ];
+        features = {
+          "jobserver" = [ "dep:jobserver" ];
+          "parallel" = [ "jobserver" ];
+        };
+        resolvedDefaultFeatures = [ "jobserver" "parallel" ];
+      };
+      "cfg-if" = rec {
+        crateName = "cfg-if";
+        version = "1.0.0";
+        edition = "2018";
+        sha256 = "1za0vb97n4brpzpv8lsbnzmq5r8f2b0cpqqr0sy8h5bn751xxwds";
+        authors = [
+          "Alex Crichton <alex@alexcrichton.com>"
+        ];
+        features = {
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "core" = [ "dep:core" ];
+          "rustc-dep-of-std" = [ "core" "compiler_builtins" ];
+        };
+      };
+      "chrono" = rec {
+        crateName = "chrono";
+        version = "0.4.31";
+        edition = "2021";
+        sha256 = "0f6vg67pipm8cziad2yms6a639pssnvysk1m05dd9crymmdnhb3z";
+        dependencies = [
+          {
+            name = "android-tzdata";
+            packageId = "android-tzdata";
+            optional = true;
+            target = { target, features }: ("android" == target."os" or null);
+          }
+          {
+            name = "iana-time-zone";
+            packageId = "iana-time-zone";
+            optional = true;
+            target = { target, features }: (target."unix" or false);
+            features = [ "fallback" ];
+          }
+          {
+            name = "num-traits";
+            packageId = "num-traits";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "windows-targets";
+            packageId = "windows-targets";
+            optional = true;
+            target = { target, features }: (target."windows" or false);
+          }
+        ];
+        features = {
+          "android-tzdata" = [ "dep:android-tzdata" ];
+          "arbitrary" = [ "dep:arbitrary" ];
+          "clock" = [ "std" "winapi" "iana-time-zone" "android-tzdata" ];
+          "default" = [ "clock" "std" "oldtime" "wasmbind" ];
+          "iana-time-zone" = [ "dep:iana-time-zone" ];
+          "js-sys" = [ "dep:js-sys" ];
+          "pure-rust-locales" = [ "dep:pure-rust-locales" ];
+          "rkyv" = [ "dep:rkyv" ];
+          "rustc-serialize" = [ "dep:rustc-serialize" ];
+          "serde" = [ "dep:serde" ];
+          "unstable-locales" = [ "pure-rust-locales" "alloc" ];
+          "wasm-bindgen" = [ "dep:wasm-bindgen" ];
+          "wasmbind" = [ "wasm-bindgen" "js-sys" ];
+          "winapi" = [ "windows-targets" ];
+          "windows-targets" = [ "dep:windows-targets" ];
+        };
+        resolvedDefaultFeatures = [ "android-tzdata" "clock" "iana-time-zone" "serde" "std" "winapi" "windows-targets" ];
+      };
+      "core-foundation" = rec {
+        crateName = "core-foundation";
+        version = "0.9.3";
+        edition = "2015";
+        sha256 = "0ii1ihpjb30fk38gdikm5wqlkmyr8k46fh4k2r8sagz5dng7ljhr";
+        authors = [
+          "The Servo Project Developers"
+        ];
+        dependencies = [
+          {
+            name = "core-foundation-sys";
+            packageId = "core-foundation-sys";
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+          }
+        ];
+        features = {
+          "chrono" = [ "dep:chrono" ];
+          "mac_os_10_7_support" = [ "core-foundation-sys/mac_os_10_7_support" ];
+          "mac_os_10_8_features" = [ "core-foundation-sys/mac_os_10_8_features" ];
+          "uuid" = [ "dep:uuid" ];
+          "with-chrono" = [ "chrono" ];
+          "with-uuid" = [ "uuid" ];
+        };
+      };
+      "core-foundation-sys" = rec {
+        crateName = "core-foundation-sys";
+        version = "0.8.4";
+        edition = "2015";
+        sha256 = "1yhf471qj6snnm2mcswai47vsbc9w30y4abmdp4crb4av87sb5p4";
+        authors = [
+          "The Servo Project Developers"
+        ];
+        features = { };
+      };
+      "cpufeatures" = rec {
+        crateName = "cpufeatures";
+        version = "0.2.11";
+        edition = "2018";
+        sha256 = "1l0gzsyy576n017g9bf0vkv5hhg9cpz1h1libxyfdlzcgbh0yhnf";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (pkgs.rust.lib.toRustTarget stdenv.hostPlatform == "aarch64-linux-android");
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (("aarch64" == target."arch" or null) && ("linux" == target."os" or null));
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (("aarch64" == target."arch" or null) && ("apple" == target."vendor" or null));
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (("loongarch64" == target."arch" or null) && ("linux" == target."os" or null));
+          }
+        ];
+
+      };
+      "crc32fast" = rec {
+        crateName = "crc32fast";
+        version = "1.3.2";
+        edition = "2015";
+        sha256 = "03c8f29yx293yf43xar946xbls1g60c207m9drf8ilqhr25vsh5m";
+        authors = [
+          "Sam Rijs <srijs@airpost.net>"
+          "Alex Crichton <alex@alexcrichton.com>"
+        ];
+        dependencies = [
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "crypto-mac" = rec {
+        crateName = "crypto-mac";
+        version = "0.11.1";
+        edition = "2018";
+        sha256 = "05672ncc54h66vph42s0a42ljl69bwnqjh0x4xgj2v1395psildi";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        dependencies = [
+          {
+            name = "generic-array";
+            packageId = "generic-array";
+          }
+          {
+            name = "subtle";
+            packageId = "subtle";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "blobby" = [ "dep:blobby" ];
+          "cipher" = [ "dep:cipher" ];
+          "dev" = [ "blobby" ];
+        };
+      };
+      "data-encoding" = rec {
+        crateName = "data-encoding";
+        version = "2.4.0";
+        edition = "2018";
+        sha256 = "023k3dk8422jgbj7k72g63x51h1mhv91dhw1j4h205vzh6fnrrn2";
+        authors = [
+          "Julien Cretin <git@ia0.eu>"
+        ];
+        features = {
+          "default" = [ "std" ];
+          "std" = [ "alloc" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "std" ];
+      };
+      "digest" = rec {
+        crateName = "digest";
+        version = "0.9.0";
+        edition = "2018";
+        sha256 = "0rmhvk33rgvd6ll71z8sng91a52rw14p0drjn1da0mqa138n1pfk";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        dependencies = [
+          {
+            name = "generic-array";
+            packageId = "generic-array";
+          }
+        ];
+        features = {
+          "blobby" = [ "dep:blobby" ];
+          "dev" = [ "blobby" ];
+          "std" = [ "alloc" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "std" ];
+      };
+      "dirs-next" = rec {
+        crateName = "dirs-next";
+        version = "2.0.0";
+        edition = "2018";
+        sha256 = "1q9kr151h9681wwp6is18750ssghz6j9j7qm7qi1ngcwy7mzi35r";
+        authors = [
+          "The @xdg-rs members"
+        ];
+        dependencies = [
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "dirs-sys-next";
+            packageId = "dirs-sys-next";
+          }
+        ];
+
+      };
+      "dirs-sys-next" = rec {
+        crateName = "dirs-sys-next";
+        version = "0.1.2";
+        edition = "2018";
+        sha256 = "0kavhavdxv4phzj4l0psvh55hszwnr0rcz8sxbvx20pyqi2a3gaf";
+        authors = [
+          "The @xdg-rs members"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (target."unix" or false);
+          }
+          {
+            name = "redox_users";
+            packageId = "redox_users";
+            usesDefaultFeatures = false;
+            target = { target, features }: ("redox" == target."os" or null);
+          }
+          {
+            name = "winapi";
+            packageId = "winapi";
+            target = { target, features }: (target."windows" or false);
+            features = [ "knownfolders" "objbase" "shlobj" "winbase" "winerror" ];
+          }
+        ];
+
+      };
+      "fnv" = rec {
+        crateName = "fnv";
+        version = "1.0.7";
+        edition = "2015";
+        sha256 = "1hc2mcqha06aibcaza94vbi81j6pr9a1bbxrxjfhc91zin8yr7iz";
+        libPath = "lib.rs";
+        authors = [
+          "Alex Crichton <alex@alexcrichton.com>"
+        ];
+        features = {
+          "default" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "futures" = rec {
+        crateName = "futures";
+        version = "0.3.30";
+        edition = "2018";
+        sha256 = "1c04g14bccmprwsvx2j9m2blhwrynq7vhl151lsvcv4gi0b6jp34";
+        dependencies = [
+          {
+            name = "futures-channel";
+            packageId = "futures-channel";
+            usesDefaultFeatures = false;
+            features = [ "sink" ];
+          }
+          {
+            name = "futures-core";
+            packageId = "futures-core";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-executor";
+            packageId = "futures-executor";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-io";
+            packageId = "futures-io";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-sink";
+            packageId = "futures-sink";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-task";
+            packageId = "futures-task";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-util";
+            packageId = "futures-util";
+            usesDefaultFeatures = false;
+            features = [ "sink" ];
+          }
+        ];
+        features = {
+          "alloc" = [ "futures-core/alloc" "futures-task/alloc" "futures-sink/alloc" "futures-channel/alloc" "futures-util/alloc" ];
+          "async-await" = [ "futures-util/async-await" "futures-util/async-await-macro" ];
+          "bilock" = [ "futures-util/bilock" ];
+          "compat" = [ "std" "futures-util/compat" ];
+          "default" = [ "std" "async-await" "executor" ];
+          "executor" = [ "std" "futures-executor/std" ];
+          "futures-executor" = [ "dep:futures-executor" ];
+          "io-compat" = [ "compat" "futures-util/io-compat" ];
+          "std" = [ "alloc" "futures-core/std" "futures-task/std" "futures-io/std" "futures-sink/std" "futures-util/std" "futures-util/io" "futures-util/channel" ];
+          "thread-pool" = [ "executor" "futures-executor/thread-pool" ];
+          "unstable" = [ "futures-core/unstable" "futures-task/unstable" "futures-channel/unstable" "futures-io/unstable" "futures-util/unstable" ];
+          "write-all-vectored" = [ "futures-util/write-all-vectored" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "async-await" "default" "executor" "futures-executor" "std" ];
+      };
+      "futures-channel" = rec {
+        crateName = "futures-channel";
+        version = "0.3.30";
+        edition = "2018";
+        sha256 = "0y6b7xxqdjm9hlcjpakcg41qfl7lihf6gavk8fyqijsxhvbzgj7a";
+        dependencies = [
+          {
+            name = "futures-core";
+            packageId = "futures-core";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-sink";
+            packageId = "futures-sink";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "alloc" = [ "futures-core/alloc" ];
+          "default" = [ "std" ];
+          "futures-sink" = [ "dep:futures-sink" ];
+          "sink" = [ "futures-sink" ];
+          "std" = [ "alloc" "futures-core/std" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "futures-sink" "sink" "std" ];
+      };
+      "futures-core" = rec {
+        crateName = "futures-core";
+        version = "0.3.30";
+        edition = "2018";
+        sha256 = "07aslayrn3lbggj54kci0ishmd1pr367fp7iks7adia1p05miinz";
+        features = {
+          "default" = [ "std" ];
+          "portable-atomic" = [ "dep:portable-atomic" ];
+          "std" = [ "alloc" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "std" ];
+      };
+      "futures-executor" = rec {
+        crateName = "futures-executor";
+        version = "0.3.30";
+        edition = "2018";
+        sha256 = "07dh08gs9vfll2h36kq32q9xd86xm6lyl9xikmmwlkqnmrrgqxm5";
+        dependencies = [
+          {
+            name = "futures-core";
+            packageId = "futures-core";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-task";
+            packageId = "futures-task";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-util";
+            packageId = "futures-util";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "num_cpus" = [ "dep:num_cpus" ];
+          "std" = [ "futures-core/std" "futures-task/std" "futures-util/std" ];
+          "thread-pool" = [ "std" "num_cpus" ];
+        };
+        resolvedDefaultFeatures = [ "std" ];
+      };
+      "futures-io" = rec {
+        crateName = "futures-io";
+        version = "0.3.30";
+        edition = "2018";
+        sha256 = "1hgh25isvsr4ybibywhr4dpys8mjnscw4wfxxwca70cn1gi26im4";
+        features = {
+          "default" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "std" ];
+      };
+      "futures-macro" = rec {
+        crateName = "futures-macro";
+        version = "0.3.30";
+        edition = "2018";
+        sha256 = "1b49qh9d402y8nka4q6wvvj0c88qq91wbr192mdn5h54nzs0qxc7";
+        procMacro = true;
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn";
+            features = [ "full" ];
+          }
+        ];
+
+      };
+      "futures-sink" = rec {
+        crateName = "futures-sink";
+        version = "0.3.30";
+        edition = "2018";
+        sha256 = "1dag8xyyaya8n8mh8smx7x6w2dpmafg2din145v973a3hw7f1f4z";
+        features = {
+          "default" = [ "std" ];
+          "std" = [ "alloc" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "std" ];
+      };
+      "futures-task" = rec {
+        crateName = "futures-task";
+        version = "0.3.30";
+        edition = "2018";
+        sha256 = "013h1724454hj8qczp8vvs10qfiqrxr937qsrv6rhii68ahlzn1q";
+        features = {
+          "default" = [ "std" ];
+          "std" = [ "alloc" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "std" ];
+      };
+      "futures-util" = rec {
+        crateName = "futures-util";
+        version = "0.3.30";
+        edition = "2018";
+        sha256 = "0j0xqhcir1zf2dcbpd421kgw6wvsk0rpxflylcysn1rlp3g02r1x";
+        dependencies = [
+          {
+            name = "futures-channel";
+            packageId = "futures-channel";
+            optional = true;
+            usesDefaultFeatures = false;
+            features = [ "std" ];
+          }
+          {
+            name = "futures-core";
+            packageId = "futures-core";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-io";
+            packageId = "futures-io";
+            optional = true;
+            usesDefaultFeatures = false;
+            features = [ "std" ];
+          }
+          {
+            name = "futures-macro";
+            packageId = "futures-macro";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-sink";
+            packageId = "futures-sink";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-task";
+            packageId = "futures-task";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "memchr";
+            packageId = "memchr";
+            optional = true;
+          }
+          {
+            name = "pin-project-lite";
+            packageId = "pin-project-lite";
+          }
+          {
+            name = "pin-utils";
+            packageId = "pin-utils";
+          }
+          {
+            name = "slab";
+            packageId = "slab";
+            optional = true;
+          }
+        ];
+        features = {
+          "alloc" = [ "futures-core/alloc" "futures-task/alloc" ];
+          "async-await-macro" = [ "async-await" "futures-macro" ];
+          "channel" = [ "std" "futures-channel" ];
+          "compat" = [ "std" "futures_01" ];
+          "default" = [ "std" "async-await" "async-await-macro" ];
+          "futures-channel" = [ "dep:futures-channel" ];
+          "futures-io" = [ "dep:futures-io" ];
+          "futures-macro" = [ "dep:futures-macro" ];
+          "futures-sink" = [ "dep:futures-sink" ];
+          "futures_01" = [ "dep:futures_01" ];
+          "io" = [ "std" "futures-io" "memchr" ];
+          "io-compat" = [ "io" "compat" "tokio-io" ];
+          "memchr" = [ "dep:memchr" ];
+          "portable-atomic" = [ "futures-core/portable-atomic" ];
+          "sink" = [ "futures-sink" ];
+          "slab" = [ "dep:slab" ];
+          "std" = [ "alloc" "futures-core/std" "futures-task/std" "slab" ];
+          "tokio-io" = [ "dep:tokio-io" ];
+          "unstable" = [ "futures-core/unstable" "futures-task/unstable" ];
+          "write-all-vectored" = [ "io" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "async-await" "async-await-macro" "channel" "futures-channel" "futures-io" "futures-macro" "futures-sink" "io" "memchr" "sink" "slab" "std" ];
+      };
+      "generic-array" = rec {
+        crateName = "generic-array";
+        version = "0.14.7";
+        edition = "2015";
+        sha256 = "16lyyrzrljfq424c3n8kfwkqihlimmsg5nhshbbp48np3yjrqr45";
+        libName = "generic_array";
+        authors = [
+          "Bartłomiej Kamiński <fizyk20@gmail.com>"
+          "Aaron Trent <novacrazy@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "typenum";
+            packageId = "typenum";
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "version_check";
+            packageId = "version_check";
+          }
+        ];
+        features = {
+          "serde" = [ "dep:serde" ];
+          "zeroize" = [ "dep:zeroize" ];
+        };
+      };
+      "getrandom" = rec {
+        crateName = "getrandom";
+        version = "0.2.11";
+        edition = "2018";
+        sha256 = "03q7120cc2kn7ry013i67zmjl2g9q73h1ks5z08hq5v9syz0d47y";
+        authors = [
+          "The Rand Project Developers"
+        ];
+        dependencies = [
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            usesDefaultFeatures = false;
+            target = { target, features }: (target."unix" or false);
+          }
+          {
+            name = "wasi";
+            packageId = "wasi";
+            usesDefaultFeatures = false;
+            target = { target, features }: ("wasi" == target."os" or null);
+          }
+        ];
+        features = {
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "core" = [ "dep:core" ];
+          "js" = [ "wasm-bindgen" "js-sys" ];
+          "js-sys" = [ "dep:js-sys" ];
+          "rustc-dep-of-std" = [ "compiler_builtins" "core" "libc/rustc-dep-of-std" "wasi/rustc-dep-of-std" ];
+          "wasm-bindgen" = [ "dep:wasm-bindgen" ];
+        };
+        resolvedDefaultFeatures = [ "std" ];
+      };
+      "gimli" = rec {
+        crateName = "gimli";
+        version = "0.28.0";
+        edition = "2018";
+        sha256 = "1h7hcl3chfvd2gfrrxjymnwj7anqxjslvz20kcargkvsya2dgf3g";
+        features = {
+          "default" = [ "read-all" "write" ];
+          "endian-reader" = [ "read" "dep:stable_deref_trait" ];
+          "fallible-iterator" = [ "dep:fallible-iterator" ];
+          "read" = [ "read-core" ];
+          "read-all" = [ "read" "std" "fallible-iterator" "endian-reader" ];
+          "rustc-dep-of-std" = [ "dep:core" "dep:alloc" "dep:compiler_builtins" ];
+          "std" = [ "fallible-iterator?/std" "stable_deref_trait?/std" ];
+          "write" = [ "dep:indexmap" ];
+        };
+        resolvedDefaultFeatures = [ "read" "read-core" ];
+      };
+      "h2" = rec {
+        crateName = "h2";
+        version = "0.3.21";
+        edition = "2018";
+        sha256 = "0cq8g5bgk3fihnqicy3g8gc3dpsalzqjg4bjyip9g4my26m27z4i";
+        authors = [
+          "Carl Lerche <me@carllerche.com>"
+          "Sean McArthur <sean@seanmonstar.com>"
+        ];
+        dependencies = [
+          {
+            name = "bytes";
+            packageId = "bytes";
+          }
+          {
+            name = "fnv";
+            packageId = "fnv";
+          }
+          {
+            name = "futures-core";
+            packageId = "futures-core";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-sink";
+            packageId = "futures-sink";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-util";
+            packageId = "futures-util";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "http";
+            packageId = "http";
+          }
+          {
+            name = "indexmap";
+            packageId = "indexmap";
+            features = [ "std" ];
+          }
+          {
+            name = "slab";
+            packageId = "slab";
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "io-util" ];
+          }
+          {
+            name = "tokio-util";
+            packageId = "tokio-util";
+            features = [ "codec" ];
+          }
+          {
+            name = "tracing";
+            packageId = "tracing";
+            usesDefaultFeatures = false;
+            features = [ "std" ];
+          }
+        ];
+        devDependencies = [
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "rt-multi-thread" "macros" "sync" "net" ];
+          }
+        ];
+        features = { };
+      };
+      "hashbrown" = rec {
+        crateName = "hashbrown";
+        version = "0.12.3";
+        edition = "2021";
+        sha256 = "1268ka4750pyg2pbgsr43f0289l5zah4arir2k4igx5a8c6fg7la";
+        authors = [
+          "Amanieu d'Antras <amanieu@gmail.com>"
+        ];
+        features = {
+          "ahash" = [ "dep:ahash" ];
+          "ahash-compile-time-rng" = [ "ahash/compile-time-rng" ];
+          "alloc" = [ "dep:alloc" ];
+          "bumpalo" = [ "dep:bumpalo" ];
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "core" = [ "dep:core" ];
+          "default" = [ "ahash" "inline-more" ];
+          "rayon" = [ "dep:rayon" ];
+          "rustc-dep-of-std" = [ "nightly" "core" "compiler_builtins" "alloc" "rustc-internal-api" ];
+          "serde" = [ "dep:serde" ];
+        };
+        resolvedDefaultFeatures = [ "raw" ];
+      };
+      "hermit-abi" = rec {
+        crateName = "hermit-abi";
+        version = "0.3.3";
+        edition = "2021";
+        sha256 = "1dyc8qsjh876n74a3rcz8h43s27nj1sypdhsn2ms61bd3b47wzyp";
+        authors = [
+          "Stefan Lankes"
+        ];
+        features = {
+          "alloc" = [ "dep:alloc" ];
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "core" = [ "dep:core" ];
+          "rustc-dep-of-std" = [ "core" "alloc" "compiler_builtins/rustc-dep-of-std" ];
+        };
+        resolvedDefaultFeatures = [ "default" ];
+      };
+      "hex" = rec {
+        crateName = "hex";
+        version = "0.4.3";
+        edition = "2018";
+        sha256 = "0w1a4davm1lgzpamwnba907aysmlrnygbqmfis2mqjx5m552a93z";
+        authors = [
+          "KokaKiwi <kokakiwi@kokakiwi.net>"
+        ];
+        features = {
+          "default" = [ "std" ];
+          "serde" = [ "dep:serde" ];
+          "std" = [ "alloc" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "std" ];
+      };
+      "hmac" = rec {
+        crateName = "hmac";
+        version = "0.11.0";
+        edition = "2018";
+        sha256 = "16z61aibdg4di40sqi4ks2s4rz6r29w4sx4gvblfph3yxch26aia";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        dependencies = [
+          {
+            name = "crypto-mac";
+            packageId = "crypto-mac";
+          }
+          {
+            name = "digest";
+            packageId = "digest";
+          }
+        ];
+        devDependencies = [
+          {
+            name = "crypto-mac";
+            packageId = "crypto-mac";
+            features = [ "dev" ];
+          }
+        ];
+        features = {
+          "std" = [ "crypto-mac/std" ];
+        };
+      };
+      "http" = rec {
+        crateName = "http";
+        version = "0.2.10";
+        edition = "2018";
+        sha256 = "0l61nzcb5rdfchn7gpml0wngipflbqarrq3q5ga30rw9msy9lnzr";
+        authors = [
+          "Alex Crichton <alex@alexcrichton.com>"
+          "Carl Lerche <me@carllerche.com>"
+          "Sean McArthur <sean@seanmonstar.com>"
+        ];
+        dependencies = [
+          {
+            name = "bytes";
+            packageId = "bytes";
+          }
+          {
+            name = "fnv";
+            packageId = "fnv";
+          }
+          {
+            name = "itoa";
+            packageId = "itoa";
+          }
+        ];
+
+      };
+      "http-body" = rec {
+        crateName = "http-body";
+        version = "0.4.5";
+        edition = "2018";
+        sha256 = "1l967qwwlvhp198xdrnc0p5d7jwfcp6q2lm510j6zqw4s4b8zwym";
+        authors = [
+          "Carl Lerche <me@carllerche.com>"
+          "Lucio Franco <luciofranco14@gmail.com>"
+          "Sean McArthur <sean@seanmonstar.com>"
+        ];
+        dependencies = [
+          {
+            name = "bytes";
+            packageId = "bytes";
+          }
+          {
+            name = "http";
+            packageId = "http";
+          }
+          {
+            name = "pin-project-lite";
+            packageId = "pin-project-lite";
+          }
+        ];
+
+      };
+      "http-serde" = rec {
+        crateName = "http-serde";
+        version = "1.1.3";
+        edition = "2021";
+        sha256 = "1vnald3g10gxj15dc5jjjk7aff23p1zly0xgzhn5gwfrb9k0nmkg";
+        authors = [
+          "Kornel <kornel@geekhood.net>"
+        ];
+        dependencies = [
+          {
+            name = "http";
+            packageId = "http";
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+            features = [ "derive" ];
+          }
+        ];
+
+      };
+      "httparse" = rec {
+        crateName = "httparse";
+        version = "1.8.0";
+        edition = "2018";
+        sha256 = "010rrfahm1jss3p022fqf3j3jmm72vhn4iqhykahb9ynpaag75yq";
+        authors = [
+          "Sean McArthur <sean@seanmonstar.com>"
+        ];
+        features = {
+          "default" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "httpdate" = rec {
+        crateName = "httpdate";
+        version = "1.0.3";
+        edition = "2021";
+        sha256 = "1aa9rd2sac0zhjqh24c9xvir96g188zldkx0hr6dnnlx5904cfyz";
+        authors = [
+          "Pyfisch <pyfisch@posteo.org>"
+        ];
+
+      };
+      "hyper" = rec {
+        crateName = "hyper";
+        version = "0.14.27";
+        edition = "2018";
+        sha256 = "0s2l74p3harvjgb0bvaxlxgxq71vpfrzv0cqz2p9w8d8akbczcgz";
+        authors = [
+          "Sean McArthur <sean@seanmonstar.com>"
+        ];
+        dependencies = [
+          {
+            name = "bytes";
+            packageId = "bytes";
+          }
+          {
+            name = "futures-channel";
+            packageId = "futures-channel";
+          }
+          {
+            name = "futures-core";
+            packageId = "futures-core";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-util";
+            packageId = "futures-util";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "h2";
+            packageId = "h2";
+            optional = true;
+          }
+          {
+            name = "http";
+            packageId = "http";
+          }
+          {
+            name = "http-body";
+            packageId = "http-body";
+          }
+          {
+            name = "httparse";
+            packageId = "httparse";
+          }
+          {
+            name = "httpdate";
+            packageId = "httpdate";
+          }
+          {
+            name = "itoa";
+            packageId = "itoa";
+          }
+          {
+            name = "pin-project-lite";
+            packageId = "pin-project-lite";
+          }
+          {
+            name = "socket2";
+            packageId = "socket2 0.4.10";
+            optional = true;
+            features = [ "all" ];
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "sync" ];
+          }
+          {
+            name = "tower-service";
+            packageId = "tower-service";
+          }
+          {
+            name = "tracing";
+            packageId = "tracing";
+            usesDefaultFeatures = false;
+            features = [ "std" ];
+          }
+          {
+            name = "want";
+            packageId = "want";
+          }
+        ];
+        devDependencies = [
+          {
+            name = "futures-util";
+            packageId = "futures-util";
+            usesDefaultFeatures = false;
+            features = [ "alloc" ];
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "fs" "macros" "io-std" "io-util" "rt" "rt-multi-thread" "sync" "time" "test-util" ];
+          }
+        ];
+        features = {
+          "ffi" = [ "libc" ];
+          "full" = [ "client" "http1" "http2" "server" "stream" "runtime" ];
+          "h2" = [ "dep:h2" ];
+          "http2" = [ "h2" ];
+          "libc" = [ "dep:libc" ];
+          "runtime" = [ "tcp" "tokio/rt" "tokio/time" ];
+          "socket2" = [ "dep:socket2" ];
+          "tcp" = [ "socket2" "tokio/net" "tokio/rt" "tokio/time" ];
+        };
+        resolvedDefaultFeatures = [ "client" "default" "h2" "http1" "http2" "runtime" "server" "socket2" "stream" "tcp" ];
+      };
+      "hyper-rustls" = rec {
+        crateName = "hyper-rustls";
+        version = "0.23.2";
+        edition = "2018";
+        sha256 = "0736s6a32dqr107f943xaz1n05flbinq6l19lq1wsrxkc5g9d20p";
+        dependencies = [
+          {
+            name = "http";
+            packageId = "http";
+          }
+          {
+            name = "hyper";
+            packageId = "hyper";
+            usesDefaultFeatures = false;
+            features = [ "client" ];
+          }
+          {
+            name = "log";
+            packageId = "log";
+            optional = true;
+          }
+          {
+            name = "rustls";
+            packageId = "rustls";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "rustls-native-certs";
+            packageId = "rustls-native-certs";
+            optional = true;
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+          }
+          {
+            name = "tokio-rustls";
+            packageId = "tokio-rustls";
+            usesDefaultFeatures = false;
+          }
+        ];
+        devDependencies = [
+          {
+            name = "hyper";
+            packageId = "hyper";
+            features = [ "full" ];
+          }
+          {
+            name = "rustls";
+            packageId = "rustls";
+            usesDefaultFeatures = false;
+            features = [ "tls12" ];
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "io-std" "macros" "net" "rt-multi-thread" ];
+          }
+        ];
+        features = {
+          "default" = [ "native-tokio" "http1" "tls12" "logging" ];
+          "http1" = [ "hyper/http1" ];
+          "http2" = [ "hyper/http2" ];
+          "log" = [ "dep:log" ];
+          "logging" = [ "log" "tokio-rustls/logging" "rustls/logging" ];
+          "native-tokio" = [ "tokio-runtime" "rustls-native-certs" ];
+          "rustls-native-certs" = [ "dep:rustls-native-certs" ];
+          "tls12" = [ "tokio-rustls/tls12" "rustls/tls12" ];
+          "tokio-runtime" = [ "hyper/runtime" ];
+          "webpki-roots" = [ "dep:webpki-roots" ];
+          "webpki-tokio" = [ "tokio-runtime" "webpki-roots" ];
+        };
+        resolvedDefaultFeatures = [ "http1" "http2" "log" "logging" "native-tokio" "rustls-native-certs" "tls12" "tokio-runtime" ];
+      };
+      "iana-time-zone" = rec {
+        crateName = "iana-time-zone";
+        version = "0.1.58";
+        edition = "2018";
+        sha256 = "081vcr8z8ddhl5r1ywif6grnswk01b2ac4nks2bhn8zzdimvh9l3";
+        authors = [
+          "Andrew Straw <strawman@astraw.com>"
+          "René Kijewski <rene.kijewski@fu-berlin.de>"
+          "Ryan Lopopolo <rjl@hyperbo.la>"
+        ];
+        dependencies = [
+          {
+            name = "android_system_properties";
+            packageId = "android_system_properties";
+            target = { target, features }: ("android" == target."os" or null);
+          }
+          {
+            name = "core-foundation-sys";
+            packageId = "core-foundation-sys";
+            target = { target, features }: (("macos" == target."os" or null) || ("ios" == target."os" or null));
+          }
+          {
+            name = "iana-time-zone-haiku";
+            packageId = "iana-time-zone-haiku";
+            target = { target, features }: ("haiku" == target."os" or null);
+          }
+          {
+            name = "js-sys";
+            packageId = "js-sys";
+            target = { target, features }: ("wasm32" == target."arch" or null);
+          }
+          {
+            name = "wasm-bindgen";
+            packageId = "wasm-bindgen";
+            target = { target, features }: ("wasm32" == target."arch" or null);
+          }
+          {
+            name = "windows-core";
+            packageId = "windows-core";
+            target = { target, features }: ("windows" == target."os" or null);
+          }
+        ];
+        features = { };
+        resolvedDefaultFeatures = [ "fallback" ];
+      };
+      "iana-time-zone-haiku" = rec {
+        crateName = "iana-time-zone-haiku";
+        version = "0.1.2";
+        edition = "2018";
+        sha256 = "17r6jmj31chn7xs9698r122mapq85mfnv98bb4pg6spm0si2f67k";
+        authors = [
+          "René Kijewski <crates.io@k6i.de>"
+        ];
+        buildDependencies = [
+          {
+            name = "cc";
+            packageId = "cc";
+          }
+        ];
+
+      };
+      "indexmap" = rec {
+        crateName = "indexmap";
+        version = "1.9.3";
+        edition = "2021";
+        sha256 = "16dxmy7yvk51wvnih3a3im6fp5lmx0wx76i03n06wyak6cwhw1xx";
+        dependencies = [
+          {
+            name = "hashbrown";
+            packageId = "hashbrown";
+            usesDefaultFeatures = false;
+            features = [ "raw" ];
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "autocfg";
+            packageId = "autocfg";
+          }
+        ];
+        features = {
+          "arbitrary" = [ "dep:arbitrary" ];
+          "quickcheck" = [ "dep:quickcheck" ];
+          "rayon" = [ "dep:rayon" ];
+          "rustc-rayon" = [ "dep:rustc-rayon" ];
+          "serde" = [ "dep:serde" ];
+          "serde-1" = [ "serde" ];
+        };
+        resolvedDefaultFeatures = [ "std" ];
+      };
+      "itoa" = rec {
+        crateName = "itoa";
+        version = "1.0.9";
+        edition = "2018";
+        sha256 = "0f6cpb4yqzhkrhhg6kqsw3wnmmhdnnffi6r2xzy248gzi2v0l5dg";
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        features = {
+          "no-panic" = [ "dep:no-panic" ];
+        };
+      };
+      "jobserver" = rec {
+        crateName = "jobserver";
+        version = "0.1.27";
+        edition = "2018";
+        sha256 = "0z9w6vfqwbr6hfk9yaw7kydlh6f7k39xdlszxlh39in4acwzcdwc";
+        authors = [
+          "Alex Crichton <alex@alexcrichton.com>"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (target."unix" or false);
+          }
+        ];
+
+      };
+      "js-sys" = rec {
+        crateName = "js-sys";
+        version = "0.3.65";
+        edition = "2018";
+        sha256 = "1s1gaxgzpqfyygc7f2pwp9y128rh5f8zvsc4nm5yazgna9cw7h2l";
+        authors = [
+          "The wasm-bindgen Developers"
+        ];
+        dependencies = [
+          {
+            name = "wasm-bindgen";
+            packageId = "wasm-bindgen";
+          }
+        ];
+
+      };
+      "lambda_runtime" = rec {
+        crateName = "lambda_runtime";
+        version = "0.8.3";
+        edition = "2021";
+        sha256 = "00m7sw7bhjkr4q7ikid0kjjqirr23gcxfjdvvvyqp6nfsxjqzjny";
+        authors = [
+          "David Calavera <dcalaver@amazon.com>"
+          "Harold Sun <sunhua@amazon.com>"
+        ];
+        dependencies = [
+          {
+            name = "async-stream";
+            packageId = "async-stream";
+          }
+          {
+            name = "base64";
+            packageId = "base64 0.20.0";
+          }
+          {
+            name = "bytes";
+            packageId = "bytes";
+          }
+          {
+            name = "futures";
+            packageId = "futures";
+          }
+          {
+            name = "http";
+            packageId = "http";
+          }
+          {
+            name = "http-body";
+            packageId = "http-body";
+          }
+          {
+            name = "http-serde";
+            packageId = "http-serde";
+          }
+          {
+            name = "hyper";
+            packageId = "hyper";
+            features = [ "http1" "client" "stream" "server" ];
+          }
+          {
+            name = "lambda_runtime_api_client";
+            packageId = "lambda_runtime_api_client";
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+            features = [ "derive" ];
+          }
+          {
+            name = "serde_json";
+            packageId = "serde_json";
+          }
+          {
+            name = "serde_path_to_error";
+            packageId = "serde_path_to_error";
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "macros" "io-util" "sync" "rt-multi-thread" ];
+          }
+          {
+            name = "tokio-stream";
+            packageId = "tokio-stream";
+          }
+          {
+            name = "tower";
+            packageId = "tower";
+            features = [ "util" ];
+          }
+          {
+            name = "tracing";
+            packageId = "tracing";
+            features = [ "log" ];
+          }
+        ];
+        features = {
+          "default" = [ "simulated" ];
+        };
+        resolvedDefaultFeatures = [ "default" "simulated" ];
+      };
+      "lambda_runtime_api_client" = rec {
+        crateName = "lambda_runtime_api_client";
+        version = "0.8.0";
+        edition = "2021";
+        sha256 = "1vgh5cl1ddxskqzgbshxgydlw1a3qipmb2rlqg4wijis3zh5l339";
+        authors = [
+          "David Calavera <dcalaver@amazon.com>"
+          "Harold Sun <sunhua@amazon.com>"
+        ];
+        dependencies = [
+          {
+            name = "http";
+            packageId = "http";
+          }
+          {
+            name = "hyper";
+            packageId = "hyper";
+            features = [ "http1" "client" "stream" "tcp" ];
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "io-util" ];
+          }
+          {
+            name = "tower-service";
+            packageId = "tower-service";
+          }
+        ];
+
+      };
+      "lazy_static" = rec {
+        crateName = "lazy_static";
+        version = "1.4.0";
+        edition = "2015";
+        sha256 = "0in6ikhw8mgl33wjv6q6xfrb5b9jr16q8ygjy803fay4zcisvaz2";
+        authors = [
+          "Marvin Löbel <loebel.marvin@gmail.com>"
+        ];
+        features = {
+          "spin" = [ "dep:spin" ];
+          "spin_no_std" = [ "spin" ];
+        };
+      };
+      "libc" = rec {
+        crateName = "libc";
+        version = "0.2.150";
+        edition = "2015";
+        sha256 = "0g10n8c830alndgjb8xk1i9kz5z727np90z1z81119pr8d3jmnc9";
+        authors = [
+          "The Rust Project Developers"
+        ];
+        features = {
+          "default" = [ "std" ];
+          "rustc-dep-of-std" = [ "align" "rustc-std-workspace-core" ];
+          "rustc-std-workspace-core" = [ "dep:rustc-std-workspace-core" ];
+          "use_std" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "libredox" = rec {
+        crateName = "libredox";
+        version = "0.0.1";
+        edition = "2021";
+        sha256 = "1s2fh4ikpp9xl0lsl01pi0n8pw1q9s3ld452vd8qh1v63v537j45";
+        authors = [
+          "4lDO2 <4lDO2@protonmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "bitflags";
+            packageId = "bitflags 2.4.1";
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+          }
+          {
+            name = "redox_syscall";
+            packageId = "redox_syscall";
+          }
+        ];
+        features = {
+          "default" = [ "scheme" "call" ];
+          "scheme" = [ "call" ];
+        };
+        resolvedDefaultFeatures = [ "call" ];
+      };
+      "lock_api" = rec {
+        crateName = "lock_api";
+        version = "0.4.11";
+        edition = "2018";
+        sha256 = "0iggx0h4jx63xm35861106af3jkxq06fpqhpkhgw0axi2n38y5iw";
+        authors = [
+          "Amanieu d'Antras <amanieu@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "scopeguard";
+            packageId = "scopeguard";
+            usesDefaultFeatures = false;
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "autocfg";
+            packageId = "autocfg";
+          }
+        ];
+        features = {
+          "default" = [ "atomic_usize" ];
+          "owning_ref" = [ "dep:owning_ref" ];
+          "serde" = [ "dep:serde" ];
+        };
+        resolvedDefaultFeatures = [ "atomic_usize" "default" ];
+      };
+      "log" = rec {
+        crateName = "log";
+        version = "0.4.20";
+        edition = "2015";
+        sha256 = "13rf7wphnwd61vazpxr7fiycin6cb1g8fmvgqg18i464p0y1drmm";
+        authors = [
+          "The Rust Project Developers"
+        ];
+        features = {
+          "kv_unstable" = [ "value-bag" ];
+          "kv_unstable_serde" = [ "kv_unstable_std" "value-bag/serde" "serde" ];
+          "kv_unstable_std" = [ "std" "kv_unstable" "value-bag/error" ];
+          "kv_unstable_sval" = [ "kv_unstable" "value-bag/sval" "sval" "sval_ref" ];
+          "serde" = [ "dep:serde" ];
+          "sval" = [ "dep:sval" ];
+          "sval_ref" = [ "dep:sval_ref" ];
+          "value-bag" = [ "dep:value-bag" ];
+        };
+        resolvedDefaultFeatures = [ "std" ];
+      };
+      "mach2" = rec {
+        crateName = "mach2";
+        version = "0.4.1";
+        edition = "2015";
+        sha256 = "1s5dbscwk0w6czzvhxp9ix9c2djv4fpnj4za9byaclfiphq1h3bd";
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+            usesDefaultFeatures = false;
+            target = { target, features }: (("macos" == target."os" or null) || ("ios" == target."os" or null));
+          }
+        ];
+        features = { };
+        resolvedDefaultFeatures = [ "default" ];
+      };
+      "magic-buffer" = rec {
+        crateName = "magic-buffer";
+        version = "0.1.1";
+        edition = "2021";
+        sha256 = "0bpzcrwq89cc5q8mgkmsyx39vjsqaxvaxs29qp8k04rndc7ysfh0";
+        authors = [
+          "Sebastian Klose <mail@sklose.com>"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: ("linux" == target."os" or null);
+          }
+          {
+            name = "mach2";
+            packageId = "mach2";
+            target = { target, features }: (("macos" == target."os" or null) || ("ios" == target."os" or null));
+          }
+          {
+            name = "thiserror";
+            packageId = "thiserror";
+          }
+          {
+            name = "windows-sys";
+            packageId = "windows-sys";
+            target = { target, features }: (target."windows" or false);
+            features = [ "Win32_Foundation" "Win32_System_SystemInformation" "Win32_System_Diagnostics_Debug" "Win32_System_Memory" "Win32_Security" ];
+          }
+        ];
+
+      };
+      "md-5" = rec {
+        crateName = "md-5";
+        version = "0.9.1";
+        edition = "2018";
+        sha256 = "059ajjacz1q3cms7vl6cvhdqs4qdw2nnwj9dq99ryzv0p6djfnkv";
+        libName = "md5";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        dependencies = [
+          {
+            name = "block-buffer";
+            packageId = "block-buffer";
+          }
+          {
+            name = "digest";
+            packageId = "digest";
+          }
+          {
+            name = "opaque-debug";
+            packageId = "opaque-debug";
+          }
+        ];
+        devDependencies = [
+          {
+            name = "digest";
+            packageId = "digest";
+            features = [ "dev" ];
+          }
+        ];
+        features = {
+          "asm" = [ "md5-asm" ];
+          "default" = [ "std" ];
+          "md5-asm" = [ "dep:md5-asm" ];
+          "std" = [ "digest/std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "memchr" = rec {
+        crateName = "memchr";
+        version = "2.6.4";
+        edition = "2021";
+        sha256 = "0rq1ka8790ns41j147npvxcqcl2anxyngsdimy85ag2api0fwrgn";
+        authors = [
+          "Andrew Gallant <jamslam@gmail.com>"
+          "bluss"
+        ];
+        features = {
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "core" = [ "dep:core" ];
+          "default" = [ "std" ];
+          "logging" = [ "dep:log" ];
+          "rustc-dep-of-std" = [ "core" "compiler_builtins" ];
+          "std" = [ "alloc" ];
+          "use_std" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "std" ];
+      };
+      "miniz_oxide" = rec {
+        crateName = "miniz_oxide";
+        version = "0.7.1";
+        edition = "2018";
+        sha256 = "1ivl3rbbdm53bzscrd01g60l46lz5krl270487d8lhjvwl5hx0g7";
+        authors = [
+          "Frommi <daniil.liferenko@gmail.com>"
+          "oyvindln <oyvindln@users.noreply.github.com>"
+        ];
+        dependencies = [
+          {
+            name = "adler";
+            packageId = "adler";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "alloc" = [ "dep:alloc" ];
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "core" = [ "dep:core" ];
+          "default" = [ "with-alloc" ];
+          "rustc-dep-of-std" = [ "core" "alloc" "compiler_builtins" "adler/rustc-dep-of-std" ];
+          "simd" = [ "simd-adler32" ];
+          "simd-adler32" = [ "dep:simd-adler32" ];
+        };
+      };
+      "mio" = rec {
+        crateName = "mio";
+        version = "0.8.9";
+        edition = "2018";
+        sha256 = "1l23hg513c23nhcdzvk25caaj28mic6qgqadbn8axgj6bqf2ikix";
+        authors = [
+          "Carl Lerche <me@carllerche.com>"
+          "Thomas de Zeeuw <thomasdezeeuw@gmail.com>"
+          "Tokio Contributors <team@tokio.rs>"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: ("wasi" == target."os" or null);
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (target."unix" or false);
+          }
+          {
+            name = "wasi";
+            packageId = "wasi";
+            target = { target, features }: ("wasi" == target."os" or null);
+          }
+          {
+            name = "windows-sys";
+            packageId = "windows-sys";
+            target = { target, features }: (target."windows" or false);
+            features = [ "Win32_Foundation" "Win32_Networking_WinSock" "Win32_Storage_FileSystem" "Win32_System_IO" "Win32_System_WindowsProgramming" ];
+          }
+        ];
+        features = {
+          "default" = [ "log" ];
+          "log" = [ "dep:log" ];
+          "os-ext" = [ "os-poll" "windows-sys/Win32_System_Pipes" "windows-sys/Win32_Security" ];
+        };
+        resolvedDefaultFeatures = [ "net" "os-ext" "os-poll" ];
+      };
+      "nu-ansi-term" = rec {
+        crateName = "nu-ansi-term";
+        version = "0.46.0";
+        edition = "2018";
+        sha256 = "115sywxh53p190lyw97alm14nc004qj5jm5lvdj608z84rbida3p";
+        authors = [
+          "ogham@bsago.me"
+          "Ryan Scheel (Havvy) <ryan.havvy@gmail.com>"
+          "Josh Triplett <josh@joshtriplett.org>"
+          "The Nushell Project Developers"
+        ];
+        dependencies = [
+          {
+            name = "overload";
+            packageId = "overload";
+          }
+          {
+            name = "winapi";
+            packageId = "winapi";
+            target = { target, features }: ("windows" == target."os" or null);
+            features = [ "consoleapi" "errhandlingapi" "fileapi" "handleapi" "processenv" ];
+          }
+        ];
+        features = {
+          "derive_serde_style" = [ "serde" ];
+          "serde" = [ "dep:serde" ];
+        };
+      };
+      "num-traits" = rec {
+        crateName = "num-traits";
+        version = "0.2.17";
+        edition = "2018";
+        sha256 = "0z16bi5zwgfysz6765v3rd6whfbjpihx3mhsn4dg8dzj2c221qrr";
+        authors = [
+          "The Rust Project Developers"
+        ];
+        buildDependencies = [
+          {
+            name = "autocfg";
+            packageId = "autocfg";
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "libm" = [ "dep:libm" ];
+        };
+      };
+      "num_cpus" = rec {
+        crateName = "num_cpus";
+        version = "1.16.0";
+        edition = "2015";
+        sha256 = "0hra6ihpnh06dvfvz9ipscys0xfqa9ca9hzp384d5m02ssvgqqa1";
+        authors = [
+          "Sean McArthur <sean@seanmonstar.com>"
+        ];
+        dependencies = [
+          {
+            name = "hermit-abi";
+            packageId = "hermit-abi";
+            target = { target, features }: ("hermit" == target."os" or null);
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (!(target."windows" or false));
+          }
+        ];
+
+      };
+      "object" = rec {
+        crateName = "object";
+        version = "0.32.1";
+        edition = "2018";
+        sha256 = "1c02x4kvqpnl3wn7gz9idm4jrbirbycyqjgiw6lm1g9k77fzkxcw";
+        dependencies = [
+          {
+            name = "memchr";
+            packageId = "memchr";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "all" = [ "read" "write" "std" "compression" "wasm" ];
+          "alloc" = [ "dep:alloc" ];
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "compression" = [ "dep:flate2" "dep:ruzstd" "std" ];
+          "core" = [ "dep:core" ];
+          "default" = [ "read" "compression" ];
+          "doc" = [ "read_core" "write_std" "std" "compression" "archive" "coff" "elf" "macho" "pe" "wasm" "xcoff" ];
+          "pe" = [ "coff" ];
+          "read" = [ "read_core" "archive" "coff" "elf" "macho" "pe" "xcoff" "unaligned" ];
+          "rustc-dep-of-std" = [ "core" "compiler_builtins" "alloc" "memchr/rustc-dep-of-std" ];
+          "std" = [ "memchr/std" ];
+          "unstable-all" = [ "all" "unstable" ];
+          "wasm" = [ "dep:wasmparser" ];
+          "write" = [ "write_std" "coff" "elf" "macho" "pe" "xcoff" ];
+          "write_core" = [ "dep:crc32fast" "dep:indexmap" "dep:hashbrown" ];
+          "write_std" = [ "write_core" "std" "indexmap?/std" "crc32fast?/std" ];
+        };
+        resolvedDefaultFeatures = [ "archive" "coff" "elf" "macho" "pe" "read_core" "unaligned" ];
+      };
+      "once_cell" = rec {
+        crateName = "once_cell";
+        version = "1.18.0";
+        edition = "2021";
+        sha256 = "0vapcd5ambwck95wyz3ymlim35jirgnqn9a0qmi19msymv95v2yx";
+        authors = [
+          "Aleksey Kladov <aleksey.kladov@gmail.com>"
+        ];
+        features = {
+          "alloc" = [ "race" ];
+          "atomic-polyfill" = [ "critical-section" ];
+          "critical-section" = [ "dep:critical-section" "dep:atomic-polyfill" ];
+          "default" = [ "std" ];
+          "parking_lot" = [ "dep:parking_lot_core" ];
+          "std" = [ "alloc" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "race" "std" ];
+      };
+      "opaque-debug" = rec {
+        crateName = "opaque-debug";
+        version = "0.3.0";
+        edition = "2018";
+        sha256 = "1m8kzi4nd6shdqimn0mgb24f0hxslhnqd1whakyq06wcqd086jk2";
+        authors = [
+          "RustCrypto Developers"
+        ];
+
+      };
+      "openssl-probe" = rec {
+        crateName = "openssl-probe";
+        version = "0.1.5";
+        edition = "2015";
+        sha256 = "1kq18qm48rvkwgcggfkqq6pm948190czqc94d6bm2sir5hq1l0gz";
+        authors = [
+          "Alex Crichton <alex@alexcrichton.com>"
+        ];
+
+      };
+      "overload" = rec {
+        crateName = "overload";
+        version = "0.1.1";
+        edition = "2018";
+        sha256 = "0fdgbaqwknillagy1xq7xfgv60qdbk010diwl7s1p0qx7hb16n5i";
+        authors = [
+          "Daniel Salvadori <danaugrs@gmail.com>"
+        ];
+
+      };
+      "parking_lot" = rec {
+        crateName = "parking_lot";
+        version = "0.12.1";
+        edition = "2018";
+        sha256 = "13r2xk7mnxfc5g0g6dkdxqdqad99j7s7z8zhzz4npw5r0g0v4hip";
+        authors = [
+          "Amanieu d'Antras <amanieu@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "lock_api";
+            packageId = "lock_api";
+          }
+          {
+            name = "parking_lot_core";
+            packageId = "parking_lot_core";
+          }
+        ];
+        features = {
+          "arc_lock" = [ "lock_api/arc_lock" ];
+          "deadlock_detection" = [ "parking_lot_core/deadlock_detection" ];
+          "nightly" = [ "parking_lot_core/nightly" "lock_api/nightly" ];
+          "owning_ref" = [ "lock_api/owning_ref" ];
+          "serde" = [ "lock_api/serde" ];
+        };
+        resolvedDefaultFeatures = [ "default" ];
+      };
+      "parking_lot_core" = rec {
+        crateName = "parking_lot_core";
+        version = "0.9.9";
+        edition = "2018";
+        sha256 = "13h0imw1aq86wj28gxkblhkzx6z1gk8q18n0v76qmmj6cliajhjc";
+        authors = [
+          "Amanieu d'Antras <amanieu@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (target."unix" or false);
+          }
+          {
+            name = "redox_syscall";
+            packageId = "redox_syscall";
+            target = { target, features }: ("redox" == target."os" or null);
+          }
+          {
+            name = "smallvec";
+            packageId = "smallvec";
+          }
+          {
+            name = "windows-targets";
+            packageId = "windows-targets";
+            target = { target, features }: (target."windows" or false);
+          }
+        ];
+        features = {
+          "backtrace" = [ "dep:backtrace" ];
+          "deadlock_detection" = [ "petgraph" "thread-id" "backtrace" ];
+          "petgraph" = [ "dep:petgraph" ];
+          "thread-id" = [ "dep:thread-id" ];
+        };
+      };
+      "percent-encoding" = rec {
+        crateName = "percent-encoding";
+        version = "2.3.0";
+        edition = "2018";
+        sha256 = "152slflmparkh27hprw62sph8rv77wckzhwl2dhqk6bf563lfalv";
+        authors = [
+          "The rust-url developers"
+        ];
+        features = {
+          "default" = [ "std" ];
+          "std" = [ "alloc" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "std" ];
+      };
+      "pin-project" = rec {
+        crateName = "pin-project";
+        version = "1.1.3";
+        edition = "2021";
+        sha256 = "08k4cpy8q3j93qqgnrbzkcgpn7g0a88l4a9nm33kyghpdhffv97x";
+        dependencies = [
+          {
+            name = "pin-project-internal";
+            packageId = "pin-project-internal";
+          }
+        ];
+
+      };
+      "pin-project-internal" = rec {
+        crateName = "pin-project-internal";
+        version = "1.1.3";
+        edition = "2021";
+        sha256 = "01a4l3vb84brv9v7wl71chzxra2kynm6yvcjca66xv3ij6fgsna3";
+        procMacro = true;
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn";
+            features = [ "full" "visit-mut" ];
+          }
+        ];
+
+      };
+      "pin-project-lite" = rec {
+        crateName = "pin-project-lite";
+        version = "0.2.13";
+        edition = "2018";
+        sha256 = "0n0bwr5qxlf0mhn2xkl36sy55118s9qmvx2yl5f3ixkb007lbywa";
+
+      };
+      "pin-utils" = rec {
+        crateName = "pin-utils";
+        version = "0.1.0";
+        edition = "2018";
+        sha256 = "117ir7vslsl2z1a7qzhws4pd01cg2d3338c47swjyvqv2n60v1wb";
+        authors = [
+          "Josef Brandl <mail@josefbrandl.de>"
+        ];
+
+      };
+      "proc-macro2" = rec {
+        crateName = "proc-macro2";
+        version = "1.0.69";
+        edition = "2021";
+        sha256 = "1nljgyllbm3yr3pa081bf83gxh6l4zvjqzaldw7v4mj9xfgihk0k";
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+          "Alex Crichton <alex@alexcrichton.com>"
+        ];
+        dependencies = [
+          {
+            name = "unicode-ident";
+            packageId = "unicode-ident";
+          }
+        ];
+        features = {
+          "default" = [ "proc-macro" ];
+        };
+        resolvedDefaultFeatures = [ "default" "proc-macro" ];
+      };
+      "quote" = rec {
+        crateName = "quote";
+        version = "1.0.33";
+        edition = "2018";
+        sha256 = "1biw54hbbr12wdwjac55z1m2x2rylciw83qnjn564a3096jgqrsj";
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "default" = [ "proc-macro" ];
+          "proc-macro" = [ "proc-macro2/proc-macro" ];
+        };
+        resolvedDefaultFeatures = [ "default" "proc-macro" ];
+      };
+      "redox_syscall" = rec {
+        crateName = "redox_syscall";
+        version = "0.4.1";
+        edition = "2018";
+        sha256 = "1aiifyz5dnybfvkk4cdab9p2kmphag1yad6iknc7aszlxxldf8j7";
+        libName = "syscall";
+        authors = [
+          "Jeremy Soller <jackpot51@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "bitflags";
+            packageId = "bitflags 1.3.2";
+          }
+        ];
+        features = {
+          "core" = [ "dep:core" ];
+          "rustc-dep-of-std" = [ "core" "bitflags/rustc-dep-of-std" ];
+        };
+      };
+      "redox_users" = rec {
+        crateName = "redox_users";
+        version = "0.4.4";
+        edition = "2021";
+        sha256 = "1d1c7dhbb62sh8jrq9dhvqcyxqsh3wg8qknsi94iwq3r0wh7k151";
+        authors = [
+          "Jose Narvaez <goyox86@gmail.com>"
+          "Wesley Hershberger <mggmugginsmc@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "getrandom";
+            packageId = "getrandom";
+            features = [ "std" ];
+          }
+          {
+            name = "libredox";
+            packageId = "libredox";
+            usesDefaultFeatures = false;
+            features = [ "call" ];
+          }
+          {
+            name = "thiserror";
+            packageId = "thiserror";
+          }
+        ];
+        features = {
+          "auth" = [ "rust-argon2" "zeroize" ];
+          "default" = [ "auth" ];
+          "rust-argon2" = [ "dep:rust-argon2" ];
+          "zeroize" = [ "dep:zeroize" ];
+        };
+      };
+      "ring 0.16.20" = rec {
+        crateName = "ring";
+        version = "0.16.20";
+        edition = "2018";
+        links = "ring-asm";
+        sha256 = "1z682xp7v38ayq9g9nkbhhfpj6ygralmlx7wdmsfv8rnw99cylrh";
+        authors = [
+          "Brian Smith <brian@briansmith.org>"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+            usesDefaultFeatures = false;
+            target = { target, features }: (("android" == target."os" or null) || ("linux" == target."os" or null));
+          }
+          {
+            name = "once_cell";
+            packageId = "once_cell";
+            optional = true;
+            usesDefaultFeatures = false;
+            target = { target, features }: (("android" == target."os" or null) || ("linux" == target."os" or null));
+            features = [ "std" ];
+          }
+          {
+            name = "once_cell";
+            packageId = "once_cell";
+            usesDefaultFeatures = false;
+            target = { target, features }: (("dragonfly" == target."os" or null) || ("freebsd" == target."os" or null) || ("illumos" == target."os" or null) || ("netbsd" == target."os" or null) || ("openbsd" == target."os" or null) || ("solaris" == target."os" or null));
+            features = [ "std" ];
+          }
+          {
+            name = "spin";
+            packageId = "spin 0.5.2";
+            usesDefaultFeatures = false;
+            target = { target, features }: (("x86" == target."arch" or null) || ("x86_64" == target."arch" or null) || ((("aarch64" == target."arch" or null) || ("arm" == target."arch" or null)) && (("android" == target."os" or null) || ("fuchsia" == target."os" or null) || ("linux" == target."os" or null))));
+          }
+          {
+            name = "untrusted";
+            packageId = "untrusted 0.7.1";
+          }
+          {
+            name = "web-sys";
+            packageId = "web-sys";
+            usesDefaultFeatures = false;
+            target = { target, features }: (("wasm32" == target."arch" or null) && ("unknown" == target."vendor" or null) && ("unknown" == target."os" or null) && ("" == target."env" or null));
+            features = [ "Crypto" "Window" ];
+          }
+          {
+            name = "winapi";
+            packageId = "winapi";
+            usesDefaultFeatures = false;
+            target = { target, features }: ("windows" == target."os" or null);
+            features = [ "ntsecapi" "wtypesbase" ];
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "cc";
+            packageId = "cc";
+            usesDefaultFeatures = false;
+          }
+        ];
+        devDependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+            usesDefaultFeatures = false;
+            target = { target, features }: ((target."unix" or false) || (target."windows" or false));
+          }
+        ];
+        features = {
+          "default" = [ "alloc" "dev_urandom_fallback" ];
+          "dev_urandom_fallback" = [ "once_cell" ];
+          "once_cell" = [ "dep:once_cell" ];
+          "std" = [ "alloc" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "dev_urandom_fallback" "once_cell" ];
+      };
+      "ring 0.17.5" = rec {
+        crateName = "ring";
+        version = "0.17.5";
+        edition = "2021";
+        links = "ring_core_0_17_5";
+        sha256 = "02sd768l7594rm3jw048z7kkml7zcyw4ir62p6cxirap8wq0a0pv";
+        authors = [
+          "Brian Smith <brian@briansmith.org>"
+        ];
+        dependencies = [
+          {
+            name = "getrandom";
+            packageId = "getrandom";
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            usesDefaultFeatures = false;
+            target = { target, features }: (("android" == target."os" or null) || ("linux" == target."os" or null));
+          }
+          {
+            name = "spin";
+            packageId = "spin 0.9.8";
+            usesDefaultFeatures = false;
+            target = { target, features }: (("x86" == target."arch" or null) || ("x86_64" == target."arch" or null) || ((("aarch64" == target."arch" or null) || ("arm" == target."arch" or null)) && (("android" == target."os" or null) || ("fuchsia" == target."os" or null) || ("linux" == target."os" or null) || ("windows" == target."os" or null))));
+            features = [ "once" ];
+          }
+          {
+            name = "untrusted";
+            packageId = "untrusted 0.9.0";
+          }
+          {
+            name = "windows-sys";
+            packageId = "windows-sys";
+            target = { target, features }: (("aarch64" == target."arch" or null) && ("windows" == target."os" or null));
+            features = [ "Win32_Foundation" "Win32_System_Threading" ];
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "cc";
+            packageId = "cc";
+            usesDefaultFeatures = false;
+          }
+        ];
+        devDependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+            usesDefaultFeatures = false;
+            target = { target, features }: ((target."unix" or false) || (target."windows" or false) || ("wasi" == target."os" or null));
+          }
+        ];
+        features = {
+          "default" = [ "alloc" "dev_urandom_fallback" ];
+          "std" = [ "alloc" ];
+          "wasm32_unknown_unknown_js" = [ "getrandom/js" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "dev_urandom_fallback" ];
+      };
+      "rusoto_core" = rec {
+        crateName = "rusoto_core";
+        version = "0.48.0";
+        edition = "2018";
+        sha256 = "18ig9x4n68cgfvhzkyhl9w2qlhk945xczbb9c8r52dd79ss0vcqx";
+        authors = [
+          "Anthony DiMarco <ocramida@gmail.com>"
+          "Jimmy Cuadra <jimmy@jimmycuadra.com>"
+          "Matthew Mayer <matthewkmayer@gmail.com>"
+          "Nikita Pekin <contact@nikitapek.in>"
+        ];
+        dependencies = [
+          {
+            name = "async-trait";
+            packageId = "async-trait";
+          }
+          {
+            name = "base64";
+            packageId = "base64 0.13.1";
+          }
+          {
+            name = "bytes";
+            packageId = "bytes";
+          }
+          {
+            name = "crc32fast";
+            packageId = "crc32fast";
+          }
+          {
+            name = "futures";
+            packageId = "futures";
+          }
+          {
+            name = "http";
+            packageId = "http";
+          }
+          {
+            name = "hyper";
+            packageId = "hyper";
+            features = [ "client" "http1" "http2" "tcp" ];
+          }
+          {
+            name = "hyper-rustls";
+            packageId = "hyper-rustls";
+            optional = true;
+            usesDefaultFeatures = false;
+            features = [ "http1" "http2" "tls12" "logging" ];
+          }
+          {
+            name = "lazy_static";
+            packageId = "lazy_static";
+          }
+          {
+            name = "log";
+            packageId = "log";
+          }
+          {
+            name = "rusoto_credential";
+            packageId = "rusoto_credential";
+          }
+          {
+            name = "rusoto_signature";
+            packageId = "rusoto_signature";
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+            features = [ "derive" ];
+          }
+          {
+            name = "serde_json";
+            packageId = "serde_json";
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "time" "io-util" ];
+          }
+          {
+            name = "xml-rs";
+            packageId = "xml-rs";
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "rustc_version";
+            packageId = "rustc_version";
+          }
+        ];
+        devDependencies = [
+          {
+            name = "serde_json";
+            packageId = "serde_json";
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "macros" ];
+          }
+        ];
+        features = {
+          "default" = [ "native-tls" ];
+          "encoding" = [ "flate2" ];
+          "flate2" = [ "dep:flate2" ];
+          "hyper-rustls" = [ "dep:hyper-rustls" ];
+          "hyper-tls" = [ "dep:hyper-tls" ];
+          "native-tls" = [ "hyper-tls" ];
+          "nightly-testing" = [ "rusoto_credential/nightly-testing" ];
+          "rustls" = [ "hyper-rustls/native-tokio" ];
+          "rustls-webpki" = [ "hyper-rustls/webpki-tokio" ];
+        };
+        resolvedDefaultFeatures = [ "hyper-rustls" "rustls" ];
+      };
+      "rusoto_credential" = rec {
+        crateName = "rusoto_credential";
+        version = "0.48.0";
+        edition = "2018";
+        sha256 = "019dq3aq6hnfg4xvxdfsnrba08dwvciz0km4nr3n1basvc9nq2pf";
+        authors = [
+          "Anthony DiMarco <ocramida@gmail.com>"
+          "Jimmy Cuadra <jimmy@jimmycuadra.com>"
+          "Matthew Mayer <matthewkmayer@gmail.com>"
+          "Nikita Pekin <contact@nikitapek.in>"
+        ];
+        dependencies = [
+          {
+            name = "async-trait";
+            packageId = "async-trait";
+          }
+          {
+            name = "chrono";
+            packageId = "chrono";
+            usesDefaultFeatures = false;
+            features = [ "clock" "serde" ];
+          }
+          {
+            name = "dirs-next";
+            packageId = "dirs-next";
+          }
+          {
+            name = "futures";
+            packageId = "futures";
+          }
+          {
+            name = "hyper";
+            packageId = "hyper";
+            features = [ "client" "http1" "tcp" "stream" ];
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+            features = [ "derive" ];
+          }
+          {
+            name = "serde_json";
+            packageId = "serde_json";
+          }
+          {
+            name = "shlex";
+            packageId = "shlex";
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "process" "sync" "time" ];
+          }
+          {
+            name = "zeroize";
+            packageId = "zeroize";
+          }
+        ];
+        devDependencies = [
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "macros" "rt-multi-thread" ];
+          }
+        ];
+        features = { };
+      };
+      "rusoto_s3" = rec {
+        crateName = "rusoto_s3";
+        version = "0.48.0";
+        edition = "2018";
+        sha256 = "0kdiqljcq1wg26mp0vnn2wwjj0slxya63mhjnjqgc49l31vldbks";
+        authors = [
+          "Anthony DiMarco <ocramida@gmail.com>"
+          "Jimmy Cuadra <jimmy@jimmycuadra.com>"
+          "Matthew Mayer <matthewkmayer@gmail.com>"
+          "Nikita Pekin <contact@nikitapek.in>"
+        ];
+        dependencies = [
+          {
+            name = "async-trait";
+            packageId = "async-trait";
+          }
+          {
+            name = "bytes";
+            packageId = "bytes";
+          }
+          {
+            name = "futures";
+            packageId = "futures";
+          }
+          {
+            name = "rusoto_core";
+            packageId = "rusoto_core";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "xml-rs";
+            packageId = "xml-rs";
+          }
+        ];
+        features = {
+          "default" = [ "native-tls" ];
+          "deserialize_structs" = [ "bytes/serde" "serde" "serde_derive" ];
+          "native-tls" = [ "rusoto_core/native-tls" ];
+          "rustls" = [ "rusoto_core/rustls" ];
+          "serde" = [ "dep:serde" ];
+          "serde_derive" = [ "dep:serde_derive" ];
+          "serialize_structs" = [ "bytes/serde" "serde" "serde_derive" ];
+        };
+        resolvedDefaultFeatures = [ "rustls" ];
+      };
+      "rusoto_signature" = rec {
+        crateName = "rusoto_signature";
+        version = "0.48.0";
+        edition = "2018";
+        sha256 = "0wjjn3n3a01xxc1kdwqkrbw6zkgc4w8ia6r93s9lfj4b3i4rbbm5";
+        authors = [
+          "Anthony DiMarco <ocramida@gmail.com>"
+          "Jimmy Cuadra <jimmy@jimmycuadra.com>"
+          "Matthew Mayer <matthewkmayer@gmail.com>"
+          "Nikita Pekin <contact@nikitapek.in>"
+        ];
+        dependencies = [
+          {
+            name = "base64";
+            packageId = "base64 0.13.1";
+          }
+          {
+            name = "bytes";
+            packageId = "bytes";
+          }
+          {
+            name = "chrono";
+            packageId = "chrono";
+            usesDefaultFeatures = false;
+            features = [ "clock" ];
+          }
+          {
+            name = "digest";
+            packageId = "digest";
+          }
+          {
+            name = "futures";
+            packageId = "futures";
+          }
+          {
+            name = "hex";
+            packageId = "hex";
+          }
+          {
+            name = "hmac";
+            packageId = "hmac";
+          }
+          {
+            name = "http";
+            packageId = "http";
+          }
+          {
+            name = "hyper";
+            packageId = "hyper";
+            features = [ "stream" ];
+          }
+          {
+            name = "log";
+            packageId = "log";
+          }
+          {
+            name = "md-5";
+            packageId = "md-5";
+          }
+          {
+            name = "percent-encoding";
+            packageId = "percent-encoding";
+          }
+          {
+            name = "pin-project-lite";
+            packageId = "pin-project-lite";
+          }
+          {
+            name = "rusoto_credential";
+            packageId = "rusoto_credential";
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+          }
+          {
+            name = "sha2";
+            packageId = "sha2";
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "macros" "rt-multi-thread" ];
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "rustc_version";
+            packageId = "rustc_version";
+          }
+        ];
+        devDependencies = [
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "io-util" ];
+          }
+        ];
+
+      };
+      "rustc-demangle" = rec {
+        crateName = "rustc-demangle";
+        version = "0.1.23";
+        edition = "2015";
+        sha256 = "0xnbk2bmyzshacjm2g1kd4zzv2y2az14bw3sjccq5qkpmsfvn9nn";
+        authors = [
+          "Alex Crichton <alex@alexcrichton.com>"
+        ];
+        features = {
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "core" = [ "dep:core" ];
+          "rustc-dep-of-std" = [ "core" "compiler_builtins" ];
+        };
+      };
+      "rustc_version" = rec {
+        crateName = "rustc_version";
+        version = "0.4.0";
+        edition = "2018";
+        sha256 = "0rpk9rcdk405xhbmgclsh4pai0svn49x35aggl4nhbkd4a2zb85z";
+        authors = [
+          "Dirkjan Ochtman <dirkjan@ochtman.nl>"
+          "Marvin Löbel <loebel.marvin@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "semver";
+            packageId = "semver";
+          }
+        ];
+
+      };
+      "rustls" = rec {
+        crateName = "rustls";
+        version = "0.20.9";
+        edition = "2018";
+        sha256 = "16byazb8jfr06kgbijy92bdk0ila806g6a00a6l9x64mqpgf700v";
+        dependencies = [
+          {
+            name = "log";
+            packageId = "log";
+            optional = true;
+          }
+          {
+            name = "ring";
+            packageId = "ring 0.16.20";
+          }
+          {
+            name = "sct";
+            packageId = "sct";
+          }
+          {
+            name = "webpki";
+            packageId = "webpki";
+            features = [ "alloc" "std" ];
+          }
+        ];
+        devDependencies = [
+          {
+            name = "log";
+            packageId = "log";
+          }
+        ];
+        features = {
+          "default" = [ "logging" "tls12" ];
+          "log" = [ "dep:log" ];
+          "logging" = [ "log" ];
+          "read_buf" = [ "rustversion" ];
+          "rustversion" = [ "dep:rustversion" ];
+        };
+        resolvedDefaultFeatures = [ "log" "logging" "tls12" ];
+      };
+      "rustls-native-certs" = rec {
+        crateName = "rustls-native-certs";
+        version = "0.6.3";
+        edition = "2021";
+        sha256 = "007zind70rd5rfsrkdcfm8vn09j8sg02phg9334kark6rdscxam9";
+        dependencies = [
+          {
+            name = "openssl-probe";
+            packageId = "openssl-probe";
+            target = { target, features }: ((target."unix" or false) && (!("macos" == target."os" or null)));
+          }
+          {
+            name = "rustls-pemfile";
+            packageId = "rustls-pemfile";
+          }
+          {
+            name = "schannel";
+            packageId = "schannel";
+            target = { target, features }: (target."windows" or false);
+          }
+          {
+            name = "security-framework";
+            packageId = "security-framework";
+            target = { target, features }: ("macos" == target."os" or null);
+          }
+        ];
+
+      };
+      "rustls-pemfile" = rec {
+        crateName = "rustls-pemfile";
+        version = "1.0.4";
+        edition = "2018";
+        sha256 = "1324n5bcns0rnw6vywr5agff3rwfvzphi7rmbyzwnv6glkhclx0w";
+        dependencies = [
+          {
+            name = "base64";
+            packageId = "base64 0.21.5";
+          }
+        ];
+
+      };
+      "ryu" = rec {
+        crateName = "ryu";
+        version = "1.0.15";
+        edition = "2018";
+        sha256 = "0hfphpn1xnpzxwj8qg916ga1lyc33lc03lnf1gb3wwpglj6wrm0s";
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        features = {
+          "no-panic" = [ "dep:no-panic" ];
+        };
+      };
+      "schannel" = rec {
+        crateName = "schannel";
+        version = "0.1.22";
+        edition = "2018";
+        sha256 = "126zy5jb95fc5hvzyjwiq6lc81r08rdcn6affn00ispp9jzk6dqc";
+        authors = [
+          "Steven Fackler <sfackler@gmail.com>"
+          "Steffen Butzer <steffen.butzer@outlook.com>"
+        ];
+        dependencies = [
+          {
+            name = "windows-sys";
+            packageId = "windows-sys";
+            features = [ "Win32_Foundation" "Win32_Security_Cryptography" "Win32_Security_Authentication_Identity" "Win32_Security_Credentials" "Win32_System_Memory" ];
+          }
+        ];
+        devDependencies = [
+          {
+            name = "windows-sys";
+            packageId = "windows-sys";
+            features = [ "Win32_System_SystemInformation" "Win32_System_Time" ];
+          }
+        ];
+
+      };
+      "scopeguard" = rec {
+        crateName = "scopeguard";
+        version = "1.2.0";
+        edition = "2015";
+        sha256 = "0jcz9sd47zlsgcnm1hdw0664krxwb5gczlif4qngj2aif8vky54l";
+        authors = [
+          "bluss"
+        ];
+        features = {
+          "default" = [ "use_std" ];
+        };
+      };
+      "sct" = rec {
+        crateName = "sct";
+        version = "0.7.1";
+        edition = "2021";
+        sha256 = "056lmi2xkzdg1dbai6ha3n57s18cbip4pnmpdhyljli3m99n216s";
+        authors = [
+          "Joseph Birr-Pixton <jpixton@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "ring";
+            packageId = "ring 0.17.5";
+          }
+          {
+            name = "untrusted";
+            packageId = "untrusted 0.9.0";
+          }
+        ];
+
+      };
+      "security-framework" = rec {
+        crateName = "security-framework";
+        version = "2.9.2";
+        edition = "2021";
+        sha256 = "1pplxk15s5yxvi2m1sz5xfmjibp96cscdcl432w9jzbk0frlzdh5";
+        authors = [
+          "Steven Fackler <sfackler@gmail.com>"
+          "Kornel <kornel@geekhood.net>"
+        ];
+        dependencies = [
+          {
+            name = "bitflags";
+            packageId = "bitflags 1.3.2";
+          }
+          {
+            name = "core-foundation";
+            packageId = "core-foundation";
+          }
+          {
+            name = "core-foundation-sys";
+            packageId = "core-foundation-sys";
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+          }
+          {
+            name = "security-framework-sys";
+            packageId = "security-framework-sys";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "OSX_10_10" = [ "OSX_10_9" "security-framework-sys/OSX_10_10" ];
+          "OSX_10_11" = [ "OSX_10_10" "security-framework-sys/OSX_10_11" ];
+          "OSX_10_12" = [ "OSX_10_11" "security-framework-sys/OSX_10_12" ];
+          "OSX_10_13" = [ "OSX_10_12" "security-framework-sys/OSX_10_13" "alpn" "session-tickets" "serial-number-bigint" ];
+          "OSX_10_14" = [ "OSX_10_13" "security-framework-sys/OSX_10_14" ];
+          "OSX_10_15" = [ "OSX_10_14" "security-framework-sys/OSX_10_15" ];
+          "OSX_10_9" = [ "security-framework-sys/OSX_10_9" ];
+          "default" = [ "OSX_10_9" ];
+          "log" = [ "dep:log" ];
+          "serial-number-bigint" = [ "dep:num-bigint" ];
+        };
+        resolvedDefaultFeatures = [ "OSX_10_9" "default" ];
+      };
+      "security-framework-sys" = rec {
+        crateName = "security-framework-sys";
+        version = "2.9.1";
+        edition = "2021";
+        sha256 = "0yhciwlsy9dh0ps1gw3197kvyqx1bvc4knrhiznhid6kax196cp9";
+        authors = [
+          "Steven Fackler <sfackler@gmail.com>"
+          "Kornel <kornel@geekhood.net>"
+        ];
+        dependencies = [
+          {
+            name = "core-foundation-sys";
+            packageId = "core-foundation-sys";
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+          }
+        ];
+        features = {
+          "OSX_10_10" = [ "OSX_10_9" ];
+          "OSX_10_11" = [ "OSX_10_10" ];
+          "OSX_10_12" = [ "OSX_10_11" ];
+          "OSX_10_13" = [ "OSX_10_12" ];
+          "OSX_10_14" = [ "OSX_10_13" ];
+          "OSX_10_15" = [ "OSX_10_14" ];
+          "default" = [ "OSX_10_9" ];
+        };
+        resolvedDefaultFeatures = [ "OSX_10_9" ];
+      };
+      "semver" = rec {
+        crateName = "semver";
+        version = "1.0.20";
+        edition = "2018";
+        sha256 = "140hmbfa743hbmah1zjf07s8apavhvn04204qjigjiz5w6iscvw3";
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        features = {
+          "default" = [ "std" ];
+          "serde" = [ "dep:serde" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "serde" = rec {
+        crateName = "serde";
+        version = "1.0.192";
+        edition = "2018";
+        sha256 = "00ghhaabyrnr2cn504lckyqzh3fwr8k7pxnhhardr1djhj2a18mw";
+        authors = [
+          "Erick Tryzelaar <erick.tryzelaar@gmail.com>"
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "serde_derive";
+            packageId = "serde_derive";
+            optional = true;
+          }
+          {
+            name = "serde_derive";
+            packageId = "serde_derive";
+            target = { target, features }: false;
+          }
+        ];
+        devDependencies = [
+          {
+            name = "serde_derive";
+            packageId = "serde_derive";
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "derive" = [ "serde_derive" ];
+          "serde_derive" = [ "dep:serde_derive" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "derive" "serde_derive" "std" ];
+      };
+      "serde_derive" = rec {
+        crateName = "serde_derive";
+        version = "1.0.192";
+        edition = "2015";
+        sha256 = "1hgvm47ffd748sx22z1da7mgcfjmpr60gqzkff0a9yn9przj1iyn";
+        procMacro = true;
+        authors = [
+          "Erick Tryzelaar <erick.tryzelaar@gmail.com>"
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn";
+          }
+        ];
+        features = { };
+        resolvedDefaultFeatures = [ "default" ];
+      };
+      "serde_json" = rec {
+        crateName = "serde_json";
+        version = "1.0.108";
+        edition = "2021";
+        sha256 = "0ssj59s7lpzqh1m50kfzlnrip0p0jg9lmhn4098i33a0mhz7w71x";
+        authors = [
+          "Erick Tryzelaar <erick.tryzelaar@gmail.com>"
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "itoa";
+            packageId = "itoa";
+          }
+          {
+            name = "ryu";
+            packageId = "ryu";
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+            usesDefaultFeatures = false;
+          }
+        ];
+        devDependencies = [
+          {
+            name = "serde";
+            packageId = "serde";
+            features = [ "derive" ];
+          }
+        ];
+        features = {
+          "alloc" = [ "serde/alloc" ];
+          "default" = [ "std" ];
+          "indexmap" = [ "dep:indexmap" ];
+          "preserve_order" = [ "indexmap" "std" ];
+          "std" = [ "serde/std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "serde_path_to_error" = rec {
+        crateName = "serde_path_to_error";
+        version = "0.1.14";
+        edition = "2021";
+        sha256 = "0dc31z4bg0jwn69gcqsczbmcy5y4w6r0vdcc4c38vma9x2ycivjb";
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "itoa";
+            packageId = "itoa";
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+          }
+        ];
+
+      };
+      "sha2" = rec {
+        crateName = "sha2";
+        version = "0.9.9";
+        edition = "2018";
+        sha256 = "006q2f0ar26xcjxqz8zsncfgz86zqa5dkwlwv03rhx1rpzhs2n2d";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        dependencies = [
+          {
+            name = "block-buffer";
+            packageId = "block-buffer";
+          }
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "cpufeatures";
+            packageId = "cpufeatures";
+            target = { target, features }: (("aarch64" == target."arch" or null) || ("x86_64" == target."arch" or null) || ("x86" == target."arch" or null));
+          }
+          {
+            name = "digest";
+            packageId = "digest";
+          }
+          {
+            name = "opaque-debug";
+            packageId = "opaque-debug";
+          }
+        ];
+        devDependencies = [
+          {
+            name = "digest";
+            packageId = "digest";
+            features = [ "dev" ];
+          }
+        ];
+        features = {
+          "asm" = [ "sha2-asm" ];
+          "asm-aarch64" = [ "asm" ];
+          "default" = [ "std" ];
+          "sha2-asm" = [ "dep:sha2-asm" ];
+          "std" = [ "digest/std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "sharded-slab" = rec {
+        crateName = "sharded-slab";
+        version = "0.1.7";
+        edition = "2018";
+        sha256 = "1xipjr4nqsgw34k7a2cgj9zaasl2ds6jwn89886kww93d32a637l";
+        authors = [
+          "Eliza Weisman <eliza@buoyant.io>"
+        ];
+        dependencies = [
+          {
+            name = "lazy_static";
+            packageId = "lazy_static";
+          }
+        ];
+        features = {
+          "loom" = [ "dep:loom" ];
+        };
+      };
+      "shlex" = rec {
+        crateName = "shlex";
+        version = "1.2.0";
+        edition = "2015";
+        sha256 = "1033pj9dyb76nm5yv597nnvj3zpvr2aw9rm5wy0gah3dk99f1km7";
+        authors = [
+          "comex <comexk@gmail.com>"
+          "Fenhl <fenhl@fenhl.net>"
+        ];
+        features = {
+          "default" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "signal-hook-registry" = rec {
+        crateName = "signal-hook-registry";
+        version = "1.4.1";
+        edition = "2015";
+        sha256 = "18crkkw5k82bvcx088xlf5g4n3772m24qhzgfan80nda7d3rn8nq";
+        authors = [
+          "Michal 'vorner' Vaner <vorner@vorner.cz>"
+          "Masaki Hara <ackie.h.gmai@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+          }
+        ];
+
+      };
+      "slab" = rec {
+        crateName = "slab";
+        version = "0.4.9";
+        edition = "2018";
+        sha256 = "0rxvsgir0qw5lkycrqgb1cxsvxzjv9bmx73bk5y42svnzfba94lg";
+        authors = [
+          "Carl Lerche <me@carllerche.com>"
+        ];
+        buildDependencies = [
+          {
+            name = "autocfg";
+            packageId = "autocfg";
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "serde" = [ "dep:serde" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "smallvec" = rec {
+        crateName = "smallvec";
+        version = "1.11.2";
+        edition = "2018";
+        sha256 = "0w79x38f7c0np7hqfmzrif9zmn0avjvvm31b166zdk9d1aad1k2d";
+        authors = [
+          "The Servo Project Developers"
+        ];
+        features = {
+          "arbitrary" = [ "dep:arbitrary" ];
+          "const_new" = [ "const_generics" ];
+          "drain_keep_rest" = [ "drain_filter" ];
+          "serde" = [ "dep:serde" ];
+        };
+      };
+      "socket2 0.4.10" = rec {
+        crateName = "socket2";
+        version = "0.4.10";
+        edition = "2018";
+        sha256 = "03ack54dxhgfifzsj14k7qa3r5c9wqy3v6mqhlim99cc03y1cycz";
+        authors = [
+          "Alex Crichton <alex@alexcrichton.com>"
+          "Thomas de Zeeuw <thomasdezeeuw@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (target."unix" or false);
+          }
+          {
+            name = "winapi";
+            packageId = "winapi";
+            target = { target, features }: (target."windows" or false);
+            features = [ "handleapi" "ws2ipdef" "ws2tcpip" ];
+          }
+        ];
+        features = { };
+        resolvedDefaultFeatures = [ "all" ];
+      };
+      "socket2 0.5.5" = rec {
+        crateName = "socket2";
+        version = "0.5.5";
+        edition = "2021";
+        sha256 = "1sgq315f1njky114ip7wcy83qlphv9qclprfjwvxcpfblmcsqpvv";
+        authors = [
+          "Alex Crichton <alex@alexcrichton.com>"
+          "Thomas de Zeeuw <thomasdezeeuw@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (target."unix" or false);
+          }
+          {
+            name = "windows-sys";
+            packageId = "windows-sys";
+            target = { target, features }: (target."windows" or false);
+            features = [ "Win32_Foundation" "Win32_Networking_WinSock" "Win32_System_IO" "Win32_System_Threading" "Win32_System_WindowsProgramming" ];
+          }
+        ];
+        features = { };
+        resolvedDefaultFeatures = [ "all" ];
+      };
+      "spin 0.5.2" = rec {
+        crateName = "spin";
+        version = "0.5.2";
+        edition = "2015";
+        sha256 = "0b84m6dbzrwf2kxylnw82d3dr8w06av7rfkr8s85fb5f43rwyqvf";
+        authors = [
+          "Mathijs van de Nes <git@mathijs.vd-nes.nl>"
+          "John Ericson <git@JohnEricson.me>"
+        ];
+
+      };
+      "spin 0.9.8" = rec {
+        crateName = "spin";
+        version = "0.9.8";
+        edition = "2015";
+        sha256 = "0rvam5r0p3a6qhc18scqpvpgb3ckzyqxpgdfyjnghh8ja7byi039";
+        authors = [
+          "Mathijs van de Nes <git@mathijs.vd-nes.nl>"
+          "John Ericson <git@JohnEricson.me>"
+          "Joshua Barretto <joshua.s.barretto@gmail.com>"
+        ];
+        features = {
+          "barrier" = [ "mutex" ];
+          "default" = [ "lock_api" "mutex" "spin_mutex" "rwlock" "once" "lazy" "barrier" ];
+          "fair_mutex" = [ "mutex" ];
+          "lazy" = [ "once" ];
+          "lock_api" = [ "lock_api_crate" ];
+          "lock_api_crate" = [ "dep:lock_api_crate" ];
+          "portable-atomic" = [ "dep:portable-atomic" ];
+          "portable_atomic" = [ "portable-atomic" ];
+          "spin_mutex" = [ "mutex" ];
+          "ticket_mutex" = [ "mutex" ];
+          "use_ticket_mutex" = [ "mutex" "ticket_mutex" ];
+        };
+        resolvedDefaultFeatures = [ "once" ];
+      };
+      "subtle" = rec {
+        crateName = "subtle";
+        version = "2.4.1";
+        edition = "2015";
+        sha256 = "00b6jzh9gzb0h9n25g06nqr90z3xzqppfhhb260s1hjhh4pg7pkb";
+        authors = [
+          "Isis Lovecruft <isis@patternsinthevoid.net>"
+          "Henry de Valence <hdevalence@hdevalence.ca>"
+        ];
+        features = {
+          "default" = [ "std" "i128" ];
+        };
+      };
+      "syn" = rec {
+        crateName = "syn";
+        version = "2.0.39";
+        edition = "2021";
+        sha256 = "0ymyhxnk1yi4pzf72qk3lrdm9lgjwcrcwci0hhz5vx7wya88prr3";
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "unicode-ident";
+            packageId = "unicode-ident";
+          }
+        ];
+        features = {
+          "default" = [ "derive" "parsing" "printing" "clone-impls" "proc-macro" ];
+          "printing" = [ "quote" ];
+          "proc-macro" = [ "proc-macro2/proc-macro" "quote/proc-macro" ];
+          "quote" = [ "dep:quote" ];
+          "test" = [ "syn-test-suite/all-features" ];
+        };
+        resolvedDefaultFeatures = [ "clone-impls" "default" "derive" "extra-traits" "full" "parsing" "printing" "proc-macro" "quote" "visit" "visit-mut" ];
+      };
+      "thiserror" = rec {
+        crateName = "thiserror";
+        version = "1.0.50";
+        edition = "2021";
+        sha256 = "1ll2sfbrxks8jja161zh1pgm3yssr7aawdmaa2xmcwcsbh7j39zr";
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "thiserror-impl";
+            packageId = "thiserror-impl";
+          }
+        ];
+
+      };
+      "thiserror-impl" = rec {
+        crateName = "thiserror-impl";
+        version = "1.0.50";
+        edition = "2021";
+        sha256 = "1f0lmam4765sfnwr4b1n00y14vxh10g0311mkk0adr80pi02wsr6";
+        procMacro = true;
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn";
+          }
+        ];
+
+      };
+      "thread_local" = rec {
+        crateName = "thread_local";
+        version = "1.1.7";
+        edition = "2021";
+        sha256 = "0lp19jdgvp5m4l60cgxdnl00yw1hlqy8gcywg9bddwng9h36zp9z";
+        authors = [
+          "Amanieu d'Antras <amanieu@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "once_cell";
+            packageId = "once_cell";
+          }
+        ];
+        features = { };
+      };
+      "tokio" = rec {
+        crateName = "tokio";
+        version = "1.34.0";
+        edition = "2021";
+        sha256 = "1fgmssdga42a2hn9spm9dh1v9ajpcbs4r3svmzvk9s0iciv19h6h";
+        authors = [
+          "Tokio Contributors <team@tokio.rs>"
+        ];
+        dependencies = [
+          {
+            name = "backtrace";
+            packageId = "backtrace";
+            target = { target, features }: (target."tokio_taskdump" or false);
+          }
+          {
+            name = "bytes";
+            packageId = "bytes";
+            optional = true;
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            optional = true;
+            target = { target, features }: (target."unix" or false);
+          }
+          {
+            name = "mio";
+            packageId = "mio";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "num_cpus";
+            packageId = "num_cpus";
+            optional = true;
+          }
+          {
+            name = "parking_lot";
+            packageId = "parking_lot";
+            optional = true;
+          }
+          {
+            name = "pin-project-lite";
+            packageId = "pin-project-lite";
+          }
+          {
+            name = "signal-hook-registry";
+            packageId = "signal-hook-registry";
+            optional = true;
+            target = { target, features }: (target."unix" or false);
+          }
+          {
+            name = "socket2";
+            packageId = "socket2 0.5.5";
+            optional = true;
+            target = { target, features }: (!(builtins.elem "wasm" target."family"));
+            features = [ "all" ];
+          }
+          {
+            name = "tokio-macros";
+            packageId = "tokio-macros";
+            optional = true;
+          }
+          {
+            name = "windows-sys";
+            packageId = "windows-sys";
+            optional = true;
+            target = { target, features }: (target."windows" or false);
+          }
+        ];
+        devDependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (target."unix" or false);
+          }
+          {
+            name = "socket2";
+            packageId = "socket2 0.5.5";
+            target = { target, features }: (!(builtins.elem "wasm" target."family"));
+          }
+          {
+            name = "windows-sys";
+            packageId = "windows-sys";
+            target = { target, features }: (target."windows" or false);
+            features = [ "Win32_Foundation" "Win32_Security_Authorization" ];
+          }
+        ];
+        features = {
+          "bytes" = [ "dep:bytes" ];
+          "full" = [ "fs" "io-util" "io-std" "macros" "net" "parking_lot" "process" "rt" "rt-multi-thread" "signal" "sync" "time" ];
+          "io-util" = [ "bytes" ];
+          "libc" = [ "dep:libc" ];
+          "macros" = [ "tokio-macros" ];
+          "mio" = [ "dep:mio" ];
+          "net" = [ "libc" "mio/os-poll" "mio/os-ext" "mio/net" "socket2" "windows-sys/Win32_Foundation" "windows-sys/Win32_Security" "windows-sys/Win32_Storage_FileSystem" "windows-sys/Win32_System_Pipes" "windows-sys/Win32_System_SystemServices" ];
+          "num_cpus" = [ "dep:num_cpus" ];
+          "parking_lot" = [ "dep:parking_lot" ];
+          "process" = [ "bytes" "libc" "mio/os-poll" "mio/os-ext" "mio/net" "signal-hook-registry" "windows-sys/Win32_Foundation" "windows-sys/Win32_System_Threading" "windows-sys/Win32_System_WindowsProgramming" ];
+          "rt-multi-thread" = [ "num_cpus" "rt" ];
+          "signal" = [ "libc" "mio/os-poll" "mio/net" "mio/os-ext" "signal-hook-registry" "windows-sys/Win32_Foundation" "windows-sys/Win32_System_Console" ];
+          "signal-hook-registry" = [ "dep:signal-hook-registry" ];
+          "socket2" = [ "dep:socket2" ];
+          "test-util" = [ "rt" "sync" "time" ];
+          "tokio-macros" = [ "dep:tokio-macros" ];
+          "tracing" = [ "dep:tracing" ];
+          "windows-sys" = [ "dep:windows-sys" ];
+        };
+        resolvedDefaultFeatures = [ "bytes" "default" "fs" "full" "io-std" "io-util" "libc" "macros" "mio" "net" "num_cpus" "parking_lot" "process" "rt" "rt-multi-thread" "signal" "signal-hook-registry" "socket2" "sync" "time" "tokio-macros" "windows-sys" ];
+      };
+      "tokio-macros" = rec {
+        crateName = "tokio-macros";
+        version = "2.2.0";
+        edition = "2021";
+        sha256 = "0fwjy4vdx1h9pi4g2nml72wi0fr27b5m954p13ji9anyy8l1x2jv";
+        procMacro = true;
+        authors = [
+          "Tokio Contributors <team@tokio.rs>"
+        ];
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn";
+            features = [ "full" ];
+          }
+        ];
+
+      };
+      "tokio-rustls" = rec {
+        crateName = "tokio-rustls";
+        version = "0.23.4";
+        edition = "2018";
+        sha256 = "0nfsmmi8l1lgpbfy6079d5i13984djzcxrdr9jc06ghi0cwyhgn4";
+        authors = [
+          "quininer kel <quininer@live.com>"
+        ];
+        dependencies = [
+          {
+            name = "rustls";
+            packageId = "rustls";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+          }
+          {
+            name = "webpki";
+            packageId = "webpki";
+          }
+        ];
+        devDependencies = [
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "full" ];
+          }
+        ];
+        features = {
+          "dangerous_configuration" = [ "rustls/dangerous_configuration" ];
+          "default" = [ "logging" "tls12" ];
+          "logging" = [ "rustls/logging" ];
+          "tls12" = [ "rustls/tls12" ];
+        };
+        resolvedDefaultFeatures = [ "logging" "tls12" ];
+      };
+      "tokio-stream" = rec {
+        crateName = "tokio-stream";
+        version = "0.1.14";
+        edition = "2021";
+        sha256 = "0hi8hcwavh5sdi1ivc9qc4yvyr32f153c212dpd7sb366y6rhz1r";
+        authors = [
+          "Tokio Contributors <team@tokio.rs>"
+        ];
+        dependencies = [
+          {
+            name = "futures-core";
+            packageId = "futures-core";
+          }
+          {
+            name = "pin-project-lite";
+            packageId = "pin-project-lite";
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "sync" ];
+          }
+        ];
+        devDependencies = [
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "full" "test-util" ];
+          }
+        ];
+        features = {
+          "default" = [ "time" ];
+          "fs" = [ "tokio/fs" ];
+          "full" = [ "time" "net" "io-util" "fs" "sync" "signal" ];
+          "io-util" = [ "tokio/io-util" ];
+          "net" = [ "tokio/net" ];
+          "signal" = [ "tokio/signal" ];
+          "sync" = [ "tokio/sync" "tokio-util" ];
+          "time" = [ "tokio/time" ];
+          "tokio-util" = [ "dep:tokio-util" ];
+        };
+        resolvedDefaultFeatures = [ "default" "time" ];
+      };
+      "tokio-util" = rec {
+        crateName = "tokio-util";
+        version = "0.7.10";
+        edition = "2021";
+        sha256 = "058y6x4mf0fsqji9rfyb77qbfyc50y4pk2spqgj6xsyr693z66al";
+        authors = [
+          "Tokio Contributors <team@tokio.rs>"
+        ];
+        dependencies = [
+          {
+            name = "bytes";
+            packageId = "bytes";
+          }
+          {
+            name = "futures-core";
+            packageId = "futures-core";
+          }
+          {
+            name = "futures-sink";
+            packageId = "futures-sink";
+          }
+          {
+            name = "pin-project-lite";
+            packageId = "pin-project-lite";
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "sync" ];
+          }
+          {
+            name = "tracing";
+            packageId = "tracing";
+            optional = true;
+            usesDefaultFeatures = false;
+            features = [ "std" ];
+          }
+        ];
+        devDependencies = [
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "full" ];
+          }
+        ];
+        features = {
+          "__docs_rs" = [ "futures-util" ];
+          "codec" = [ "tracing" ];
+          "compat" = [ "futures-io" ];
+          "full" = [ "codec" "compat" "io-util" "time" "net" "rt" ];
+          "futures-io" = [ "dep:futures-io" ];
+          "futures-util" = [ "dep:futures-util" ];
+          "hashbrown" = [ "dep:hashbrown" ];
+          "io-util" = [ "io" "tokio/rt" "tokio/io-util" ];
+          "net" = [ "tokio/net" ];
+          "rt" = [ "tokio/rt" "tokio/sync" "futures-util" "hashbrown" ];
+          "slab" = [ "dep:slab" ];
+          "time" = [ "tokio/time" "slab" ];
+          "tracing" = [ "dep:tracing" ];
+        };
+        resolvedDefaultFeatures = [ "codec" "default" "tracing" ];
+      };
+      "tower" = rec {
+        crateName = "tower";
+        version = "0.4.13";
+        edition = "2018";
+        sha256 = "073wncyqav4sak1p755hf6vl66njgfc1z1g1di9rxx3cvvh9pymq";
+        authors = [
+          "Tower Maintainers <team@tower-rs.com>"
+        ];
+        dependencies = [
+          {
+            name = "futures-core";
+            packageId = "futures-core";
+            optional = true;
+          }
+          {
+            name = "futures-util";
+            packageId = "futures-util";
+            optional = true;
+            usesDefaultFeatures = false;
+            features = [ "alloc" ];
+          }
+          {
+            name = "pin-project";
+            packageId = "pin-project";
+            optional = true;
+          }
+          {
+            name = "pin-project-lite";
+            packageId = "pin-project-lite";
+            optional = true;
+          }
+          {
+            name = "tower-layer";
+            packageId = "tower-layer";
+          }
+          {
+            name = "tower-service";
+            packageId = "tower-service";
+          }
+          {
+            name = "tracing";
+            packageId = "tracing";
+            optional = true;
+            usesDefaultFeatures = false;
+            features = [ "std" ];
+          }
+        ];
+        devDependencies = [
+          {
+            name = "pin-project-lite";
+            packageId = "pin-project-lite";
+          }
+        ];
+        features = {
+          "__common" = [ "futures-core" "pin-project-lite" ];
+          "balance" = [ "discover" "load" "ready-cache" "make" "rand" "slab" ];
+          "buffer" = [ "__common" "tokio/sync" "tokio/rt" "tokio-util" "tracing" ];
+          "default" = [ "log" ];
+          "discover" = [ "__common" ];
+          "filter" = [ "__common" "futures-util" ];
+          "full" = [ "balance" "buffer" "discover" "filter" "hedge" "limit" "load" "load-shed" "make" "ready-cache" "reconnect" "retry" "spawn-ready" "steer" "timeout" "util" ];
+          "futures-core" = [ "dep:futures-core" ];
+          "futures-util" = [ "dep:futures-util" ];
+          "hdrhistogram" = [ "dep:hdrhistogram" ];
+          "hedge" = [ "util" "filter" "futures-util" "hdrhistogram" "tokio/time" "tracing" ];
+          "indexmap" = [ "dep:indexmap" ];
+          "limit" = [ "__common" "tokio/time" "tokio/sync" "tokio-util" "tracing" ];
+          "load" = [ "__common" "tokio/time" "tracing" ];
+          "load-shed" = [ "__common" ];
+          "log" = [ "tracing/log" ];
+          "make" = [ "futures-util" "pin-project-lite" "tokio/io-std" ];
+          "pin-project" = [ "dep:pin-project" ];
+          "pin-project-lite" = [ "dep:pin-project-lite" ];
+          "rand" = [ "dep:rand" ];
+          "ready-cache" = [ "futures-core" "futures-util" "indexmap" "tokio/sync" "tracing" "pin-project-lite" ];
+          "reconnect" = [ "make" "tokio/io-std" "tracing" ];
+          "retry" = [ "__common" "tokio/time" ];
+          "slab" = [ "dep:slab" ];
+          "spawn-ready" = [ "__common" "futures-util" "tokio/sync" "tokio/rt" "util" "tracing" ];
+          "timeout" = [ "pin-project-lite" "tokio/time" ];
+          "tokio" = [ "dep:tokio" ];
+          "tokio-stream" = [ "dep:tokio-stream" ];
+          "tokio-util" = [ "dep:tokio-util" ];
+          "tracing" = [ "dep:tracing" ];
+          "util" = [ "__common" "futures-util" "pin-project" ];
+        };
+        resolvedDefaultFeatures = [ "__common" "default" "futures-core" "futures-util" "log" "pin-project" "pin-project-lite" "tracing" "util" ];
+      };
+      "tower-layer" = rec {
+        crateName = "tower-layer";
+        version = "0.3.2";
+        edition = "2018";
+        sha256 = "1l7i17k9vlssrdg4s3b0ia5jjkmmxsvv8s9y9ih0jfi8ssz8s362";
+        authors = [
+          "Tower Maintainers <team@tower-rs.com>"
+        ];
+
+      };
+      "tower-service" = rec {
+        crateName = "tower-service";
+        version = "0.3.2";
+        edition = "2018";
+        sha256 = "0lmfzmmvid2yp2l36mbavhmqgsvzqf7r2wiwz73ml4xmwaf1rg5n";
+        authors = [
+          "Tower Maintainers <team@tower-rs.com>"
+        ];
+
+      };
+      "tracing" = rec {
+        crateName = "tracing";
+        version = "0.1.40";
+        edition = "2018";
+        sha256 = "1vv48dac9zgj9650pg2b4d0j3w6f3x9gbggf43scq5hrlysklln3";
+        authors = [
+          "Eliza Weisman <eliza@buoyant.io>"
+          "Tokio Contributors <team@tokio.rs>"
+        ];
+        dependencies = [
+          {
+            name = "log";
+            packageId = "log";
+            optional = true;
+          }
+          {
+            name = "pin-project-lite";
+            packageId = "pin-project-lite";
+          }
+          {
+            name = "tracing-attributes";
+            packageId = "tracing-attributes";
+            optional = true;
+          }
+          {
+            name = "tracing-core";
+            packageId = "tracing-core";
+            usesDefaultFeatures = false;
+          }
+        ];
+        devDependencies = [
+          {
+            name = "log";
+            packageId = "log";
+          }
+        ];
+        features = {
+          "attributes" = [ "tracing-attributes" ];
+          "default" = [ "std" "attributes" ];
+          "log" = [ "dep:log" ];
+          "log-always" = [ "log" ];
+          "std" = [ "tracing-core/std" ];
+          "tracing-attributes" = [ "dep:tracing-attributes" ];
+          "valuable" = [ "tracing-core/valuable" ];
+        };
+        resolvedDefaultFeatures = [ "attributes" "default" "log" "std" "tracing-attributes" ];
+      };
+      "tracing-attributes" = rec {
+        crateName = "tracing-attributes";
+        version = "0.1.27";
+        edition = "2018";
+        sha256 = "1rvb5dn9z6d0xdj14r403z0af0bbaqhg02hq4jc97g5wds6lqw1l";
+        procMacro = true;
+        authors = [
+          "Tokio Contributors <team@tokio.rs>"
+          "Eliza Weisman <eliza@buoyant.io>"
+          "David Barsky <dbarsky@amazon.com>"
+        ];
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn";
+            usesDefaultFeatures = false;
+            features = [ "full" "parsing" "printing" "visit-mut" "clone-impls" "extra-traits" "proc-macro" ];
+          }
+        ];
+        features = { };
+      };
+      "tracing-core" = rec {
+        crateName = "tracing-core";
+        version = "0.1.32";
+        edition = "2018";
+        sha256 = "0m5aglin3cdwxpvbg6kz0r9r0k31j48n0kcfwsp6l49z26k3svf0";
+        authors = [
+          "Tokio Contributors <team@tokio.rs>"
+        ];
+        dependencies = [
+          {
+            name = "once_cell";
+            packageId = "once_cell";
+            optional = true;
+          }
+          {
+            name = "valuable";
+            packageId = "valuable";
+            optional = true;
+            usesDefaultFeatures = false;
+            target = { target, features }: (target."tracing_unstable" or false);
+          }
+        ];
+        features = {
+          "default" = [ "std" "valuable/std" ];
+          "once_cell" = [ "dep:once_cell" ];
+          "std" = [ "once_cell" ];
+          "valuable" = [ "dep:valuable" ];
+        };
+        resolvedDefaultFeatures = [ "default" "once_cell" "std" "valuable" ];
+      };
+      "tracing-log" = rec {
+        crateName = "tracing-log";
+        version = "0.1.4";
+        edition = "2018";
+        sha256 = "1wmxawaz94sk52i4vs2wg5d5clyks972rqskrvc93rxl14ki2lgp";
+        authors = [
+          "Tokio Contributors <team@tokio.rs>"
+        ];
+        dependencies = [
+          {
+            name = "log";
+            packageId = "log";
+          }
+          {
+            name = "once_cell";
+            packageId = "once_cell";
+          }
+          {
+            name = "tracing-core";
+            packageId = "tracing-core";
+          }
+        ];
+        features = {
+          "ahash" = [ "dep:ahash" ];
+          "default" = [ "log-tracer" "trace-logger" "std" ];
+          "env_logger" = [ "dep:env_logger" ];
+          "interest-cache" = [ "lru" "ahash" ];
+          "lru" = [ "dep:lru" ];
+          "std" = [ "log/std" ];
+        };
+        resolvedDefaultFeatures = [ "log-tracer" "std" ];
+      };
+      "tracing-serde" = rec {
+        crateName = "tracing-serde";
+        version = "0.1.3";
+        edition = "2018";
+        sha256 = "1qfr0va69djvxqvjrx4vqq7p6myy414lx4w1f6amcn0hfwqj2sxw";
+        authors = [
+          "Tokio Contributors <team@tokio.rs>"
+        ];
+        dependencies = [
+          {
+            name = "serde";
+            packageId = "serde";
+          }
+          {
+            name = "tracing-core";
+            packageId = "tracing-core";
+          }
+        ];
+        features = {
+          "valuable" = [ "valuable_crate" "valuable-serde" "tracing-core/valuable" ];
+          "valuable-serde" = [ "dep:valuable-serde" ];
+          "valuable_crate" = [ "dep:valuable_crate" ];
+        };
+      };
+      "tracing-subscriber" = rec {
+        crateName = "tracing-subscriber";
+        version = "0.3.17";
+        edition = "2018";
+        sha256 = "0xvwfpmb943hdy4gzyn7a2azgigf30mfd1kx10gyh5gr6yy539ih";
+        authors = [
+          "Eliza Weisman <eliza@buoyant.io>"
+          "David Barsky <me@davidbarsky.com>"
+          "Tokio Contributors <team@tokio.rs>"
+        ];
+        dependencies = [
+          {
+            name = "nu-ansi-term";
+            packageId = "nu-ansi-term";
+            optional = true;
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+            optional = true;
+          }
+          {
+            name = "serde_json";
+            packageId = "serde_json";
+            optional = true;
+          }
+          {
+            name = "sharded-slab";
+            packageId = "sharded-slab";
+            optional = true;
+          }
+          {
+            name = "smallvec";
+            packageId = "smallvec";
+            optional = true;
+          }
+          {
+            name = "thread_local";
+            packageId = "thread_local";
+            optional = true;
+          }
+          {
+            name = "tracing-core";
+            packageId = "tracing-core";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "tracing-log";
+            packageId = "tracing-log";
+            optional = true;
+            usesDefaultFeatures = false;
+            features = [ "log-tracer" "std" ];
+          }
+          {
+            name = "tracing-serde";
+            packageId = "tracing-serde";
+            optional = true;
+          }
+        ];
+        devDependencies = [
+          {
+            name = "tracing-log";
+            packageId = "tracing-log";
+          }
+        ];
+        features = {
+          "ansi" = [ "fmt" "nu-ansi-term" ];
+          "default" = [ "smallvec" "fmt" "ansi" "tracing-log" "std" ];
+          "env-filter" = [ "matchers" "regex" "once_cell" "tracing" "std" "thread_local" ];
+          "fmt" = [ "registry" "std" ];
+          "json" = [ "tracing-serde" "serde" "serde_json" ];
+          "local-time" = [ "time/local-offset" ];
+          "matchers" = [ "dep:matchers" ];
+          "nu-ansi-term" = [ "dep:nu-ansi-term" ];
+          "once_cell" = [ "dep:once_cell" ];
+          "parking_lot" = [ "dep:parking_lot" ];
+          "regex" = [ "dep:regex" ];
+          "registry" = [ "sharded-slab" "thread_local" "std" ];
+          "serde" = [ "dep:serde" ];
+          "serde_json" = [ "dep:serde_json" ];
+          "sharded-slab" = [ "dep:sharded-slab" ];
+          "smallvec" = [ "dep:smallvec" ];
+          "std" = [ "alloc" "tracing-core/std" ];
+          "thread_local" = [ "dep:thread_local" ];
+          "time" = [ "dep:time" ];
+          "tracing" = [ "dep:tracing" ];
+          "tracing-log" = [ "dep:tracing-log" ];
+          "tracing-serde" = [ "dep:tracing-serde" ];
+          "valuable" = [ "tracing-core/valuable" "valuable_crate" "valuable-serde" "tracing-serde/valuable" ];
+          "valuable-serde" = [ "dep:valuable-serde" ];
+          "valuable_crate" = [ "dep:valuable_crate" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "ansi" "default" "fmt" "json" "nu-ansi-term" "registry" "serde" "serde_json" "sharded-slab" "smallvec" "std" "thread_local" "tracing-log" "tracing-serde" ];
+      };
+      "try-lock" = rec {
+        crateName = "try-lock";
+        version = "0.2.4";
+        edition = "2015";
+        sha256 = "1vc15paa4zi06ixsxihwbvfn24d708nsyg1ncgqwcrn42byyqa1m";
+        authors = [
+          "Sean McArthur <sean@seanmonstar.com>"
+        ];
+
+      };
+      "turbofetch" = rec {
+        crateName = "turbofetch";
+        version = "0.1.0";
+        edition = "2021";
+        crateBin = [
+          {
+            name = "turbofetch";
+            path = "src/main.rs";
+            requiredFeatures = [ ];
+          }
+        ];
+        # We can't filter paths with references in Nix 2.4
+        # See https://github.com/NixOS/nix/issues/5410
+        src =
+          if ((lib.versionOlder builtins.nixVersion "2.4pre20211007") || (lib.versionOlder "2.5" builtins.nixVersion))
+          then lib.cleanSourceWith { filter = sourceFilter; src = ./.; }
+          else ./.;
+        dependencies = [
+          {
+            name = "aws_lambda_events";
+            packageId = "aws_lambda_events";
+            usesDefaultFeatures = false;
+            features = [ "lambda_function_urls" ];
+          }
+          {
+            name = "bytes";
+            packageId = "bytes";
+          }
+          {
+            name = "data-encoding";
+            packageId = "data-encoding";
+          }
+          {
+            name = "futures";
+            packageId = "futures";
+            usesDefaultFeatures = false;
+            features = [ "std" ];
+          }
+          {
+            name = "httparse";
+            packageId = "httparse";
+          }
+          {
+            name = "hyper";
+            packageId = "hyper";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "lambda_runtime";
+            packageId = "lambda_runtime";
+          }
+          {
+            name = "magic-buffer";
+            packageId = "magic-buffer";
+          }
+          {
+            name = "rusoto_core";
+            packageId = "rusoto_core";
+            usesDefaultFeatures = false;
+            features = [ "rustls" ];
+          }
+          {
+            name = "rusoto_s3";
+            packageId = "rusoto_s3";
+            usesDefaultFeatures = false;
+            features = [ "rustls" ];
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+            features = [ "derive" ];
+          }
+          {
+            name = "serde_json";
+            packageId = "serde_json";
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "full" ];
+          }
+          {
+            name = "tower";
+            packageId = "tower";
+          }
+          {
+            name = "tracing";
+            packageId = "tracing";
+          }
+          {
+            name = "tracing-subscriber";
+            packageId = "tracing-subscriber";
+            features = [ "json" ];
+          }
+          {
+            name = "zstd";
+            packageId = "zstd";
+          }
+        ];
+
+      };
+      "typenum" = rec {
+        crateName = "typenum";
+        version = "1.17.0";
+        edition = "2018";
+        sha256 = "09dqxv69m9lj9zvv6xw5vxaqx15ps0vxyy5myg33i0kbqvq0pzs2";
+        build = "build/main.rs";
+        authors = [
+          "Paho Lurie-Gregg <paho@paholg.com>"
+          "Andre Bogus <bogusandre@gmail.com>"
+        ];
+        features = {
+          "scale-info" = [ "dep:scale-info" ];
+          "scale_info" = [ "scale-info/derive" ];
+        };
+      };
+      "unicode-ident" = rec {
+        crateName = "unicode-ident";
+        version = "1.0.12";
+        edition = "2018";
+        sha256 = "0jzf1znfpb2gx8nr8mvmyqs1crnv79l57nxnbiszc7xf7ynbjm1k";
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+
+      };
+      "untrusted 0.7.1" = rec {
+        crateName = "untrusted";
+        version = "0.7.1";
+        edition = "2018";
+        sha256 = "0jkbqaj9d3v5a91pp3wp9mffvng1nhycx6sh4qkdd9qyr62ccmm1";
+        libPath = "src/untrusted.rs";
+        authors = [
+          "Brian Smith <brian@briansmith.org>"
+        ];
+
+      };
+      "untrusted 0.9.0" = rec {
+        crateName = "untrusted";
+        version = "0.9.0";
+        edition = "2018";
+        sha256 = "1ha7ib98vkc538x0z60gfn0fc5whqdd85mb87dvisdcaifi6vjwf";
+        authors = [
+          "Brian Smith <brian@briansmith.org>"
+        ];
+
+      };
+      "valuable" = rec {
+        crateName = "valuable";
+        version = "0.1.0";
+        edition = "2018";
+        sha256 = "0v9gp3nkjbl30z0fd56d8mx7w1csk86wwjhfjhr400wh9mfpw2w3";
+        features = {
+          "default" = [ "std" ];
+          "derive" = [ "valuable-derive" ];
+          "std" = [ "alloc" ];
+          "valuable-derive" = [ "dep:valuable-derive" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "std" ];
+      };
+      "version_check" = rec {
+        crateName = "version_check";
+        version = "0.9.4";
+        edition = "2015";
+        sha256 = "0gs8grwdlgh0xq660d7wr80x14vxbizmd8dbp29p2pdncx8lp1s9";
+        authors = [
+          "Sergio Benitez <sb@sergio.bz>"
+        ];
+
+      };
+      "want" = rec {
+        crateName = "want";
+        version = "0.3.1";
+        edition = "2018";
+        sha256 = "03hbfrnvqqdchb5kgxyavb9jabwza0dmh2vw5kg0dq8rxl57d9xz";
+        authors = [
+          "Sean McArthur <sean@seanmonstar.com>"
+        ];
+        dependencies = [
+          {
+            name = "try-lock";
+            packageId = "try-lock";
+          }
+        ];
+
+      };
+      "wasi" = rec {
+        crateName = "wasi";
+        version = "0.11.0+wasi-snapshot-preview1";
+        edition = "2018";
+        sha256 = "08z4hxwkpdpalxjps1ai9y7ihin26y9f476i53dv98v45gkqg3cw";
+        authors = [
+          "The Cranelift Project Developers"
+        ];
+        features = {
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "core" = [ "dep:core" ];
+          "default" = [ "std" ];
+          "rustc-dep-of-std" = [ "compiler_builtins" "core" "rustc-std-workspace-alloc" ];
+          "rustc-std-workspace-alloc" = [ "dep:rustc-std-workspace-alloc" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "wasm-bindgen" = rec {
+        crateName = "wasm-bindgen";
+        version = "0.2.88";
+        edition = "2018";
+        sha256 = "1khgsh4z9bga35mjhg41dl7523i69ffc5m8ckhqaw6ssyabc5bkx";
+        authors = [
+          "The wasm-bindgen Developers"
+        ];
+        dependencies = [
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "wasm-bindgen-macro";
+            packageId = "wasm-bindgen-macro";
+          }
+        ];
+        features = {
+          "default" = [ "spans" "std" ];
+          "enable-interning" = [ "std" ];
+          "gg-alloc" = [ "wasm-bindgen-test/gg-alloc" ];
+          "serde" = [ "dep:serde" ];
+          "serde-serialize" = [ "serde" "serde_json" "std" ];
+          "serde_json" = [ "dep:serde_json" ];
+          "spans" = [ "wasm-bindgen-macro/spans" ];
+          "strict-macro" = [ "wasm-bindgen-macro/strict-macro" ];
+          "xxx_debug_only_print_generated_code" = [ "wasm-bindgen-macro/xxx_debug_only_print_generated_code" ];
+        };
+        resolvedDefaultFeatures = [ "default" "spans" "std" ];
+      };
+      "wasm-bindgen-backend" = rec {
+        crateName = "wasm-bindgen-backend";
+        version = "0.2.88";
+        edition = "2018";
+        sha256 = "05zj8yl243rvs87rhicq2l1d6443lnm6k90khf744khf9ikg95z3";
+        authors = [
+          "The wasm-bindgen Developers"
+        ];
+        dependencies = [
+          {
+            name = "bumpalo";
+            packageId = "bumpalo";
+          }
+          {
+            name = "log";
+            packageId = "log";
+          }
+          {
+            name = "once_cell";
+            packageId = "once_cell";
+          }
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn";
+            features = [ "full" ];
+          }
+          {
+            name = "wasm-bindgen-shared";
+            packageId = "wasm-bindgen-shared";
+          }
+        ];
+        features = {
+          "extra-traits" = [ "syn/extra-traits" ];
+        };
+        resolvedDefaultFeatures = [ "spans" ];
+      };
+      "wasm-bindgen-macro" = rec {
+        crateName = "wasm-bindgen-macro";
+        version = "0.2.88";
+        edition = "2018";
+        sha256 = "1chn3wgw9awmvs0fpmazbqyc5rwfgy3pj7lzwczmzb887dxh2qar";
+        procMacro = true;
+        authors = [
+          "The wasm-bindgen Developers"
+        ];
+        dependencies = [
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "wasm-bindgen-macro-support";
+            packageId = "wasm-bindgen-macro-support";
+          }
+        ];
+        features = {
+          "spans" = [ "wasm-bindgen-macro-support/spans" ];
+          "strict-macro" = [ "wasm-bindgen-macro-support/strict-macro" ];
+        };
+        resolvedDefaultFeatures = [ "spans" ];
+      };
+      "wasm-bindgen-macro-support" = rec {
+        crateName = "wasm-bindgen-macro-support";
+        version = "0.2.88";
+        edition = "2018";
+        sha256 = "01rrzg3y1apqygsjz1jg0n7p831nm4kdyxmxyl85x7v6mf6kndf5";
+        authors = [
+          "The wasm-bindgen Developers"
+        ];
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn";
+            features = [ "visit" "full" ];
+          }
+          {
+            name = "wasm-bindgen-backend";
+            packageId = "wasm-bindgen-backend";
+          }
+          {
+            name = "wasm-bindgen-shared";
+            packageId = "wasm-bindgen-shared";
+          }
+        ];
+        features = {
+          "extra-traits" = [ "syn/extra-traits" ];
+          "spans" = [ "wasm-bindgen-backend/spans" ];
+        };
+        resolvedDefaultFeatures = [ "spans" ];
+      };
+      "wasm-bindgen-shared" = rec {
+        crateName = "wasm-bindgen-shared";
+        version = "0.2.88";
+        edition = "2018";
+        links = "wasm_bindgen";
+        sha256 = "02vmw2rzsla1qm0zgfng4kqz52xn8k54v8ads4g1macv09fnq10d";
+        authors = [
+          "The wasm-bindgen Developers"
+        ];
+
+      };
+      "web-sys" = rec {
+        crateName = "web-sys";
+        version = "0.3.65";
+        edition = "2018";
+        sha256 = "11ba406ca9qssc21c37v49sn2y2gsdn6c3nva4hjf8v3yv2rkd2x";
+        authors = [
+          "The wasm-bindgen Developers"
+        ];
+        dependencies = [
+          {
+            name = "js-sys";
+            packageId = "js-sys";
+          }
+          {
+            name = "wasm-bindgen";
+            packageId = "wasm-bindgen";
+          }
+        ];
+        features = {
+          "AbortSignal" = [ "EventTarget" ];
+          "AnalyserNode" = [ "AudioNode" "EventTarget" ];
+          "Animation" = [ "EventTarget" ];
+          "AnimationEvent" = [ "Event" ];
+          "AnimationPlaybackEvent" = [ "Event" ];
+          "Attr" = [ "EventTarget" "Node" ];
+          "AudioBufferSourceNode" = [ "AudioNode" "AudioScheduledSourceNode" "EventTarget" ];
+          "AudioContext" = [ "BaseAudioContext" "EventTarget" ];
+          "AudioDestinationNode" = [ "AudioNode" "EventTarget" ];
+          "AudioNode" = [ "EventTarget" ];
+          "AudioProcessingEvent" = [ "Event" ];
+          "AudioScheduledSourceNode" = [ "AudioNode" "EventTarget" ];
+          "AudioStreamTrack" = [ "EventTarget" "MediaStreamTrack" ];
+          "AudioTrackList" = [ "EventTarget" ];
+          "AudioWorklet" = [ "Worklet" ];
+          "AudioWorkletGlobalScope" = [ "WorkletGlobalScope" ];
+          "AudioWorkletNode" = [ "AudioNode" "EventTarget" ];
+          "AuthenticatorAssertionResponse" = [ "AuthenticatorResponse" ];
+          "AuthenticatorAttestationResponse" = [ "AuthenticatorResponse" ];
+          "BaseAudioContext" = [ "EventTarget" ];
+          "BatteryManager" = [ "EventTarget" ];
+          "BeforeUnloadEvent" = [ "Event" ];
+          "BiquadFilterNode" = [ "AudioNode" "EventTarget" ];
+          "BlobEvent" = [ "Event" ];
+          "Bluetooth" = [ "EventTarget" ];
+          "BluetoothAdvertisingEvent" = [ "Event" ];
+          "BluetoothDevice" = [ "EventTarget" ];
+          "BluetoothPermissionResult" = [ "EventTarget" "PermissionStatus" ];
+          "BluetoothRemoteGattCharacteristic" = [ "EventTarget" ];
+          "BluetoothRemoteGattService" = [ "EventTarget" ];
+          "BroadcastChannel" = [ "EventTarget" ];
+          "CanvasCaptureMediaStream" = [ "EventTarget" "MediaStream" ];
+          "CanvasCaptureMediaStreamTrack" = [ "EventTarget" "MediaStreamTrack" ];
+          "CdataSection" = [ "CharacterData" "EventTarget" "Node" "Text" ];
+          "ChannelMergerNode" = [ "AudioNode" "EventTarget" ];
+          "ChannelSplitterNode" = [ "AudioNode" "EventTarget" ];
+          "CharacterData" = [ "EventTarget" "Node" ];
+          "ChromeWorker" = [ "EventTarget" "Worker" ];
+          "Clipboard" = [ "EventTarget" ];
+          "ClipboardEvent" = [ "Event" ];
+          "CloseEvent" = [ "Event" ];
+          "Comment" = [ "CharacterData" "EventTarget" "Node" ];
+          "CompositionEvent" = [ "Event" "UiEvent" ];
+          "ConstantSourceNode" = [ "AudioNode" "AudioScheduledSourceNode" "EventTarget" ];
+          "ConvolverNode" = [ "AudioNode" "EventTarget" ];
+          "CssAnimation" = [ "Animation" "EventTarget" ];
+          "CssConditionRule" = [ "CssGroupingRule" "CssRule" ];
+          "CssCounterStyleRule" = [ "CssRule" ];
+          "CssFontFaceRule" = [ "CssRule" ];
+          "CssFontFeatureValuesRule" = [ "CssRule" ];
+          "CssGroupingRule" = [ "CssRule" ];
+          "CssImportRule" = [ "CssRule" ];
+          "CssKeyframeRule" = [ "CssRule" ];
+          "CssKeyframesRule" = [ "CssRule" ];
+          "CssMediaRule" = [ "CssConditionRule" "CssGroupingRule" "CssRule" ];
+          "CssNamespaceRule" = [ "CssRule" ];
+          "CssPageRule" = [ "CssRule" ];
+          "CssStyleRule" = [ "CssRule" ];
+          "CssStyleSheet" = [ "StyleSheet" ];
+          "CssSupportsRule" = [ "CssConditionRule" "CssGroupingRule" "CssRule" ];
+          "CssTransition" = [ "Animation" "EventTarget" ];
+          "CustomEvent" = [ "Event" ];
+          "DedicatedWorkerGlobalScope" = [ "EventTarget" "WorkerGlobalScope" ];
+          "DelayNode" = [ "AudioNode" "EventTarget" ];
+          "DeviceLightEvent" = [ "Event" ];
+          "DeviceMotionEvent" = [ "Event" ];
+          "DeviceOrientationEvent" = [ "Event" ];
+          "DeviceProximityEvent" = [ "Event" ];
+          "Document" = [ "EventTarget" "Node" ];
+          "DocumentFragment" = [ "EventTarget" "Node" ];
+          "DocumentTimeline" = [ "AnimationTimeline" ];
+          "DocumentType" = [ "EventTarget" "Node" ];
+          "DomMatrix" = [ "DomMatrixReadOnly" ];
+          "DomPoint" = [ "DomPointReadOnly" ];
+          "DomRect" = [ "DomRectReadOnly" ];
+          "DomRequest" = [ "EventTarget" ];
+          "DragEvent" = [ "Event" "MouseEvent" "UiEvent" ];
+          "DynamicsCompressorNode" = [ "AudioNode" "EventTarget" ];
+          "Element" = [ "EventTarget" "Node" ];
+          "ErrorEvent" = [ "Event" ];
+          "EventSource" = [ "EventTarget" ];
+          "ExtendableEvent" = [ "Event" ];
+          "ExtendableMessageEvent" = [ "Event" "ExtendableEvent" ];
+          "FetchEvent" = [ "Event" "ExtendableEvent" ];
+          "FetchObserver" = [ "EventTarget" ];
+          "File" = [ "Blob" ];
+          "FileReader" = [ "EventTarget" ];
+          "FileSystemDirectoryEntry" = [ "FileSystemEntry" ];
+          "FileSystemDirectoryHandle" = [ "FileSystemHandle" ];
+          "FileSystemFileEntry" = [ "FileSystemEntry" ];
+          "FileSystemFileHandle" = [ "FileSystemHandle" ];
+          "FileSystemWritableFileStream" = [ "WritableStream" ];
+          "FocusEvent" = [ "Event" "UiEvent" ];
+          "FontFaceSet" = [ "EventTarget" ];
+          "FontFaceSetLoadEvent" = [ "Event" ];
+          "GainNode" = [ "AudioNode" "EventTarget" ];
+          "GamepadAxisMoveEvent" = [ "Event" "GamepadEvent" ];
+          "GamepadButtonEvent" = [ "Event" "GamepadEvent" ];
+          "GamepadEvent" = [ "Event" ];
+          "GpuDevice" = [ "EventTarget" ];
+          "GpuInternalError" = [ "GpuError" ];
+          "GpuOutOfMemoryError" = [ "GpuError" ];
+          "GpuPipelineError" = [ "DomException" ];
+          "GpuUncapturedErrorEvent" = [ "Event" ];
+          "GpuValidationError" = [ "GpuError" ];
+          "HashChangeEvent" = [ "Event" ];
+          "Hid" = [ "EventTarget" ];
+          "HidConnectionEvent" = [ "Event" ];
+          "HidDevice" = [ "EventTarget" ];
+          "HidInputReportEvent" = [ "Event" ];
+          "HtmlAnchorElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlAreaElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlAudioElement" = [ "Element" "EventTarget" "HtmlElement" "HtmlMediaElement" "Node" ];
+          "HtmlBaseElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlBodyElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlBrElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlButtonElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlCanvasElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlDListElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlDataElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlDataListElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlDetailsElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlDialogElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlDirectoryElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlDivElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlDocument" = [ "Document" "EventTarget" "Node" ];
+          "HtmlElement" = [ "Element" "EventTarget" "Node" ];
+          "HtmlEmbedElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlFieldSetElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlFontElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlFormControlsCollection" = [ "HtmlCollection" ];
+          "HtmlFormElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlFrameElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlFrameSetElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlHeadElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlHeadingElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlHrElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlHtmlElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlIFrameElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlImageElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlInputElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlLabelElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlLegendElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlLiElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlLinkElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlMapElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlMediaElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlMenuElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlMenuItemElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlMetaElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlMeterElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlModElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlOListElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlObjectElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlOptGroupElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlOptionElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlOptionsCollection" = [ "HtmlCollection" ];
+          "HtmlOutputElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlParagraphElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlParamElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlPictureElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlPreElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlProgressElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlQuoteElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlScriptElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlSelectElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlSlotElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlSourceElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlSpanElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlStyleElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlTableCaptionElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlTableCellElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlTableColElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlTableElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlTableRowElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlTableSectionElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlTemplateElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlTextAreaElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlTimeElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlTitleElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlTrackElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlUListElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlUnknownElement" = [ "Element" "EventTarget" "HtmlElement" "Node" ];
+          "HtmlVideoElement" = [ "Element" "EventTarget" "HtmlElement" "HtmlMediaElement" "Node" ];
+          "IdbCursorWithValue" = [ "IdbCursor" ];
+          "IdbDatabase" = [ "EventTarget" ];
+          "IdbFileHandle" = [ "EventTarget" ];
+          "IdbFileRequest" = [ "DomRequest" "EventTarget" ];
+          "IdbLocaleAwareKeyRange" = [ "IdbKeyRange" ];
+          "IdbMutableFile" = [ "EventTarget" ];
+          "IdbOpenDbRequest" = [ "EventTarget" "IdbRequest" ];
+          "IdbRequest" = [ "EventTarget" ];
+          "IdbTransaction" = [ "EventTarget" ];
+          "IdbVersionChangeEvent" = [ "Event" ];
+          "IirFilterNode" = [ "AudioNode" "EventTarget" ];
+          "ImageCaptureErrorEvent" = [ "Event" ];
+          "ImageTrack" = [ "EventTarget" ];
+          "InputEvent" = [ "Event" "UiEvent" ];
+          "KeyboardEvent" = [ "Event" "UiEvent" ];
+          "KeyframeEffect" = [ "AnimationEffect" ];
+          "LocalMediaStream" = [ "EventTarget" "MediaStream" ];
+          "MediaDevices" = [ "EventTarget" ];
+          "MediaElementAudioSourceNode" = [ "AudioNode" "EventTarget" ];
+          "MediaEncryptedEvent" = [ "Event" ];
+          "MediaKeyError" = [ "Event" ];
+          "MediaKeyMessageEvent" = [ "Event" ];
+          "MediaKeySession" = [ "EventTarget" ];
+          "MediaQueryList" = [ "EventTarget" ];
+          "MediaQueryListEvent" = [ "Event" ];
+          "MediaRecorder" = [ "EventTarget" ];
+          "MediaRecorderErrorEvent" = [ "Event" ];
+          "MediaSource" = [ "EventTarget" ];
+          "MediaStream" = [ "EventTarget" ];
+          "MediaStreamAudioDestinationNode" = [ "AudioNode" "EventTarget" ];
+          "MediaStreamAudioSourceNode" = [ "AudioNode" "EventTarget" ];
+          "MediaStreamEvent" = [ "Event" ];
+          "MediaStreamTrack" = [ "EventTarget" ];
+          "MediaStreamTrackEvent" = [ "Event" ];
+          "MediaStreamTrackGenerator" = [ "EventTarget" "MediaStreamTrack" ];
+          "MessageEvent" = [ "Event" ];
+          "MessagePort" = [ "EventTarget" ];
+          "MidiAccess" = [ "EventTarget" ];
+          "MidiConnectionEvent" = [ "Event" ];
+          "MidiInput" = [ "EventTarget" "MidiPort" ];
+          "MidiMessageEvent" = [ "Event" ];
+          "MidiOutput" = [ "EventTarget" "MidiPort" ];
+          "MidiPort" = [ "EventTarget" ];
+          "MouseEvent" = [ "Event" "UiEvent" ];
+          "MouseScrollEvent" = [ "Event" "MouseEvent" "UiEvent" ];
+          "MutationEvent" = [ "Event" ];
+          "NetworkInformation" = [ "EventTarget" ];
+          "Node" = [ "EventTarget" ];
+          "Notification" = [ "EventTarget" ];
+          "NotificationEvent" = [ "Event" "ExtendableEvent" ];
+          "OfflineAudioCompletionEvent" = [ "Event" ];
+          "OfflineAudioContext" = [ "BaseAudioContext" "EventTarget" ];
+          "OfflineResourceList" = [ "EventTarget" ];
+          "OffscreenCanvas" = [ "EventTarget" ];
+          "OscillatorNode" = [ "AudioNode" "AudioScheduledSourceNode" "EventTarget" ];
+          "PageTransitionEvent" = [ "Event" ];
+          "PaintWorkletGlobalScope" = [ "WorkletGlobalScope" ];
+          "PannerNode" = [ "AudioNode" "EventTarget" ];
+          "PaymentMethodChangeEvent" = [ "Event" "PaymentRequestUpdateEvent" ];
+          "PaymentRequestUpdateEvent" = [ "Event" ];
+          "Performance" = [ "EventTarget" ];
+          "PerformanceMark" = [ "PerformanceEntry" ];
+          "PerformanceMeasure" = [ "PerformanceEntry" ];
+          "PerformanceNavigationTiming" = [ "PerformanceEntry" "PerformanceResourceTiming" ];
+          "PerformanceResourceTiming" = [ "PerformanceEntry" ];
+          "PermissionStatus" = [ "EventTarget" ];
+          "PointerEvent" = [ "Event" "MouseEvent" "UiEvent" ];
+          "PopStateEvent" = [ "Event" ];
+          "PopupBlockedEvent" = [ "Event" ];
+          "PresentationAvailability" = [ "EventTarget" ];
+          "PresentationConnection" = [ "EventTarget" ];
+          "PresentationConnectionAvailableEvent" = [ "Event" ];
+          "PresentationConnectionCloseEvent" = [ "Event" ];
+          "PresentationConnectionList" = [ "EventTarget" ];
+          "PresentationRequest" = [ "EventTarget" ];
+          "ProcessingInstruction" = [ "CharacterData" "EventTarget" "Node" ];
+          "ProgressEvent" = [ "Event" ];
+          "PromiseRejectionEvent" = [ "Event" ];
+          "PublicKeyCredential" = [ "Credential" ];
+          "PushEvent" = [ "Event" "ExtendableEvent" ];
+          "RadioNodeList" = [ "NodeList" ];
+          "RtcDataChannel" = [ "EventTarget" ];
+          "RtcDataChannelEvent" = [ "Event" ];
+          "RtcPeerConnection" = [ "EventTarget" ];
+          "RtcPeerConnectionIceEvent" = [ "Event" ];
+          "RtcTrackEvent" = [ "Event" ];
+          "RtcdtmfSender" = [ "EventTarget" ];
+          "RtcdtmfToneChangeEvent" = [ "Event" ];
+          "Screen" = [ "EventTarget" ];
+          "ScreenOrientation" = [ "EventTarget" ];
+          "ScriptProcessorNode" = [ "AudioNode" "EventTarget" ];
+          "ScrollAreaEvent" = [ "Event" "UiEvent" ];
+          "SecurityPolicyViolationEvent" = [ "Event" ];
+          "Serial" = [ "EventTarget" ];
+          "SerialPort" = [ "EventTarget" ];
+          "ServiceWorker" = [ "EventTarget" ];
+          "ServiceWorkerContainer" = [ "EventTarget" ];
+          "ServiceWorkerGlobalScope" = [ "EventTarget" "WorkerGlobalScope" ];
+          "ServiceWorkerRegistration" = [ "EventTarget" ];
+          "ShadowRoot" = [ "DocumentFragment" "EventTarget" "Node" ];
+          "SharedWorker" = [ "EventTarget" ];
+          "SharedWorkerGlobalScope" = [ "EventTarget" "WorkerGlobalScope" ];
+          "SourceBuffer" = [ "EventTarget" ];
+          "SourceBufferList" = [ "EventTarget" ];
+          "SpeechRecognition" = [ "EventTarget" ];
+          "SpeechRecognitionError" = [ "Event" ];
+          "SpeechRecognitionEvent" = [ "Event" ];
+          "SpeechSynthesis" = [ "EventTarget" ];
+          "SpeechSynthesisErrorEvent" = [ "Event" "SpeechSynthesisEvent" ];
+          "SpeechSynthesisEvent" = [ "Event" ];
+          "SpeechSynthesisUtterance" = [ "EventTarget" ];
+          "StereoPannerNode" = [ "AudioNode" "EventTarget" ];
+          "StorageEvent" = [ "Event" ];
+          "SubmitEvent" = [ "Event" ];
+          "SvgAnimateElement" = [ "Element" "EventTarget" "Node" "SvgAnimationElement" "SvgElement" ];
+          "SvgAnimateMotionElement" = [ "Element" "EventTarget" "Node" "SvgAnimationElement" "SvgElement" ];
+          "SvgAnimateTransformElement" = [ "Element" "EventTarget" "Node" "SvgAnimationElement" "SvgElement" ];
+          "SvgAnimationElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgCircleElement" = [ "Element" "EventTarget" "Node" "SvgElement" "SvgGeometryElement" "SvgGraphicsElement" ];
+          "SvgClipPathElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgComponentTransferFunctionElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgDefsElement" = [ "Element" "EventTarget" "Node" "SvgElement" "SvgGraphicsElement" ];
+          "SvgDescElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgElement" = [ "Element" "EventTarget" "Node" ];
+          "SvgEllipseElement" = [ "Element" "EventTarget" "Node" "SvgElement" "SvgGeometryElement" "SvgGraphicsElement" ];
+          "SvgFilterElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgForeignObjectElement" = [ "Element" "EventTarget" "Node" "SvgElement" "SvgGraphicsElement" ];
+          "SvgGeometryElement" = [ "Element" "EventTarget" "Node" "SvgElement" "SvgGraphicsElement" ];
+          "SvgGradientElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgGraphicsElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgImageElement" = [ "Element" "EventTarget" "Node" "SvgElement" "SvgGraphicsElement" ];
+          "SvgLineElement" = [ "Element" "EventTarget" "Node" "SvgElement" "SvgGeometryElement" "SvgGraphicsElement" ];
+          "SvgLinearGradientElement" = [ "Element" "EventTarget" "Node" "SvgElement" "SvgGradientElement" ];
+          "SvgMarkerElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgMaskElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgMetadataElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgPathElement" = [ "Element" "EventTarget" "Node" "SvgElement" "SvgGeometryElement" "SvgGraphicsElement" ];
+          "SvgPathSegArcAbs" = [ "SvgPathSeg" ];
+          "SvgPathSegArcRel" = [ "SvgPathSeg" ];
+          "SvgPathSegClosePath" = [ "SvgPathSeg" ];
+          "SvgPathSegCurvetoCubicAbs" = [ "SvgPathSeg" ];
+          "SvgPathSegCurvetoCubicRel" = [ "SvgPathSeg" ];
+          "SvgPathSegCurvetoCubicSmoothAbs" = [ "SvgPathSeg" ];
+          "SvgPathSegCurvetoCubicSmoothRel" = [ "SvgPathSeg" ];
+          "SvgPathSegCurvetoQuadraticAbs" = [ "SvgPathSeg" ];
+          "SvgPathSegCurvetoQuadraticRel" = [ "SvgPathSeg" ];
+          "SvgPathSegCurvetoQuadraticSmoothAbs" = [ "SvgPathSeg" ];
+          "SvgPathSegCurvetoQuadraticSmoothRel" = [ "SvgPathSeg" ];
+          "SvgPathSegLinetoAbs" = [ "SvgPathSeg" ];
+          "SvgPathSegLinetoHorizontalAbs" = [ "SvgPathSeg" ];
+          "SvgPathSegLinetoHorizontalRel" = [ "SvgPathSeg" ];
+          "SvgPathSegLinetoRel" = [ "SvgPathSeg" ];
+          "SvgPathSegLinetoVerticalAbs" = [ "SvgPathSeg" ];
+          "SvgPathSegLinetoVerticalRel" = [ "SvgPathSeg" ];
+          "SvgPathSegMovetoAbs" = [ "SvgPathSeg" ];
+          "SvgPathSegMovetoRel" = [ "SvgPathSeg" ];
+          "SvgPatternElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgPolygonElement" = [ "Element" "EventTarget" "Node" "SvgElement" "SvgGeometryElement" "SvgGraphicsElement" ];
+          "SvgPolylineElement" = [ "Element" "EventTarget" "Node" "SvgElement" "SvgGeometryElement" "SvgGraphicsElement" ];
+          "SvgRadialGradientElement" = [ "Element" "EventTarget" "Node" "SvgElement" "SvgGradientElement" ];
+          "SvgRectElement" = [ "Element" "EventTarget" "Node" "SvgElement" "SvgGeometryElement" "SvgGraphicsElement" ];
+          "SvgScriptElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgSetElement" = [ "Element" "EventTarget" "Node" "SvgAnimationElement" "SvgElement" ];
+          "SvgStopElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgStyleElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgSwitchElement" = [ "Element" "EventTarget" "Node" "SvgElement" "SvgGraphicsElement" ];
+          "SvgSymbolElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgTextContentElement" = [ "Element" "EventTarget" "Node" "SvgElement" "SvgGraphicsElement" ];
+          "SvgTextElement" = [ "Element" "EventTarget" "Node" "SvgElement" "SvgGraphicsElement" "SvgTextContentElement" "SvgTextPositioningElement" ];
+          "SvgTextPathElement" = [ "Element" "EventTarget" "Node" "SvgElement" "SvgGraphicsElement" "SvgTextContentElement" ];
+          "SvgTextPositioningElement" = [ "Element" "EventTarget" "Node" "SvgElement" "SvgGraphicsElement" "SvgTextContentElement" ];
+          "SvgTitleElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgUseElement" = [ "Element" "EventTarget" "Node" "SvgElement" "SvgGraphicsElement" ];
+          "SvgViewElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgaElement" = [ "Element" "EventTarget" "Node" "SvgElement" "SvgGraphicsElement" ];
+          "SvgfeBlendElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgfeColorMatrixElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgfeComponentTransferElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgfeCompositeElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgfeConvolveMatrixElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgfeDiffuseLightingElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgfeDisplacementMapElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgfeDistantLightElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgfeDropShadowElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgfeFloodElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgfeFuncAElement" = [ "Element" "EventTarget" "Node" "SvgComponentTransferFunctionElement" "SvgElement" ];
+          "SvgfeFuncBElement" = [ "Element" "EventTarget" "Node" "SvgComponentTransferFunctionElement" "SvgElement" ];
+          "SvgfeFuncGElement" = [ "Element" "EventTarget" "Node" "SvgComponentTransferFunctionElement" "SvgElement" ];
+          "SvgfeFuncRElement" = [ "Element" "EventTarget" "Node" "SvgComponentTransferFunctionElement" "SvgElement" ];
+          "SvgfeGaussianBlurElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgfeImageElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgfeMergeElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgfeMergeNodeElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgfeMorphologyElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgfeOffsetElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgfePointLightElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgfeSpecularLightingElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgfeSpotLightElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgfeTileElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgfeTurbulenceElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvggElement" = [ "Element" "EventTarget" "Node" "SvgElement" "SvgGraphicsElement" ];
+          "SvgmPathElement" = [ "Element" "EventTarget" "Node" "SvgElement" ];
+          "SvgsvgElement" = [ "Element" "EventTarget" "Node" "SvgElement" "SvgGraphicsElement" ];
+          "SvgtSpanElement" = [ "Element" "EventTarget" "Node" "SvgElement" "SvgGraphicsElement" "SvgTextContentElement" "SvgTextPositioningElement" ];
+          "TaskController" = [ "AbortController" ];
+          "TaskPriorityChangeEvent" = [ "Event" ];
+          "TaskSignal" = [ "AbortSignal" "EventTarget" ];
+          "TcpServerSocket" = [ "EventTarget" ];
+          "TcpServerSocketEvent" = [ "Event" ];
+          "TcpSocket" = [ "EventTarget" ];
+          "TcpSocketErrorEvent" = [ "Event" ];
+          "TcpSocketEvent" = [ "Event" ];
+          "Text" = [ "CharacterData" "EventTarget" "Node" ];
+          "TextTrack" = [ "EventTarget" ];
+          "TextTrackCue" = [ "EventTarget" ];
+          "TextTrackList" = [ "EventTarget" ];
+          "TimeEvent" = [ "Event" ];
+          "TouchEvent" = [ "Event" "UiEvent" ];
+          "TrackEvent" = [ "Event" ];
+          "TransitionEvent" = [ "Event" ];
+          "UiEvent" = [ "Event" ];
+          "Usb" = [ "EventTarget" ];
+          "UsbConnectionEvent" = [ "Event" ];
+          "UsbPermissionResult" = [ "EventTarget" "PermissionStatus" ];
+          "UserProximityEvent" = [ "Event" ];
+          "ValueEvent" = [ "Event" ];
+          "VideoStreamTrack" = [ "EventTarget" "MediaStreamTrack" ];
+          "VideoTrackList" = [ "EventTarget" ];
+          "VrDisplay" = [ "EventTarget" ];
+          "VttCue" = [ "EventTarget" "TextTrackCue" ];
+          "WakeLockSentinel" = [ "EventTarget" ];
+          "WaveShaperNode" = [ "AudioNode" "EventTarget" ];
+          "WebGlContextEvent" = [ "Event" ];
+          "WebKitCssMatrix" = [ "DomMatrix" "DomMatrixReadOnly" ];
+          "WebSocket" = [ "EventTarget" ];
+          "WebTransportError" = [ "DomException" ];
+          "WebTransportReceiveStream" = [ "ReadableStream" ];
+          "WebTransportSendStream" = [ "WritableStream" ];
+          "WheelEvent" = [ "Event" "MouseEvent" "UiEvent" ];
+          "Window" = [ "EventTarget" ];
+          "WindowClient" = [ "Client" ];
+          "Worker" = [ "EventTarget" ];
+          "WorkerDebuggerGlobalScope" = [ "EventTarget" ];
+          "WorkerGlobalScope" = [ "EventTarget" ];
+          "XmlDocument" = [ "Document" "EventTarget" "Node" ];
+          "XmlHttpRequest" = [ "EventTarget" "XmlHttpRequestEventTarget" ];
+          "XmlHttpRequestEventTarget" = [ "EventTarget" ];
+          "XmlHttpRequestUpload" = [ "EventTarget" "XmlHttpRequestEventTarget" ];
+          "XrBoundedReferenceSpace" = [ "EventTarget" "XrReferenceSpace" "XrSpace" ];
+          "XrInputSourceEvent" = [ "Event" ];
+          "XrInputSourcesChangeEvent" = [ "Event" ];
+          "XrJointPose" = [ "XrPose" ];
+          "XrJointSpace" = [ "EventTarget" "XrSpace" ];
+          "XrLayer" = [ "EventTarget" ];
+          "XrPermissionStatus" = [ "EventTarget" "PermissionStatus" ];
+          "XrReferenceSpace" = [ "EventTarget" "XrSpace" ];
+          "XrReferenceSpaceEvent" = [ "Event" ];
+          "XrSession" = [ "EventTarget" ];
+          "XrSessionEvent" = [ "Event" ];
+          "XrSpace" = [ "EventTarget" ];
+          "XrSystem" = [ "EventTarget" ];
+          "XrViewerPose" = [ "XrPose" ];
+          "XrWebGlLayer" = [ "EventTarget" "XrLayer" ];
+        };
+        resolvedDefaultFeatures = [ "Crypto" "EventTarget" "Window" ];
+      };
+      "webpki" = rec {
+        crateName = "webpki";
+        version = "0.22.4";
+        edition = "2018";
+        sha256 = "0lwv7jdlcqjjqqhxcrapnyk5bz4lvr12q444b50gzl3krsjswqzd";
+        authors = [
+          "Brian Smith <brian@briansmith.org>"
+        ];
+        dependencies = [
+          {
+            name = "ring";
+            packageId = "ring 0.17.5";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "untrusted";
+            packageId = "untrusted 0.9.0";
+          }
+        ];
+        features = {
+          "alloc" = [ "ring/alloc" ];
+          "std" = [ "alloc" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "std" ];
+      };
+      "winapi" = rec {
+        crateName = "winapi";
+        version = "0.3.9";
+        edition = "2015";
+        sha256 = "06gl025x418lchw1wxj64ycr7gha83m44cjr5sarhynd9xkrm0sw";
+        authors = [
+          "Peter Atashian <retep998@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "winapi-i686-pc-windows-gnu";
+            packageId = "winapi-i686-pc-windows-gnu";
+            target = { target, features }: (pkgs.rust.lib.toRustTarget stdenv.hostPlatform == "i686-pc-windows-gnu");
+          }
+          {
+            name = "winapi-x86_64-pc-windows-gnu";
+            packageId = "winapi-x86_64-pc-windows-gnu";
+            target = { target, features }: (pkgs.rust.lib.toRustTarget stdenv.hostPlatform == "x86_64-pc-windows-gnu");
+          }
+        ];
+        features = {
+          "debug" = [ "impl-debug" ];
+        };
+        resolvedDefaultFeatures = [ "consoleapi" "errhandlingapi" "fileapi" "handleapi" "knownfolders" "ntsecapi" "objbase" "processenv" "shlobj" "winbase" "winerror" "ws2ipdef" "ws2tcpip" "wtypesbase" ];
+      };
+      "winapi-i686-pc-windows-gnu" = rec {
+        crateName = "winapi-i686-pc-windows-gnu";
+        version = "0.4.0";
+        edition = "2015";
+        sha256 = "1dmpa6mvcvzz16zg6d5vrfy4bxgg541wxrcip7cnshi06v38ffxc";
+        authors = [
+          "Peter Atashian <retep998@gmail.com>"
+        ];
+
+      };
+      "winapi-x86_64-pc-windows-gnu" = rec {
+        crateName = "winapi-x86_64-pc-windows-gnu";
+        version = "0.4.0";
+        edition = "2015";
+        sha256 = "0gqq64czqb64kskjryj8isp62m2sgvx25yyj3kpc2myh85w24bki";
+        authors = [
+          "Peter Atashian <retep998@gmail.com>"
+        ];
+
+      };
+      "windows-core" = rec {
+        crateName = "windows-core";
+        version = "0.51.1";
+        edition = "2021";
+        sha256 = "0r1f57hsshsghjyc7ypp2s0i78f7b1vr93w68sdb8baxyf2czy7i";
+        authors = [
+          "Microsoft"
+        ];
+        dependencies = [
+          {
+            name = "windows-targets";
+            packageId = "windows-targets";
+          }
+        ];
+        features = { };
+        resolvedDefaultFeatures = [ "default" ];
+      };
+      "windows-sys" = rec {
+        crateName = "windows-sys";
+        version = "0.48.0";
+        edition = "2018";
+        sha256 = "1aan23v5gs7gya1lc46hqn9mdh8yph3fhxmhxlw36pn6pqc28zb7";
+        authors = [
+          "Microsoft"
+        ];
+        dependencies = [
+          {
+            name = "windows-targets";
+            packageId = "windows-targets";
+          }
+        ];
+        features = {
+          "Wdk_System" = [ "Wdk" ];
+          "Wdk_System_OfflineRegistry" = [ "Wdk_System" ];
+          "Win32_Data" = [ "Win32" ];
+          "Win32_Data_HtmlHelp" = [ "Win32_Data" ];
+          "Win32_Data_RightsManagement" = [ "Win32_Data" ];
+          "Win32_Data_Xml" = [ "Win32_Data" ];
+          "Win32_Data_Xml_MsXml" = [ "Win32_Data_Xml" ];
+          "Win32_Data_Xml_XmlLite" = [ "Win32_Data_Xml" ];
+          "Win32_Devices" = [ "Win32" ];
+          "Win32_Devices_AllJoyn" = [ "Win32_Devices" ];
+          "Win32_Devices_BiometricFramework" = [ "Win32_Devices" ];
+          "Win32_Devices_Bluetooth" = [ "Win32_Devices" ];
+          "Win32_Devices_Communication" = [ "Win32_Devices" ];
+          "Win32_Devices_DeviceAccess" = [ "Win32_Devices" ];
+          "Win32_Devices_DeviceAndDriverInstallation" = [ "Win32_Devices" ];
+          "Win32_Devices_DeviceQuery" = [ "Win32_Devices" ];
+          "Win32_Devices_Display" = [ "Win32_Devices" ];
+          "Win32_Devices_Enumeration" = [ "Win32_Devices" ];
+          "Win32_Devices_Enumeration_Pnp" = [ "Win32_Devices_Enumeration" ];
+          "Win32_Devices_Fax" = [ "Win32_Devices" ];
+          "Win32_Devices_FunctionDiscovery" = [ "Win32_Devices" ];
+          "Win32_Devices_Geolocation" = [ "Win32_Devices" ];
+          "Win32_Devices_HumanInterfaceDevice" = [ "Win32_Devices" ];
+          "Win32_Devices_ImageAcquisition" = [ "Win32_Devices" ];
+          "Win32_Devices_PortableDevices" = [ "Win32_Devices" ];
+          "Win32_Devices_Properties" = [ "Win32_Devices" ];
+          "Win32_Devices_Pwm" = [ "Win32_Devices" ];
+          "Win32_Devices_Sensors" = [ "Win32_Devices" ];
+          "Win32_Devices_SerialCommunication" = [ "Win32_Devices" ];
+          "Win32_Devices_Tapi" = [ "Win32_Devices" ];
+          "Win32_Devices_Usb" = [ "Win32_Devices" ];
+          "Win32_Devices_WebServicesOnDevices" = [ "Win32_Devices" ];
+          "Win32_Foundation" = [ "Win32" ];
+          "Win32_Gaming" = [ "Win32" ];
+          "Win32_Globalization" = [ "Win32" ];
+          "Win32_Graphics" = [ "Win32" ];
+          "Win32_Graphics_Dwm" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Gdi" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Hlsl" = [ "Win32_Graphics" ];
+          "Win32_Graphics_OpenGL" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Printing" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Printing_PrintTicket" = [ "Win32_Graphics_Printing" ];
+          "Win32_Management" = [ "Win32" ];
+          "Win32_Management_MobileDeviceManagementRegistration" = [ "Win32_Management" ];
+          "Win32_Media" = [ "Win32" ];
+          "Win32_Media_Audio" = [ "Win32_Media" ];
+          "Win32_Media_Audio_Apo" = [ "Win32_Media_Audio" ];
+          "Win32_Media_Audio_DirectMusic" = [ "Win32_Media_Audio" ];
+          "Win32_Media_Audio_Endpoints" = [ "Win32_Media_Audio" ];
+          "Win32_Media_Audio_XAudio2" = [ "Win32_Media_Audio" ];
+          "Win32_Media_DeviceManager" = [ "Win32_Media" ];
+          "Win32_Media_DxMediaObjects" = [ "Win32_Media" ];
+          "Win32_Media_KernelStreaming" = [ "Win32_Media" ];
+          "Win32_Media_LibrarySharingServices" = [ "Win32_Media" ];
+          "Win32_Media_MediaPlayer" = [ "Win32_Media" ];
+          "Win32_Media_Multimedia" = [ "Win32_Media" ];
+          "Win32_Media_Speech" = [ "Win32_Media" ];
+          "Win32_Media_Streaming" = [ "Win32_Media" ];
+          "Win32_Media_WindowsMediaFormat" = [ "Win32_Media" ];
+          "Win32_NetworkManagement" = [ "Win32" ];
+          "Win32_NetworkManagement_Dhcp" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_Dns" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_InternetConnectionWizard" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_IpHelper" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_MobileBroadband" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_Multicast" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_Ndis" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_NetBios" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_NetManagement" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_NetShell" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_NetworkDiagnosticsFramework" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_NetworkPolicyServer" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_P2P" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_QoS" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_Rras" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_Snmp" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WNet" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WebDav" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WiFi" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WindowsConnectNow" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WindowsConnectionManager" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WindowsFilteringPlatform" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WindowsFirewall" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WindowsNetworkVirtualization" = [ "Win32_NetworkManagement" ];
+          "Win32_Networking" = [ "Win32" ];
+          "Win32_Networking_ActiveDirectory" = [ "Win32_Networking" ];
+          "Win32_Networking_BackgroundIntelligentTransferService" = [ "Win32_Networking" ];
+          "Win32_Networking_Clustering" = [ "Win32_Networking" ];
+          "Win32_Networking_HttpServer" = [ "Win32_Networking" ];
+          "Win32_Networking_Ldap" = [ "Win32_Networking" ];
+          "Win32_Networking_NetworkListManager" = [ "Win32_Networking" ];
+          "Win32_Networking_RemoteDifferentialCompression" = [ "Win32_Networking" ];
+          "Win32_Networking_WebSocket" = [ "Win32_Networking" ];
+          "Win32_Networking_WinHttp" = [ "Win32_Networking" ];
+          "Win32_Networking_WinInet" = [ "Win32_Networking" ];
+          "Win32_Networking_WinSock" = [ "Win32_Networking" ];
+          "Win32_Networking_WindowsWebServices" = [ "Win32_Networking" ];
+          "Win32_Security" = [ "Win32" ];
+          "Win32_Security_AppLocker" = [ "Win32_Security" ];
+          "Win32_Security_Authentication" = [ "Win32_Security" ];
+          "Win32_Security_Authentication_Identity" = [ "Win32_Security_Authentication" ];
+          "Win32_Security_Authentication_Identity_Provider" = [ "Win32_Security_Authentication_Identity" ];
+          "Win32_Security_Authorization" = [ "Win32_Security" ];
+          "Win32_Security_Authorization_UI" = [ "Win32_Security_Authorization" ];
+          "Win32_Security_ConfigurationSnapin" = [ "Win32_Security" ];
+          "Win32_Security_Credentials" = [ "Win32_Security" ];
+          "Win32_Security_Cryptography" = [ "Win32_Security" ];
+          "Win32_Security_Cryptography_Catalog" = [ "Win32_Security_Cryptography" ];
+          "Win32_Security_Cryptography_Certificates" = [ "Win32_Security_Cryptography" ];
+          "Win32_Security_Cryptography_Sip" = [ "Win32_Security_Cryptography" ];
+          "Win32_Security_Cryptography_UI" = [ "Win32_Security_Cryptography" ];
+          "Win32_Security_DiagnosticDataQuery" = [ "Win32_Security" ];
+          "Win32_Security_DirectoryServices" = [ "Win32_Security" ];
+          "Win32_Security_EnterpriseData" = [ "Win32_Security" ];
+          "Win32_Security_ExtensibleAuthenticationProtocol" = [ "Win32_Security" ];
+          "Win32_Security_Isolation" = [ "Win32_Security" ];
+          "Win32_Security_LicenseProtection" = [ "Win32_Security" ];
+          "Win32_Security_NetworkAccessProtection" = [ "Win32_Security" ];
+          "Win32_Security_Tpm" = [ "Win32_Security" ];
+          "Win32_Security_WinTrust" = [ "Win32_Security" ];
+          "Win32_Security_WinWlx" = [ "Win32_Security" ];
+          "Win32_Storage" = [ "Win32" ];
+          "Win32_Storage_Cabinets" = [ "Win32_Storage" ];
+          "Win32_Storage_CloudFilters" = [ "Win32_Storage" ];
+          "Win32_Storage_Compression" = [ "Win32_Storage" ];
+          "Win32_Storage_DataDeduplication" = [ "Win32_Storage" ];
+          "Win32_Storage_DistributedFileSystem" = [ "Win32_Storage" ];
+          "Win32_Storage_EnhancedStorage" = [ "Win32_Storage" ];
+          "Win32_Storage_FileHistory" = [ "Win32_Storage" ];
+          "Win32_Storage_FileServerResourceManager" = [ "Win32_Storage" ];
+          "Win32_Storage_FileSystem" = [ "Win32_Storage" ];
+          "Win32_Storage_Imapi" = [ "Win32_Storage" ];
+          "Win32_Storage_IndexServer" = [ "Win32_Storage" ];
+          "Win32_Storage_InstallableFileSystems" = [ "Win32_Storage" ];
+          "Win32_Storage_IscsiDisc" = [ "Win32_Storage" ];
+          "Win32_Storage_Jet" = [ "Win32_Storage" ];
+          "Win32_Storage_OfflineFiles" = [ "Win32_Storage" ];
+          "Win32_Storage_OperationRecorder" = [ "Win32_Storage" ];
+          "Win32_Storage_Packaging" = [ "Win32_Storage" ];
+          "Win32_Storage_Packaging_Appx" = [ "Win32_Storage_Packaging" ];
+          "Win32_Storage_Packaging_Opc" = [ "Win32_Storage_Packaging" ];
+          "Win32_Storage_ProjectedFileSystem" = [ "Win32_Storage" ];
+          "Win32_Storage_StructuredStorage" = [ "Win32_Storage" ];
+          "Win32_Storage_Vhd" = [ "Win32_Storage" ];
+          "Win32_Storage_VirtualDiskService" = [ "Win32_Storage" ];
+          "Win32_Storage_Vss" = [ "Win32_Storage" ];
+          "Win32_Storage_Xps" = [ "Win32_Storage" ];
+          "Win32_Storage_Xps_Printing" = [ "Win32_Storage_Xps" ];
+          "Win32_System" = [ "Win32" ];
+          "Win32_System_AddressBook" = [ "Win32_System" ];
+          "Win32_System_Antimalware" = [ "Win32_System" ];
+          "Win32_System_ApplicationInstallationAndServicing" = [ "Win32_System" ];
+          "Win32_System_ApplicationVerifier" = [ "Win32_System" ];
+          "Win32_System_AssessmentTool" = [ "Win32_System" ];
+          "Win32_System_ClrHosting" = [ "Win32_System" ];
+          "Win32_System_Com" = [ "Win32_System" ];
+          "Win32_System_Com_CallObj" = [ "Win32_System_Com" ];
+          "Win32_System_Com_ChannelCredentials" = [ "Win32_System_Com" ];
+          "Win32_System_Com_Events" = [ "Win32_System_Com" ];
+          "Win32_System_Com_Marshal" = [ "Win32_System_Com" ];
+          "Win32_System_Com_StructuredStorage" = [ "Win32_System_Com" ];
+          "Win32_System_Com_UI" = [ "Win32_System_Com" ];
+          "Win32_System_Com_Urlmon" = [ "Win32_System_Com" ];
+          "Win32_System_ComponentServices" = [ "Win32_System" ];
+          "Win32_System_Console" = [ "Win32_System" ];
+          "Win32_System_Contacts" = [ "Win32_System" ];
+          "Win32_System_CorrelationVector" = [ "Win32_System" ];
+          "Win32_System_DataExchange" = [ "Win32_System" ];
+          "Win32_System_DeploymentServices" = [ "Win32_System" ];
+          "Win32_System_DesktopSharing" = [ "Win32_System" ];
+          "Win32_System_DeveloperLicensing" = [ "Win32_System" ];
+          "Win32_System_Diagnostics" = [ "Win32_System" ];
+          "Win32_System_Diagnostics_Ceip" = [ "Win32_System_Diagnostics" ];
+          "Win32_System_Diagnostics_ClrProfiling" = [ "Win32_System_Diagnostics" ];
+          "Win32_System_Diagnostics_Debug" = [ "Win32_System_Diagnostics" ];
+          "Win32_System_Diagnostics_Debug_ActiveScript" = [ "Win32_System_Diagnostics_Debug" ];
+          "Win32_System_Diagnostics_Debug_Extensions" = [ "Win32_System_Diagnostics_Debug" ];
+          "Win32_System_Diagnostics_Etw" = [ "Win32_System_Diagnostics" ];
+          "Win32_System_Diagnostics_ProcessSnapshotting" = [ "Win32_System_Diagnostics" ];
+          "Win32_System_Diagnostics_ToolHelp" = [ "Win32_System_Diagnostics" ];
+          "Win32_System_DistributedTransactionCoordinator" = [ "Win32_System" ];
+          "Win32_System_Environment" = [ "Win32_System" ];
+          "Win32_System_ErrorReporting" = [ "Win32_System" ];
+          "Win32_System_EventCollector" = [ "Win32_System" ];
+          "Win32_System_EventLog" = [ "Win32_System" ];
+          "Win32_System_EventNotificationService" = [ "Win32_System" ];
+          "Win32_System_GroupPolicy" = [ "Win32_System" ];
+          "Win32_System_HostCompute" = [ "Win32_System" ];
+          "Win32_System_HostComputeNetwork" = [ "Win32_System" ];
+          "Win32_System_HostComputeSystem" = [ "Win32_System" ];
+          "Win32_System_Hypervisor" = [ "Win32_System" ];
+          "Win32_System_IO" = [ "Win32_System" ];
+          "Win32_System_Iis" = [ "Win32_System" ];
+          "Win32_System_Ioctl" = [ "Win32_System" ];
+          "Win32_System_JobObjects" = [ "Win32_System" ];
+          "Win32_System_Js" = [ "Win32_System" ];
+          "Win32_System_Kernel" = [ "Win32_System" ];
+          "Win32_System_LibraryLoader" = [ "Win32_System" ];
+          "Win32_System_Mailslots" = [ "Win32_System" ];
+          "Win32_System_Mapi" = [ "Win32_System" ];
+          "Win32_System_Memory" = [ "Win32_System" ];
+          "Win32_System_Memory_NonVolatile" = [ "Win32_System_Memory" ];
+          "Win32_System_MessageQueuing" = [ "Win32_System" ];
+          "Win32_System_MixedReality" = [ "Win32_System" ];
+          "Win32_System_Mmc" = [ "Win32_System" ];
+          "Win32_System_Ole" = [ "Win32_System" ];
+          "Win32_System_ParentalControls" = [ "Win32_System" ];
+          "Win32_System_PasswordManagement" = [ "Win32_System" ];
+          "Win32_System_Performance" = [ "Win32_System" ];
+          "Win32_System_Performance_HardwareCounterProfiling" = [ "Win32_System_Performance" ];
+          "Win32_System_Pipes" = [ "Win32_System" ];
+          "Win32_System_Power" = [ "Win32_System" ];
+          "Win32_System_ProcessStatus" = [ "Win32_System" ];
+          "Win32_System_RealTimeCommunications" = [ "Win32_System" ];
+          "Win32_System_Recovery" = [ "Win32_System" ];
+          "Win32_System_Registry" = [ "Win32_System" ];
+          "Win32_System_RemoteAssistance" = [ "Win32_System" ];
+          "Win32_System_RemoteDesktop" = [ "Win32_System" ];
+          "Win32_System_RemoteManagement" = [ "Win32_System" ];
+          "Win32_System_RestartManager" = [ "Win32_System" ];
+          "Win32_System_Restore" = [ "Win32_System" ];
+          "Win32_System_Rpc" = [ "Win32_System" ];
+          "Win32_System_Search" = [ "Win32_System" ];
+          "Win32_System_Search_Common" = [ "Win32_System_Search" ];
+          "Win32_System_SecurityCenter" = [ "Win32_System" ];
+          "Win32_System_ServerBackup" = [ "Win32_System" ];
+          "Win32_System_Services" = [ "Win32_System" ];
+          "Win32_System_SettingsManagementInfrastructure" = [ "Win32_System" ];
+          "Win32_System_SetupAndMigration" = [ "Win32_System" ];
+          "Win32_System_Shutdown" = [ "Win32_System" ];
+          "Win32_System_StationsAndDesktops" = [ "Win32_System" ];
+          "Win32_System_SubsystemForLinux" = [ "Win32_System" ];
+          "Win32_System_SystemInformation" = [ "Win32_System" ];
+          "Win32_System_SystemServices" = [ "Win32_System" ];
+          "Win32_System_TaskScheduler" = [ "Win32_System" ];
+          "Win32_System_Threading" = [ "Win32_System" ];
+          "Win32_System_Time" = [ "Win32_System" ];
+          "Win32_System_TpmBaseServices" = [ "Win32_System" ];
+          "Win32_System_UpdateAgent" = [ "Win32_System" ];
+          "Win32_System_UpdateAssessment" = [ "Win32_System" ];
+          "Win32_System_UserAccessLogging" = [ "Win32_System" ];
+          "Win32_System_VirtualDosMachines" = [ "Win32_System" ];
+          "Win32_System_WindowsProgramming" = [ "Win32_System" ];
+          "Win32_System_WindowsSync" = [ "Win32_System" ];
+          "Win32_System_Wmi" = [ "Win32_System" ];
+          "Win32_UI" = [ "Win32" ];
+          "Win32_UI_Accessibility" = [ "Win32_UI" ];
+          "Win32_UI_Animation" = [ "Win32_UI" ];
+          "Win32_UI_ColorSystem" = [ "Win32_UI" ];
+          "Win32_UI_Controls" = [ "Win32_UI" ];
+          "Win32_UI_Controls_Dialogs" = [ "Win32_UI_Controls" ];
+          "Win32_UI_Controls_RichEdit" = [ "Win32_UI_Controls" ];
+          "Win32_UI_HiDpi" = [ "Win32_UI" ];
+          "Win32_UI_Input" = [ "Win32_UI" ];
+          "Win32_UI_Input_Ime" = [ "Win32_UI_Input" ];
+          "Win32_UI_Input_Ink" = [ "Win32_UI_Input" ];
+          "Win32_UI_Input_KeyboardAndMouse" = [ "Win32_UI_Input" ];
+          "Win32_UI_Input_Pointer" = [ "Win32_UI_Input" ];
+          "Win32_UI_Input_Radial" = [ "Win32_UI_Input" ];
+          "Win32_UI_Input_Touch" = [ "Win32_UI_Input" ];
+          "Win32_UI_Input_XboxController" = [ "Win32_UI_Input" ];
+          "Win32_UI_InteractionContext" = [ "Win32_UI" ];
+          "Win32_UI_LegacyWindowsEnvironmentFeatures" = [ "Win32_UI" ];
+          "Win32_UI_Magnification" = [ "Win32_UI" ];
+          "Win32_UI_Notifications" = [ "Win32_UI" ];
+          "Win32_UI_Ribbon" = [ "Win32_UI" ];
+          "Win32_UI_Shell" = [ "Win32_UI" ];
+          "Win32_UI_Shell_Common" = [ "Win32_UI_Shell" ];
+          "Win32_UI_Shell_PropertiesSystem" = [ "Win32_UI_Shell" ];
+          "Win32_UI_TabletPC" = [ "Win32_UI" ];
+          "Win32_UI_TextServices" = [ "Win32_UI" ];
+          "Win32_UI_WindowsAndMessaging" = [ "Win32_UI" ];
+          "Win32_UI_Wpf" = [ "Win32_UI" ];
+          "Win32_Web" = [ "Win32" ];
+          "Win32_Web_InternetExplorer" = [ "Win32_Web" ];
+        };
+        resolvedDefaultFeatures = [ "Win32" "Win32_Foundation" "Win32_Networking" "Win32_Networking_WinSock" "Win32_Security" "Win32_Security_Authentication" "Win32_Security_Authentication_Identity" "Win32_Security_Credentials" "Win32_Security_Cryptography" "Win32_Storage" "Win32_Storage_FileSystem" "Win32_System" "Win32_System_Console" "Win32_System_Diagnostics" "Win32_System_Diagnostics_Debug" "Win32_System_IO" "Win32_System_Memory" "Win32_System_Pipes" "Win32_System_SystemInformation" "Win32_System_SystemServices" "Win32_System_Threading" "Win32_System_WindowsProgramming" "default" ];
+      };
+      "windows-targets" = rec {
+        crateName = "windows-targets";
+        version = "0.48.5";
+        edition = "2018";
+        sha256 = "034ljxqshifs1lan89xwpcy1hp0lhdh4b5n0d2z4fwjx2piacbws";
+        authors = [
+          "Microsoft"
+        ];
+        dependencies = [
+          {
+            name = "windows_aarch64_gnullvm";
+            packageId = "windows_aarch64_gnullvm";
+            target = { target, features }: (pkgs.rust.lib.toRustTarget stdenv.hostPlatform == "aarch64-pc-windows-gnullvm");
+          }
+          {
+            name = "windows_aarch64_msvc";
+            packageId = "windows_aarch64_msvc";
+            target = { target, features }: (("aarch64" == target."arch" or null) && ("msvc" == target."env" or null) && (!(target."windows_raw_dylib" or false)));
+          }
+          {
+            name = "windows_i686_gnu";
+            packageId = "windows_i686_gnu";
+            target = { target, features }: (("x86" == target."arch" or null) && ("gnu" == target."env" or null) && (!(target."windows_raw_dylib" or false)));
+          }
+          {
+            name = "windows_i686_msvc";
+            packageId = "windows_i686_msvc";
+            target = { target, features }: (("x86" == target."arch" or null) && ("msvc" == target."env" or null) && (!(target."windows_raw_dylib" or false)));
+          }
+          {
+            name = "windows_x86_64_gnu";
+            packageId = "windows_x86_64_gnu";
+            target = { target, features }: (("x86_64" == target."arch" or null) && ("gnu" == target."env" or null) && (!("llvm" == target."abi" or null)) && (!(target."windows_raw_dylib" or false)));
+          }
+          {
+            name = "windows_x86_64_gnullvm";
+            packageId = "windows_x86_64_gnullvm";
+            target = { target, features }: (pkgs.rust.lib.toRustTarget stdenv.hostPlatform == "x86_64-pc-windows-gnullvm");
+          }
+          {
+            name = "windows_x86_64_msvc";
+            packageId = "windows_x86_64_msvc";
+            target = { target, features }: (("x86_64" == target."arch" or null) && ("msvc" == target."env" or null) && (!(target."windows_raw_dylib" or false)));
+          }
+        ];
+
+      };
+      "windows_aarch64_gnullvm" = rec {
+        crateName = "windows_aarch64_gnullvm";
+        version = "0.48.5";
+        edition = "2018";
+        sha256 = "1n05v7qblg1ci3i567inc7xrkmywczxrs1z3lj3rkkxw18py6f1b";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "windows_aarch64_msvc" = rec {
+        crateName = "windows_aarch64_msvc";
+        version = "0.48.5";
+        edition = "2018";
+        sha256 = "1g5l4ry968p73g6bg6jgyvy9lb8fyhcs54067yzxpcpkf44k2dfw";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "windows_i686_gnu" = rec {
+        crateName = "windows_i686_gnu";
+        version = "0.48.5";
+        edition = "2018";
+        sha256 = "0gklnglwd9ilqx7ac3cn8hbhkraqisd0n83jxzf9837nvvkiand7";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "windows_i686_msvc" = rec {
+        crateName = "windows_i686_msvc";
+        version = "0.48.5";
+        edition = "2018";
+        sha256 = "01m4rik437dl9rdf0ndnm2syh10hizvq0dajdkv2fjqcywrw4mcg";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "windows_x86_64_gnu" = rec {
+        crateName = "windows_x86_64_gnu";
+        version = "0.48.5";
+        edition = "2018";
+        sha256 = "13kiqqcvz2vnyxzydjh73hwgigsdr2z1xpzx313kxll34nyhmm2k";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "windows_x86_64_gnullvm" = rec {
+        crateName = "windows_x86_64_gnullvm";
+        version = "0.48.5";
+        edition = "2018";
+        sha256 = "1k24810wfbgz8k48c2yknqjmiigmql6kk3knmddkv8k8g1v54yqb";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "windows_x86_64_msvc" = rec {
+        crateName = "windows_x86_64_msvc";
+        version = "0.48.5";
+        edition = "2018";
+        sha256 = "0f4mdp895kkjh9zv8dxvn4pc10xr7839lf5pa9l0193i2pkgr57d";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "xml-rs" = rec {
+        crateName = "xml-rs";
+        version = "0.8.19";
+        edition = "2021";
+        crateBin = [ ];
+        sha256 = "0nnpvk3fv32hgh7vs9gbg2swmzxx5yz73f4b7rak7q39q2x9rjqg";
+        libName = "xml";
+        authors = [
+          "Vladimir Matveev <vmatveev@citrine.cc>"
+        ];
+
+      };
+      "zeroize" = rec {
+        crateName = "zeroize";
+        version = "1.6.0";
+        edition = "2021";
+        sha256 = "1ndar43r58zbmasjhrhgas168vxb4i0rwbkcnszhjybwpbqmc29a";
+        authors = [
+          "The RustCrypto Project Developers"
+        ];
+        features = {
+          "default" = [ "alloc" ];
+          "derive" = [ "zeroize_derive" ];
+          "serde" = [ "dep:serde" ];
+          "std" = [ "alloc" ];
+          "zeroize_derive" = [ "dep:zeroize_derive" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" ];
+      };
+      "zstd" = rec {
+        crateName = "zstd";
+        version = "0.9.2+zstd.1.5.1";
+        edition = "2018";
+        sha256 = "0m5aik2jy2w1g68i4isa0c3gq9a7avq9abgjfjbc6f60yqdym413";
+        authors = [
+          "Alexandre Bury <alexandre.bury@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "zstd-safe";
+            packageId = "zstd-safe";
+            usesDefaultFeatures = false;
+            features = [ "std" ];
+          }
+        ];
+        features = {
+          "arrays" = [ "zstd-safe/arrays" ];
+          "bindgen" = [ "zstd-safe/bindgen" ];
+          "debug" = [ "zstd-safe/debug" ];
+          "default" = [ "legacy" "arrays" ];
+          "experimental" = [ "zstd-safe/experimental" ];
+          "legacy" = [ "zstd-safe/legacy" ];
+          "no_asm" = [ "zstd-safe/no_asm" ];
+          "pkg-config" = [ "zstd-safe/pkg-config" ];
+          "thin" = [ "zstd-safe/thin" ];
+          "zstdmt" = [ "zstd-safe/zstdmt" ];
+        };
+        resolvedDefaultFeatures = [ "arrays" "default" "legacy" ];
+      };
+      "zstd-safe" = rec {
+        crateName = "zstd-safe";
+        version = "4.1.3+zstd.1.5.1";
+        edition = "2018";
+        sha256 = "0yfvqzzkbj871f2vaikal5rm2gf60p1mdzp3jk3w5hmkkywq37g9";
+        authors = [
+          "Alexandre Bury <alexandre.bury@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+          }
+          {
+            name = "zstd-sys";
+            packageId = "zstd-sys";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "bindgen" = [ "zstd-sys/bindgen" ];
+          "debug" = [ "zstd-sys/debug" ];
+          "default" = [ "legacy" "arrays" ];
+          "experimental" = [ "zstd-sys/experimental" ];
+          "legacy" = [ "zstd-sys/legacy" ];
+          "no_asm" = [ "zstd-sys/no_asm" ];
+          "pkg-config" = [ "zstd-sys/pkg-config" ];
+          "std" = [ "zstd-sys/std" ];
+          "thin" = [ "zstd-sys/thin" ];
+          "zstdmt" = [ "zstd-sys/zstdmt" ];
+        };
+        resolvedDefaultFeatures = [ "arrays" "legacy" "std" ];
+      };
+      "zstd-sys" = rec {
+        crateName = "zstd-sys";
+        version = "1.6.2+zstd.1.5.1";
+        edition = "2018";
+        links = "zstd";
+        sha256 = "17xcr0mw8ps9hlc8m0dzj7yd52lb9r9ic9fbpxa4994yilj2zbrd";
+        authors = [
+          "Alexandre Bury <alexandre.bury@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "cc";
+            packageId = "cc";
+            features = [ "parallel" ];
+          }
+        ];
+        features = {
+          "bindgen" = [ "dep:bindgen" ];
+          "default" = [ "legacy" ];
+          "pkg-config" = [ "dep:pkg-config" ];
+        };
+        resolvedDefaultFeatures = [ "legacy" "std" ];
+      };
+    };
+
+    #
+    # crate2nix/default.nix (excerpt start)
+    #
+
+    /* Target (platform) data for conditional dependencies.
+      This corresponds roughly to what buildRustCrate is setting.
+    */
+    makeDefaultTarget = platform: {
+      unix = platform.isUnix;
+      windows = platform.isWindows;
+      fuchsia = true;
+      test = false;
+
+      /* We are choosing an arbitrary rust version to grab `lib` from,
+      which is unfortunate, but `lib` has been version-agnostic the
+      whole time so this is good enough for now.
+      */
+      os = pkgs.rust.lib.toTargetOs platform;
+      arch = pkgs.rust.lib.toTargetArch platform;
+      family = pkgs.rust.lib.toTargetFamily platform;
+      vendor = pkgs.rust.lib.toTargetVendor platform;
+      env = "gnu";
+      endian =
+        if platform.parsed.cpu.significantByte.name == "littleEndian"
+        then "little" else "big";
+      pointer_width = toString platform.parsed.cpu.bits;
+      debug_assertions = false;
+    };
+
+    /* Filters common temp files and build files. */
+    # TODO(pkolloch): Substitute with gitignore filter
+    sourceFilter = name: type:
+      let
+        baseName = builtins.baseNameOf (builtins.toString name);
+      in
+        ! (
+          # Filter out git
+          baseName == ".gitignore"
+          || (type == "directory" && baseName == ".git")
+
+          # Filter out build results
+          || (
+            type == "directory" && (
+              baseName == "target"
+              || baseName == "_site"
+              || baseName == ".sass-cache"
+              || baseName == ".jekyll-metadata"
+              || baseName == "build-artifacts"
+            )
+          )
+
+          # Filter out nix-build result symlinks
+          || (
+            type == "symlink" && lib.hasPrefix "result" baseName
+          )
+
+          # Filter out IDE config
+          || (
+            type == "directory" && (
+              baseName == ".idea" || baseName == ".vscode"
+            )
+          ) || lib.hasSuffix ".iml" baseName
+
+          # Filter out nix build files
+          || baseName == "Cargo.nix"
+
+          # Filter out editor backup / swap files.
+          || lib.hasSuffix "~" baseName
+          || builtins.match "^\\.sw[a-z]$$" baseName != null
+          || builtins.match "^\\..*\\.sw[a-z]$$" baseName != null
+          || lib.hasSuffix ".tmp" baseName
+          || lib.hasSuffix ".bak" baseName
+          || baseName == "tests.nix"
+        );
+
+    /* Returns a crate which depends on successful test execution
+      of crate given as the second argument.
+
+      testCrateFlags: list of flags to pass to the test exectuable
+      testInputs: list of packages that should be available during test execution
+    */
+    crateWithTest = { crate, testCrate, testCrateFlags, testInputs, testPreRun, testPostRun }:
+      assert builtins.typeOf testCrateFlags == "list";
+      assert builtins.typeOf testInputs == "list";
+      assert builtins.typeOf testPreRun == "string";
+      assert builtins.typeOf testPostRun == "string";
+      let
+        # override the `crate` so that it will build and execute tests instead of
+        # building the actual lib and bin targets We just have to pass `--test`
+        # to rustc and it will do the right thing.  We execute the tests and copy
+        # their log and the test executables to $out for later inspection.
+        test =
+          let
+            drv = testCrate.override
+              (
+                _: {
+                  buildTests = true;
+                }
+              );
+            # If the user hasn't set any pre/post commands, we don't want to
+            # insert empty lines. This means that any existing users of crate2nix
+            # don't get a spurious rebuild unless they set these explicitly.
+            testCommand = pkgs.lib.concatStringsSep "\n"
+              (pkgs.lib.filter (s: s != "") [
+                testPreRun
+                "$f $testCrateFlags 2>&1 | tee -a $out"
+                testPostRun
+              ]);
+          in
+          pkgs.runCommand "run-tests-${testCrate.name}"
+            {
+              inherit testCrateFlags;
+              buildInputs = testInputs;
+            } ''
+            set -e
+
+            export RUST_BACKTRACE=1
+
+            # recreate a file hierarchy as when running tests with cargo
+
+            # the source for test data
+            # It's necessary to locate the source in $NIX_BUILD_TOP/source/
+            # instead of $NIX_BUILD_TOP/
+            # because we compiled those test binaries in the former and not the latter.
+            # So all paths will expect source tree to be there and not in the build top directly.
+            # For example: $NIX_BUILD_TOP := /build in general, if you ask yourself.
+            # TODO(raitobezarius): I believe there could be more edge cases if `crate.sourceRoot`
+            # do exist but it's very hard to reason about them, so let's wait until the first bug report.
+            mkdir -p source/
+            cd source/
+
+            ${pkgs.buildPackages.xorg.lndir}/bin/lndir ${crate.src}
+
+            # build outputs
+            testRoot=target/debug
+            mkdir -p $testRoot
+
+            # executables of the crate
+            # we copy to prevent std::env::current_exe() to resolve to a store location
+            for i in ${crate}/bin/*; do
+              cp "$i" "$testRoot"
+            done
+            chmod +w -R .
+
+            # test harness executables are suffixed with a hash, like cargo does
+            # this allows to prevent name collision with the main
+            # executables of the crate
+            hash=$(basename $out)
+            for file in ${drv}/tests/*; do
+              f=$testRoot/$(basename $file)-$hash
+              cp $file $f
+              ${testCommand}
+            done
+          '';
+      in
+      pkgs.runCommand "${crate.name}-linked"
+        {
+          inherit (crate) outputs crateName;
+          passthru = (crate.passthru or { }) // {
+            inherit test;
+          };
+        }
+        (lib.optionalString (stdenv.buildPlatform.canExecute stdenv.hostPlatform) ''
+          echo tested by ${test}
+        '' + ''
+          ${lib.concatMapStringsSep "\n" (output: "ln -s ${crate.${output}} ${"$"}${output}") crate.outputs}
+        '');
+
+    /* A restricted overridable version of builtRustCratesWithFeatures. */
+    buildRustCrateWithFeatures =
+      { packageId
+      , features ? rootFeatures
+      , crateOverrides ? defaultCrateOverrides
+      , buildRustCrateForPkgsFunc ? null
+      , runTests ? false
+      , testCrateFlags ? [ ]
+      , testInputs ? [ ]
+        # Any command to run immediatelly before a test is executed.
+      , testPreRun ? ""
+        # Any command run immediatelly after a test is executed.
+      , testPostRun ? ""
+      }:
+      lib.makeOverridable
+        (
+          { features
+          , crateOverrides
+          , runTests
+          , testCrateFlags
+          , testInputs
+          , testPreRun
+          , testPostRun
+          }:
+          let
+            buildRustCrateForPkgsFuncOverriden =
+              if buildRustCrateForPkgsFunc != null
+              then buildRustCrateForPkgsFunc
+              else
+                (
+                  if crateOverrides == pkgs.defaultCrateOverrides
+                  then buildRustCrateForPkgs
+                  else
+                    pkgs: (buildRustCrateForPkgs pkgs).override {
+                      defaultCrateOverrides = crateOverrides;
+                    }
+                );
+            builtRustCrates = builtRustCratesWithFeatures {
+              inherit packageId features;
+              buildRustCrateForPkgsFunc = buildRustCrateForPkgsFuncOverriden;
+              runTests = false;
+            };
+            builtTestRustCrates = builtRustCratesWithFeatures {
+              inherit packageId features;
+              buildRustCrateForPkgsFunc = buildRustCrateForPkgsFuncOverriden;
+              runTests = true;
+            };
+            drv = builtRustCrates.crates.${packageId};
+            testDrv = builtTestRustCrates.crates.${packageId};
+            derivation =
+              if runTests then
+                crateWithTest
+                  {
+                    crate = drv;
+                    testCrate = testDrv;
+                    inherit testCrateFlags testInputs testPreRun testPostRun;
+                  }
+              else drv;
+          in
+          derivation
+        )
+        { inherit features crateOverrides runTests testCrateFlags testInputs testPreRun testPostRun; };
+
+    /* Returns an attr set with packageId mapped to the result of buildRustCrateForPkgsFunc
+      for the corresponding crate.
+    */
+    builtRustCratesWithFeatures =
+      { packageId
+      , features
+      , crateConfigs ? crates
+      , buildRustCrateForPkgsFunc
+      , runTests
+      , makeTarget ? makeDefaultTarget
+      } @ args:
+        assert (builtins.isAttrs crateConfigs);
+        assert (builtins.isString packageId);
+        assert (builtins.isList features);
+        assert (builtins.isAttrs (makeTarget stdenv.hostPlatform));
+        assert (builtins.isBool runTests);
+        let
+          rootPackageId = packageId;
+          mergedFeatures = mergePackageFeatures
+            (
+              args // {
+                inherit rootPackageId;
+                target = makeTarget stdenv.hostPlatform // { test = runTests; };
+              }
+            );
+          # Memoize built packages so that reappearing packages are only built once.
+          builtByPackageIdByPkgs = mkBuiltByPackageIdByPkgs pkgs;
+          mkBuiltByPackageIdByPkgs = pkgs:
+            let
+              self = {
+                crates = lib.mapAttrs (packageId: value: buildByPackageIdForPkgsImpl self pkgs packageId) crateConfigs;
+                target = makeTarget pkgs.stdenv.hostPlatform;
+                build = mkBuiltByPackageIdByPkgs pkgs.buildPackages;
+              };
+            in
+            self;
+          buildByPackageIdForPkgsImpl = self: pkgs: packageId:
+            let
+              features = mergedFeatures."${packageId}" or [ ];
+              crateConfig' = crateConfigs."${packageId}";
+              crateConfig =
+                builtins.removeAttrs crateConfig' [ "resolvedDefaultFeatures" "devDependencies" ];
+              devDependencies =
+                lib.optionals
+                  (runTests && packageId == rootPackageId)
+                  (crateConfig'.devDependencies or [ ]);
+              dependencies =
+                dependencyDerivations {
+                  inherit features;
+                  inherit (self) target;
+                  buildByPackageId = depPackageId:
+                    # proc_macro crates must be compiled for the build architecture
+                    if crateConfigs.${depPackageId}.procMacro or false
+                    then self.build.crates.${depPackageId}
+                    else self.crates.${depPackageId};
+                  dependencies =
+                    (crateConfig.dependencies or [ ])
+                    ++ devDependencies;
+                };
+              buildDependencies =
+                dependencyDerivations {
+                  inherit features;
+                  inherit (self.build) target;
+                  buildByPackageId = depPackageId:
+                    self.build.crates.${depPackageId};
+                  dependencies = crateConfig.buildDependencies or [ ];
+                };
+              dependenciesWithRenames =
+                let
+                  buildDeps = filterEnabledDependencies {
+                    inherit features;
+                    inherit (self) target;
+                    dependencies = crateConfig.dependencies or [ ] ++ devDependencies;
+                  };
+                  hostDeps = filterEnabledDependencies {
+                    inherit features;
+                    inherit (self.build) target;
+                    dependencies = crateConfig.buildDependencies or [ ];
+                  };
+                in
+                lib.filter (d: d ? "rename") (hostDeps ++ buildDeps);
+              # Crate renames have the form:
+              #
+              # {
+              #    crate_name = [
+              #       { version = "1.2.3"; rename = "crate_name01"; }
+              #    ];
+              #    # ...
+              # }
+              crateRenames =
+                let
+                  grouped =
+                    lib.groupBy
+                      (dependency: dependency.name)
+                      dependenciesWithRenames;
+                  versionAndRename = dep:
+                    let
+                      package = crateConfigs."${dep.packageId}";
+                    in
+                    { inherit (dep) rename; inherit (package) version; };
+                in
+                lib.mapAttrs (name: builtins.map versionAndRename) grouped;
+            in
+            buildRustCrateForPkgsFunc pkgs
+              (
+                crateConfig // {
+                  # https://github.com/NixOS/nixpkgs/issues/218712
+                  dontStrip = stdenv.hostPlatform.isDarwin;
+                  src = crateConfig.src or (
+                    pkgs.fetchurl rec {
+                      name = "${crateConfig.crateName}-${crateConfig.version}.tar.gz";
+                      # https://www.pietroalbini.org/blog/downloading-crates-io/
+                      # Not rate-limited, CDN URL.
+                      url = "https://static.crates.io/crates/${crateConfig.crateName}/${crateConfig.crateName}-${crateConfig.version}.crate";
+                      sha256 =
+                        assert (lib.assertMsg (crateConfig ? sha256) "Missing sha256 for ${name}");
+                        crateConfig.sha256;
+                    }
+                  );
+                  extraRustcOpts = lib.lists.optional (targetFeatures != [ ]) "-C target-feature=${lib.concatMapStringsSep "," (x: "+${x}") targetFeatures}";
+                  inherit features dependencies buildDependencies crateRenames release;
+                }
+              );
+        in
+        builtByPackageIdByPkgs;
+
+    /* Returns the actual derivations for the given dependencies. */
+    dependencyDerivations =
+      { buildByPackageId
+      , features
+      , dependencies
+      , target
+      }:
+        assert (builtins.isList features);
+        assert (builtins.isList dependencies);
+        assert (builtins.isAttrs target);
+        let
+          enabledDependencies = filterEnabledDependencies {
+            inherit dependencies features target;
+          };
+          depDerivation = dependency: buildByPackageId dependency.packageId;
+        in
+        map depDerivation enabledDependencies;
+
+    /* Returns a sanitized version of val with all values substituted that cannot
+      be serialized as JSON.
+    */
+    sanitizeForJson = val:
+      if builtins.isAttrs val
+      then lib.mapAttrs (n: sanitizeForJson) val
+      else if builtins.isList val
+      then builtins.map sanitizeForJson val
+      else if builtins.isFunction val
+      then "function"
+      else val;
+
+    /* Returns various tools to debug a crate. */
+    debugCrate = { packageId, target ? makeDefaultTarget stdenv.hostPlatform }:
+      assert (builtins.isString packageId);
+      let
+        debug = rec {
+          # The built tree as passed to buildRustCrate.
+          buildTree = buildRustCrateWithFeatures {
+            buildRustCrateForPkgsFunc = _: lib.id;
+            inherit packageId;
+          };
+          sanitizedBuildTree = sanitizeForJson buildTree;
+          dependencyTree = sanitizeForJson
+            (
+              buildRustCrateWithFeatures {
+                buildRustCrateForPkgsFunc = _: crate: {
+                  "01_crateName" = crate.crateName or false;
+                  "02_features" = crate.features or [ ];
+                  "03_dependencies" = crate.dependencies or [ ];
+                };
+                inherit packageId;
+              }
+            );
+          mergedPackageFeatures = mergePackageFeatures {
+            features = rootFeatures;
+            inherit packageId target;
+          };
+          diffedDefaultPackageFeatures = diffDefaultPackageFeatures {
+            inherit packageId target;
+          };
+        };
+      in
+      { internal = debug; };
+
+    /* Returns differences between cargo default features and crate2nix default
+      features.
+
+      This is useful for verifying the feature resolution in crate2nix.
+    */
+    diffDefaultPackageFeatures =
+      { crateConfigs ? crates
+      , packageId
+      , target
+      }:
+        assert (builtins.isAttrs crateConfigs);
+        let
+          prefixValues = prefix: lib.mapAttrs (n: v: { "${prefix}" = v; });
+          mergedFeatures =
+            prefixValues
+              "crate2nix"
+              (mergePackageFeatures { inherit crateConfigs packageId target; features = [ "default" ]; });
+          configs = prefixValues "cargo" crateConfigs;
+          combined = lib.foldAttrs (a: b: a // b) { } [ mergedFeatures configs ];
+          onlyInCargo =
+            builtins.attrNames
+              (lib.filterAttrs (n: v: !(v ? "crate2nix") && (v ? "cargo")) combined);
+          onlyInCrate2Nix =
+            builtins.attrNames
+              (lib.filterAttrs (n: v: (v ? "crate2nix") && !(v ? "cargo")) combined);
+          differentFeatures = lib.filterAttrs
+            (
+              n: v:
+                (v ? "crate2nix")
+                && (v ? "cargo")
+                && (v.crate2nix.features or [ ]) != (v."cargo".resolved_default_features or [ ])
+            )
+            combined;
+        in
+        builtins.toJSON {
+          inherit onlyInCargo onlyInCrate2Nix differentFeatures;
+        };
+
+    /* Returns an attrset mapping packageId to the list of enabled features.
+
+      If multiple paths to a dependency enable different features, the
+      corresponding feature sets are merged. Features in rust are additive.
+    */
+    mergePackageFeatures =
+      { crateConfigs ? crates
+      , packageId
+      , rootPackageId ? packageId
+      , features ? rootFeatures
+      , dependencyPath ? [ crates.${packageId}.crateName ]
+      , featuresByPackageId ? { }
+      , target
+        # Adds devDependencies to the crate with rootPackageId.
+      , runTests ? false
+      , ...
+      } @ args:
+        assert (builtins.isAttrs crateConfigs);
+        assert (builtins.isString packageId);
+        assert (builtins.isString rootPackageId);
+        assert (builtins.isList features);
+        assert (builtins.isList dependencyPath);
+        assert (builtins.isAttrs featuresByPackageId);
+        assert (builtins.isAttrs target);
+        assert (builtins.isBool runTests);
+        let
+          crateConfig = crateConfigs."${packageId}" or (builtins.throw "Package not found: ${packageId}");
+          expandedFeatures = expandFeatures (crateConfig.features or { }) features;
+          enabledFeatures = enableFeatures (crateConfig.dependencies or [ ]) expandedFeatures;
+          depWithResolvedFeatures = dependency:
+            let
+              inherit (dependency) packageId;
+              features = dependencyFeatures enabledFeatures dependency;
+            in
+            { inherit packageId features; };
+          resolveDependencies = cache: path: dependencies:
+            assert (builtins.isAttrs cache);
+            assert (builtins.isList dependencies);
+            let
+              enabledDependencies = filterEnabledDependencies {
+                inherit dependencies target;
+                features = enabledFeatures;
+              };
+              directDependencies = map depWithResolvedFeatures enabledDependencies;
+              foldOverCache = op: lib.foldl op cache directDependencies;
+            in
+            foldOverCache
+              (
+                cache: { packageId, features }:
+                  let
+                    cacheFeatures = cache.${packageId} or [ ];
+                    combinedFeatures = sortedUnique (cacheFeatures ++ features);
+                  in
+                  if cache ? ${packageId} && cache.${packageId} == combinedFeatures
+                  then cache
+                  else
+                    mergePackageFeatures {
+                      features = combinedFeatures;
+                      featuresByPackageId = cache;
+                      inherit crateConfigs packageId target runTests rootPackageId;
+                    }
+              );
+          cacheWithSelf =
+            let
+              cacheFeatures = featuresByPackageId.${packageId} or [ ];
+              combinedFeatures = sortedUnique (cacheFeatures ++ enabledFeatures);
+            in
+            featuresByPackageId // {
+              "${packageId}" = combinedFeatures;
+            };
+          cacheWithDependencies =
+            resolveDependencies cacheWithSelf "dep"
+              (
+                crateConfig.dependencies or [ ]
+                ++ lib.optionals
+                  (runTests && packageId == rootPackageId)
+                  (crateConfig.devDependencies or [ ])
+              );
+          cacheWithAll =
+            resolveDependencies
+              cacheWithDependencies "build"
+              (crateConfig.buildDependencies or [ ]);
+        in
+        cacheWithAll;
+
+    /* Returns the enabled dependencies given the enabled features. */
+    filterEnabledDependencies = { dependencies, features, target }:
+      assert (builtins.isList dependencies);
+      assert (builtins.isList features);
+      assert (builtins.isAttrs target);
+
+      lib.filter
+        (
+          dep:
+          let
+            targetFunc = dep.target or (features: true);
+          in
+          targetFunc { inherit features target; }
+          && (
+            !(dep.optional or false)
+            || builtins.any (doesFeatureEnableDependency dep) features
+          )
+        )
+        dependencies;
+
+    /* Returns whether the given feature should enable the given dependency. */
+    doesFeatureEnableDependency = dependency: feature:
+      let
+        name = dependency.rename or dependency.name;
+        prefix = "${name}/";
+        len = builtins.stringLength prefix;
+        startsWithPrefix = builtins.substring 0 len feature == prefix;
+      in
+      feature == name || feature == "dep:" + name || startsWithPrefix;
+
+    /* Returns the expanded features for the given inputFeatures by applying the
+      rules in featureMap.
+
+      featureMap is an attribute set which maps feature names to lists of further
+      feature names to enable in case this feature is selected.
+    */
+    expandFeatures = featureMap: inputFeatures:
+      assert (builtins.isAttrs featureMap);
+      assert (builtins.isList inputFeatures);
+      let
+        expandFeaturesNoCycle = oldSeen: inputFeatures:
+          if inputFeatures != [ ]
+          then
+            let
+              # The feature we're currently expanding.
+              feature = builtins.head inputFeatures;
+              # All the features we've seen/expanded so far, including the one
+              # we're currently processing.
+              seen = oldSeen // { ${feature} = 1; };
+              # Expand the feature but be careful to not re-introduce a feature
+              # that we've already seen: this can easily cause a cycle, see issue
+              # #209.
+              enables = builtins.filter (f: !(seen ? "${f}")) (featureMap."${feature}" or [ ]);
+            in
+            [ feature ] ++ (expandFeaturesNoCycle seen (builtins.tail inputFeatures ++ enables))
+          # No more features left, nothing to expand to.
+          else [ ];
+        outFeatures = expandFeaturesNoCycle { } inputFeatures;
+      in
+      sortedUnique outFeatures;
+
+    /* This function adds optional dependencies as features if they are enabled
+      indirectly by dependency features. This function mimics Cargo's behavior
+      described in a note at:
+      https://doc.rust-lang.org/nightly/cargo/reference/features.html#dependency-features
+    */
+    enableFeatures = dependencies: features:
+      assert (builtins.isList features);
+      assert (builtins.isList dependencies);
+      let
+        additionalFeatures = lib.concatMap
+          (
+            dependency:
+              assert (builtins.isAttrs dependency);
+              let
+                enabled = builtins.any (doesFeatureEnableDependency dependency) features;
+              in
+              if (dependency.optional or false) && enabled
+              then [ (dependency.rename or dependency.name) ]
+              else [ ]
+          )
+          dependencies;
+      in
+      sortedUnique (features ++ additionalFeatures);
+
+    /*
+      Returns the actual features for the given dependency.
+
+      features: The features of the crate that refers this dependency.
+    */
+    dependencyFeatures = features: dependency:
+      assert (builtins.isList features);
+      assert (builtins.isAttrs dependency);
+      let
+        defaultOrNil =
+          if dependency.usesDefaultFeatures or true
+          then [ "default" ]
+          else [ ];
+        explicitFeatures = dependency.features or [ ];
+        additionalDependencyFeatures =
+          let
+            name = dependency.rename or dependency.name;
+            stripPrefixMatch = prefix: s:
+              if lib.hasPrefix prefix s
+              then lib.removePrefix prefix s
+              else null;
+            extractFeature = feature: lib.findFirst
+              (f: f != null)
+              null
+              (map (prefix: stripPrefixMatch prefix feature) [
+                (name + "/")
+                (name + "?/")
+              ]);
+            dependencyFeatures = lib.filter (f: f != null) (map extractFeature features);
+          in
+          dependencyFeatures;
+      in
+      defaultOrNil ++ explicitFeatures ++ additionalDependencyFeatures;
+
+    /* Sorts and removes duplicates from a list of strings. */
+    sortedUnique = features:
+      assert (builtins.isList features);
+      assert (builtins.all builtins.isString features);
+      let
+        outFeaturesSet = lib.foldl (set: feature: set // { "${feature}" = 1; }) { } features;
+        outFeaturesUnique = builtins.attrNames outFeaturesSet;
+      in
+      builtins.sort (a: b: a < b) outFeaturesUnique;
+
+    deprecationWarning = message: value:
+      if strictDeprecation
+      then builtins.throw "strictDeprecation enabled, aborting: ${message}"
+      else builtins.trace message value;
+
+    #
+    # crate2nix/default.nix (excerpt end)
+    #
+  };
+}
+
diff --git a/tvix/tools/turbofetch/Cargo.toml b/tvix/tools/turbofetch/Cargo.toml
new file mode 100644
index 0000000000..6b2f0e7520
--- /dev/null
+++ b/tvix/tools/turbofetch/Cargo.toml
@@ -0,0 +1,28 @@
+[package]
+name = "turbofetch"
+version = "0.1.0"
+edition = "2021"
+
+# We don't join the //tvix workspace, as this is fairly cache.nixos.org-specific.
+[workspace]
+members = ["."]
+
+[dependencies]
+aws_lambda_events = { version = "0.11.1", default-features = false, features = ["lambda_function_urls"] }
+bytes = "1.5.0"
+data-encoding = "2.4.0"
+futures = { version = "0.3.30", default-features = false, features = ["std"] }
+httparse = "1.8.0"
+hyper = { version = "0.14.27", default-features = false }
+lambda_runtime = "0.8.2"
+magic-buffer = "0.1.1"
+rusoto_core = { version = "0.48.0", features = ["rustls"], default-features = false }
+rusoto_s3 = { version = "0.48.0", features = ["rustls"], default-features = false }
+serde_json = "1.0.108"
+serde = { version = "1.0.190", features = ["derive"] }
+tokio = { version = "1.33.0", features = ["full"] }
+tower = "0.4.13"
+# TODO(edef): zstd = "0.13.0"
+zstd = "0.9.0"
+tracing-subscriber = { version = "0.3.17", features = ["json"] }
+tracing = "0.1.40"
diff --git a/tvix/tools/turbofetch/OWNERS b/tvix/tools/turbofetch/OWNERS
new file mode 100644
index 0000000000..b9bc074a80
--- /dev/null
+++ b/tvix/tools/turbofetch/OWNERS
@@ -0,0 +1 @@
+edef
diff --git a/tvix/tools/turbofetch/default.nix b/tvix/tools/turbofetch/default.nix
new file mode 100644
index 0000000000..bd70f6a5e6
--- /dev/null
+++ b/tvix/tools/turbofetch/default.nix
@@ -0,0 +1,12 @@
+{ lib, pkgs, ... }:
+
+(pkgs.callPackage ./Cargo.nix {
+  defaultCrateOverrides = pkgs.defaultCrateOverrides // {
+
+    ring = prev: {
+      links = ''ring_core_${lib.replaceStrings ["."] ["_"] prev.version}'';
+    };
+  };
+}).rootCrate.build.override {
+  runTests = true;
+}
diff --git a/tvix/tools/turbofetch/deploy.sh b/tvix/tools/turbofetch/deploy.sh
new file mode 100755
index 0000000000..65564ce2ed
--- /dev/null
+++ b/tvix/tools/turbofetch/deploy.sh
@@ -0,0 +1,5 @@
+#! /usr/bin/env nix-shell
+#! nix-shell -i "bash -e"
+#! nix-shell -p cargo-lambda
+cargo lambda build --release
+cargo lambda deploy
diff --git a/tvix/tools/turbofetch/src/buffer.rs b/tvix/tools/turbofetch/src/buffer.rs
new file mode 100644
index 0000000000..d6ff93e3cf
--- /dev/null
+++ b/tvix/tools/turbofetch/src/buffer.rs
@@ -0,0 +1,83 @@
+use magic_buffer::MagicBuffer;
+use std::cell::Cell;
+
+/// Buffer is a FIFO queue for bytes, built on a ring buffer.
+/// It always provides contiguous slices for both the readable and writable parts,
+/// using an underlying buffer that is "mirrored" in virtual memory.
+pub struct Buffer {
+    buffer: MagicBuffer,
+    /// first readable byte
+    head: Cell<usize>,
+    /// first writable byte
+    tail: usize,
+}
+
+impl Buffer {
+    /// Allocate a fresh buffer, with the specified capacity.
+    /// The buffer can contain at most `capacity - 1` bytes.
+    /// The capacity must be a power of two, and at least [Buffer::min_len].
+    pub fn new(capacity: usize) -> Buffer {
+        Buffer {
+            // MagicBuffer::new verifies that `capacity` is a power of two,
+            // and at least MagicBuffer::min_len().
+            buffer: MagicBuffer::new(capacity).unwrap(),
+            // `head == tail` means the buffer is empty.
+            // In order to ensure that this remains unambiguous,
+            // the buffer can only be filled with capacity-1 bytes.
+            head: Cell::new(0),
+            tail: 0,
+        }
+    }
+
+    /// Returns the minimum buffer capacity.
+    /// This depends on the operating system and architecture.
+    pub fn min_capacity() -> usize {
+        MagicBuffer::min_len()
+    }
+
+    /// Return the capacity of the buffer.
+    /// This is equal to `self.data().len() + self.space().len() + 1`.
+    pub fn capacity(&self) -> usize {
+        self.buffer.len()
+    }
+
+    /// Return the valid, readable data in the buffer.
+    pub fn data(&self) -> &[u8] {
+        let len = self.buffer.len();
+        let head = self.head.get();
+
+        if head <= self.tail {
+            &self.buffer[head..self.tail]
+        } else {
+            &self.buffer[head..self.tail + len]
+        }
+    }
+
+    /// Mark `read_len` bytes of the readable data as consumed, freeing the space.
+    pub fn consume(&self, read_len: usize) {
+        debug_assert!(read_len <= self.data().len());
+        let mut head = self.head.get();
+        head += read_len;
+        head &= self.buffer.len() - 1;
+        self.head.set(head);
+    }
+
+    /// Return the empty, writable space in the buffer.
+    pub fn space(&mut self) -> &mut [u8] {
+        let len = self.buffer.len();
+        let head = self.head.get();
+
+        if head <= self.tail {
+            &mut self.buffer[self.tail..head + len - 1]
+        } else {
+            &mut self.buffer[self.tail..head - 1]
+        }
+    }
+
+    /// Mark `written_len` bytes of the writable space as valid, readable data.
+    pub fn commit(&mut self, written_len: usize) {
+        debug_assert!(written_len <= self.space().len());
+        self.tail += written_len;
+        self.tail &= self.buffer.len() - 1;
+    }
+}
diff --git a/tvix/tools/turbofetch/src/lib.rs b/tvix/tools/turbofetch/src/lib.rs
new file mode 100644
index 0000000000..4b62fa4d75
--- /dev/null
+++ b/tvix/tools/turbofetch/src/lib.rs
@@ -0,0 +1,103 @@
+use std::{mem::MaybeUninit, str};
+use tokio::io::{self, AsyncRead, AsyncReadExt};
+
+pub use buffer::Buffer;
+mod buffer;
+
+/// Read as much data into `buffer` as possible.
+/// Returns [io::ErrorKind::OutOfMemory] if the buffer is already full.
+async fn slurp(buffer: &mut Buffer, sock: &mut (impl AsyncRead + Unpin)) -> io::Result<()> {
+    match buffer.space() {
+        [] => Err(io::Error::new(io::ErrorKind::OutOfMemory, "buffer filled")),
+        buf => {
+            let n = sock.read(buf).await?;
+            if n == 0 {
+                return Err(io::ErrorKind::UnexpectedEof.into());
+            }
+            buffer.commit(n);
+
+            Ok(())
+        }
+    }
+}
+
+fn get_content_length(headers: &[httparse::Header]) -> io::Result<u64> {
+    for header in headers {
+        if header.name == "Transfer-Encoding" {
+            return Err(io::Error::new(
+                io::ErrorKind::InvalidData,
+                "Transfer-Encoding is unsupported",
+            ));
+        }
+
+        if header.name == "Content-Length" {
+            return str::from_utf8(header.value)
+                .ok()
+                .and_then(|v| v.parse().ok())
+                .ok_or_else(|| {
+                    io::Error::new(io::ErrorKind::InvalidData, "invalid Content-Length")
+                });
+        }
+    }
+
+    Err(io::Error::new(
+        io::ErrorKind::InvalidData,
+        "Content-Length missing",
+    ))
+}
+
+/// Read an HTTP response from `sock` using `buffer`, returning the response body.
+/// Returns an error if anything but 200 OK is received.
+///
+/// The buffer must have enough space to contain the entire response body.
+/// If there is not enough space, [io::ErrorKind::OutOfMemory] is returned.
+///
+/// The HTTP response must use `Content-Length`, without `Transfer-Encoding`.
+pub async fn parse_response<'a>(
+    sock: &mut (impl AsyncRead + Unpin),
+    buffer: &'a mut Buffer,
+) -> io::Result<&'a [u8]> {
+    let body_len = loop {
+        let mut headers = [MaybeUninit::uninit(); 16];
+        let mut response = httparse::Response::new(&mut []);
+        let status = httparse::ParserConfig::default()
+            .parse_response_with_uninit_headers(&mut response, buffer.data(), &mut headers)
+            .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
+
+        if let httparse::Status::Complete(n) = status {
+            buffer.consume(n);
+
+            let code = response.code.unwrap();
+            if code != 200 {
+                return Err(io::Error::new(
+                    io::ErrorKind::Other,
+                    format!("HTTP response {code}"),
+                ));
+            }
+
+            break get_content_length(response.headers)?;
+        }
+
+        slurp(buffer, sock).await?;
+    };
+
+    let buf_len = buffer.space().len() + buffer.data().len();
+
+    if body_len > buf_len as u64 {
+        return Err(io::Error::new(
+            io::ErrorKind::OutOfMemory,
+            "HTTP response body does not fit in buffer",
+        ));
+    }
+
+    let body_len = body_len as usize;
+
+    while buffer.data().len() < body_len {
+        slurp(buffer, sock).await?;
+    }
+
+    let data = buffer.data();
+    buffer.consume(body_len);
+
+    Ok(&data[..body_len])
+}
diff --git a/tvix/tools/turbofetch/src/main.rs b/tvix/tools/turbofetch/src/main.rs
new file mode 100644
index 0000000000..4b3a50eb39
--- /dev/null
+++ b/tvix/tools/turbofetch/src/main.rs
@@ -0,0 +1,220 @@
+//! turbofetch is a high-performance bulk S3 object aggregator.
+//!
+//! It operates on two S3 buckets: a source bucket (nix-cache), and a
+//! work bucket defined at runtime. The work bucket contains a job file
+//! consisting of concatenated 32-character keys, representing narinfo
+//! files in the source bucket, without the `.narinfo` suffix or any
+//! other separators.
+//!
+//! Each run of turbofetch processes a half-open range of indices from the
+//! job file, and outputs a zstd stream of concatenated objects, without
+//! additional separators and in no particular order. These segment files
+//! are written into the work bucket, named for the range of indices they
+//! cover. `/narinfo.zst/000000000c380d40-000000000c385b60` covers the 20k
+//! objects `[0xc380d40, 0xc385b60) = [205000000, 205020000)`. Empirically,
+//! segment files of 20k objects achieve a compression ratio of 4.7x.
+//!
+//! Reassembly is left to narinfo2parquet, which interprets StorePath lines.
+//!
+//! TODO(edef): any retries/error handling whatsoever
+//! Currently, it fails an entire range if anything goes wrong, and doesn't
+//! write any output.
+
+use bytes::Bytes;
+use futures::{stream::FuturesUnordered, Stream, TryStreamExt};
+use rusoto_core::ByteStream;
+use rusoto_s3::{GetObjectRequest, PutObjectRequest, S3Client, S3};
+use serde::Deserialize;
+use std::{io::Write, mem, ops::Range, ptr};
+use tokio::{
+    io::{self, AsyncReadExt, AsyncWriteExt},
+    net::TcpStream,
+};
+
+/// Fetch a group of keys, streaming concatenated chunks as they arrive from S3.
+/// `keys` must be a slice from the job file. Any network error at all fails the
+/// entire batch, and there is no rate limiting.
+fn fetch(keys: &[[u8; 32]]) -> impl Stream<Item = io::Result<Bytes>> {
+    // S3 supports only HTTP/1.1, but we can ease the pain somewhat by using
+    // HTTP pipelining. It terminates the TCP connection after receiving 100
+    // requests, so we chunk the keys up accordingly, and make one connection
+    // for each chunk.
+    keys.chunks(100)
+        .map(|chunk| {
+            const PREFIX: &[u8] = b"GET /nix-cache/";
+            const SUFFIX: &[u8] = b".narinfo HTTP/1.1\nHost: s3.amazonaws.com\n\n";
+            const LENGTH: usize = PREFIX.len() + 32 + SUFFIX.len();
+
+            let mut request = Vec::with_capacity(LENGTH * 100);
+            for key in chunk {
+                request.extend_from_slice(PREFIX);
+                request.extend_from_slice(key);
+                request.extend_from_slice(SUFFIX);
+            }
+
+            (request, chunk.len())
+        })
+        .map(|(request, n)| async move {
+            let (mut read, mut write) = TcpStream::connect("s3.amazonaws.com:80")
+                .await?
+                .into_split();
+
+            let _handle = tokio::spawn(async move {
+                let request = request;
+                write.write_all(&request).await
+            });
+
+            let mut buffer = turbofetch::Buffer::new(512 * 1024);
+            let mut bodies = vec![];
+
+            for _ in 0..n {
+                let body = turbofetch::parse_response(&mut read, &mut buffer).await?;
+                bodies.extend_from_slice(body);
+            }
+
+            Ok::<_, io::Error>(Bytes::from(bodies))
+        })
+        .collect::<FuturesUnordered<_>>()
+}
+
+/// Retrieve a range of keys from the job file.
+async fn get_range(
+    s3: &'static S3Client,
+    bucket: String,
+    key: String,
+    range: Range<u64>,
+) -> io::Result<Box<[[u8; 32]]>> {
+    let resp = s3
+        .get_object(GetObjectRequest {
+            bucket,
+            key,
+            range: Some(format!("bytes={}-{}", range.start * 32, range.end * 32 - 1)),
+            ..GetObjectRequest::default()
+        })
+        .await
+        .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
+
+    let mut body = vec![];
+    resp.body
+        .ok_or(io::ErrorKind::InvalidData)?
+        .into_async_read()
+        .read_to_end(&mut body)
+        .await?;
+
+    let body = exact_chunks(body.into_boxed_slice()).ok_or(io::ErrorKind::InvalidData)?;
+
+    Ok(body)
+}
+
+fn exact_chunks(mut buf: Box<[u8]>) -> Option<Box<[[u8; 32]]>> {
+    // SAFETY: We ensure that `buf.len()` is a multiple of 32, and there are no alignment requirements.
+    unsafe {
+        let ptr = buf.as_mut_ptr();
+        let len = buf.len();
+
+        if len % 32 != 0 {
+            return None;
+        }
+
+        let ptr = ptr as *mut [u8; 32];
+        let len = len / 32;
+        mem::forget(buf);
+
+        Some(Box::from_raw(ptr::slice_from_raw_parts_mut(ptr, len)))
+    }
+}
+
+// TODO(edef): factor this out into a separate entry point
+#[tokio::main(flavor = "current_thread")]
+async fn main() -> Result<(), lambda_runtime::Error> {
+    let s3 = S3Client::new(rusoto_core::Region::UsEast1);
+    let s3 = &*Box::leak(Box::new(s3));
+
+    tracing_subscriber::fmt()
+        .json()
+        .with_max_level(tracing::Level::INFO)
+        // this needs to be set to remove duplicated information in the log.
+        .with_current_span(false)
+        // this needs to be set to false, otherwise ANSI color codes will
+        // show up in a confusing manner in CloudWatch logs.
+        .with_ansi(false)
+        // disabling time is handy because CloudWatch will add the ingestion time.
+        .without_time()
+        // remove the name of the function from every log entry
+        .with_target(false)
+        .init();
+
+    lambda_runtime::run(lambda_runtime::service_fn(|event| func(s3, event))).await
+}
+
+/// Lambda request body
+#[derive(Debug, Deserialize)]
+struct Params {
+    work_bucket: String,
+    job_file: String,
+    start: u64,
+    end: u64,
+}
+
+#[tracing::instrument(skip(s3, event), fields(req_id = %event.context.request_id))]
+async fn func(
+    s3: &'static S3Client,
+    event: lambda_runtime::LambdaEvent<
+        aws_lambda_events::lambda_function_urls::LambdaFunctionUrlRequest,
+    >,
+) -> Result<&'static str, lambda_runtime::Error> {
+    let mut params = event.payload.body.ok_or("no body")?;
+
+    if event.payload.is_base64_encoded {
+        params = String::from_utf8(data_encoding::BASE64.decode(params.as_bytes())?)?;
+    }
+
+    let params: Params = serde_json::from_str(&params)?;
+
+    if params.start >= params.end {
+        return Err("nope".into());
+    }
+
+    let keys = get_range(
+        s3,
+        params.work_bucket.clone(),
+        params.job_file.to_owned(),
+        params.start..params.end,
+    )
+    .await?;
+
+    let zchunks = fetch(&keys)
+        .try_fold(
+            Box::new(zstd::Encoder::new(vec![], zstd::DEFAULT_COMPRESSION_LEVEL).unwrap()),
+            |mut w, buf| {
+                w.write_all(&buf).unwrap();
+                async { Ok(w) }
+            },
+        )
+        .await?;
+
+    let zchunks = to_byte_stream(zchunks.finish().unwrap());
+
+    tracing::info!("we got to put_object");
+
+    s3.put_object(PutObjectRequest {
+        bucket: params.work_bucket,
+        key: format!("narinfo.zst/{:016x}-{:016x}", params.start, params.end),
+        body: Some(zchunks),
+        ..Default::default()
+    })
+    .await
+    .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
+
+    tracing::info!("… and it worked!");
+
+    Ok("OK")
+}
+
+fn to_byte_stream(buffer: Vec<u8>) -> ByteStream {
+    let size_hint = buffer.len();
+    ByteStream::new_with_size(
+        futures::stream::once(async { Ok(buffer.into()) }),
+        size_hint,
+    )
+}
diff --git a/tvix/tools/weave/Cargo.lock b/tvix/tools/weave/Cargo.lock
new file mode 100644
index 0000000000..46a90cb482
--- /dev/null
+++ b/tvix/tools/weave/Cargo.lock
@@ -0,0 +1,2179 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "addr2line"
+version = "0.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb"
+dependencies = [
+ "gimli",
+]
+
+[[package]]
+name = "adler"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+
+[[package]]
+name = "ahash"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01"
+dependencies = [
+ "cfg-if",
+ "getrandom",
+ "once_cell",
+ "version_check",
+ "zerocopy",
+]
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "alloc-no-stdlib"
+version = "2.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3"
+
+[[package]]
+name = "alloc-stdlib"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece"
+dependencies = [
+ "alloc-no-stdlib",
+]
+
+[[package]]
+name = "allocator-api2"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5"
+
+[[package]]
+name = "android-tzdata"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
+
+[[package]]
+name = "android_system_properties"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.79"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca"
+dependencies = [
+ "backtrace",
+]
+
+[[package]]
+name = "argminmax"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "202108b46429b765ef483f8a24d5c46f48c14acfdacc086dd4ab6dddf6bcdbd2"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "array-init-cursor"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf7d0a018de4f6aa429b9d33d69edf69072b1c5b1cb8d3e4a5f7ef898fc3eb76"
+
+[[package]]
+name = "arrow-format"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07884ea216994cdc32a2d5f8274a8bee979cfe90274b83f86f440866ee3132c7"
+dependencies = [
+ "planus",
+ "serde",
+]
+
+[[package]]
+name = "async-stream"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51"
+dependencies = [
+ "async-stream-impl",
+ "futures-core",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "async-stream-impl"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "async-trait"
+version = "0.1.77"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "atoi"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "atoi_simd"
+version = "0.15.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ae037714f313c1353189ead58ef9eec30a8e8dc101b2622d461418fd59e28a9"
+
+[[package]]
+name = "autocfg"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+
+[[package]]
+name = "backtrace"
+version = "0.3.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837"
+dependencies = [
+ "addr2line",
+ "cc",
+ "cfg-if",
+ "libc",
+ "miniz_oxide",
+ "object",
+ "rustc-demangle",
+]
+
+[[package]]
+name = "base64"
+version = "0.21.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
+
+[[package]]
+name = "base64ct"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "bitflags"
+version = "2.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
+
+[[package]]
+name = "block-buffer"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "brotli"
+version = "3.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "516074a47ef4bce09577a3b379392300159ce5b1ba2e501ff1c819950066100f"
+dependencies = [
+ "alloc-no-stdlib",
+ "alloc-stdlib",
+ "brotli-decompressor",
+]
+
+[[package]]
+name = "brotli-decompressor"
+version = "2.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4e2e4afe60d7dd600fdd3de8d0f08c2b7ec039712e3b6137ff98b7004e82de4f"
+dependencies = [
+ "alloc-no-stdlib",
+ "alloc-stdlib",
+]
+
+[[package]]
+name = "bstr"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c48f0051a4b4c5e0b6d365cd04af53aeaa209e3cc15ec2cdb69e73cc87fbd0dc"
+dependencies = [
+ "memchr",
+ "regex-automata",
+ "serde",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"
+
+[[package]]
+name = "bytemuck"
+version = "1.14.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea31d69bda4949c1c1562c1e6f042a1caefac98cdc8a298260a2ff41c1e2d42b"
+dependencies = [
+ "bytemuck_derive",
+]
+
+[[package]]
+name = "bytemuck_derive"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "965ab7eb5f8f97d2a083c799f3a1b994fc397b2fe2da5d1da1626ce15a39f2b1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "bytes"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
+
+[[package]]
+name = "cc"
+version = "1.0.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
+dependencies = [
+ "jobserver",
+ "libc",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "chrono"
+version = "0.4.33"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f13690e35a5e4ace198e7beea2895d29f3a9cc55015fcebe6336bd2010af9eb"
+dependencies = [
+ "android-tzdata",
+ "iana-time-zone",
+ "num-traits",
+ "windows-targets 0.52.0",
+]
+
+[[package]]
+name = "comfy-table"
+version = "7.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c64043d6c7b7a4c58e39e7efccfdea7b93d885a795d0c054a69dbbf4dd52686"
+dependencies = [
+ "crossterm",
+ "strum",
+ "strum_macros",
+ "unicode-width",
+]
+
+[[package]]
+name = "const-oid"
+version = "0.9.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
+
+[[package]]
+name = "cpufeatures"
+version = "0.2.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crc32fast"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "crossbeam-channel"
+version = "0.5.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "176dc175b78f56c0f321911d9c8eb2b77a78a4860b9c19db83835fea1a46649b"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-deque"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d"
+dependencies = [
+ "crossbeam-epoch",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.9.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-queue"
+version = "0.3.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
+
+[[package]]
+name = "crossterm"
+version = "0.27.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df"
+dependencies = [
+ "bitflags 2.4.2",
+ "crossterm_winapi",
+ "libc",
+ "parking_lot",
+ "winapi",
+]
+
+[[package]]
+name = "crossterm_winapi"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "crypto-common"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
+dependencies = [
+ "generic-array",
+ "typenum",
+]
+
+[[package]]
+name = "curve25519-dalek"
+version = "4.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0a677b8922c94e01bdbb12126b0bc852f00447528dee1782229af9c720c3f348"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "curve25519-dalek-derive",
+ "digest",
+ "fiat-crypto",
+ "platforms",
+ "rustc_version",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "curve25519-dalek-derive"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "data-encoding"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5"
+
+[[package]]
+name = "der"
+version = "0.7.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c"
+dependencies = [
+ "const-oid",
+ "zeroize",
+]
+
+[[package]]
+name = "digest"
+version = "0.10.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
+dependencies = [
+ "block-buffer",
+ "crypto-common",
+]
+
+[[package]]
+name = "dyn-clone"
+version = "1.0.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "545b22097d44f8a9581187cdf93de7a71e4722bf51200cfaba810865b49a495d"
+
+[[package]]
+name = "ed25519"
+version = "2.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53"
+dependencies = [
+ "pkcs8",
+ "signature",
+]
+
+[[package]]
+name = "ed25519-dalek"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871"
+dependencies = [
+ "curve25519-dalek",
+ "ed25519",
+ "serde",
+ "sha2",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "either"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
+
+[[package]]
+name = "enum_dispatch"
+version = "0.3.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f33313078bb8d4d05a2733a94ac4c2d8a0df9a2b84424ebf4f33bfc224a890e"
+dependencies = [
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "equivalent"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
+
+[[package]]
+name = "ethnum"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b90ca2580b73ab6a1f724b76ca11ab632df820fd6040c336200d2c1df7b3c82c"
+
+[[package]]
+name = "fallible-streaming-iterator"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
+
+[[package]]
+name = "fast-float"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95765f67b4b18863968b4a1bd5bb576f732b29a4a28c7cd84c09fa3e2875f33c"
+
+[[package]]
+name = "fiat-crypto"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1676f435fc1dadde4d03e43f5d62b259e1ce5f40bd4ffb21db2b42ebe59c1382"
+
+[[package]]
+name = "flate2"
+version = "1.0.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e"
+dependencies = [
+ "crc32fast",
+ "miniz_oxide",
+]
+
+[[package]]
+name = "foreign_vec"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee1b05cbd864bcaecbd3455d6d967862d446e4ebfc3c2e5e5b9841e53cba6673"
+
+[[package]]
+name = "futures"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-io",
+ "futures-sink",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
+
+[[package]]
+name = "futures-executor"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-io"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
+
+[[package]]
+name = "futures-macro"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "futures-sink"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5"
+
+[[package]]
+name = "futures-task"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
+
+[[package]]
+name = "futures-util"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-macro",
+ "futures-sink",
+ "futures-task",
+ "memchr",
+ "pin-project-lite",
+ "pin-utils",
+ "slab",
+]
+
+[[package]]
+name = "generic-array"
+version = "0.14.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "libc",
+ "wasi",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "gimli"
+version = "0.28.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
+
+[[package]]
+name = "glob"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
+
+[[package]]
+name = "hashbrown"
+version = "0.14.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
+dependencies = [
+ "ahash",
+ "allocator-api2",
+ "rayon",
+]
+
+[[package]]
+name = "heck"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
+
+[[package]]
+name = "hermit-abi"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0c62115964e08cb8039170eb33c1d0e2388a256930279edca206fff675f82c3"
+
+[[package]]
+name = "home"
+version = "0.5.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5"
+dependencies = [
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "iana-time-zone"
+version = "0.1.60"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "iana-time-zone-haiku",
+ "js-sys",
+ "wasm-bindgen",
+ "windows-core",
+]
+
+[[package]]
+name = "iana-time-zone-haiku"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "indexmap"
+version = "2.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "824b2ae422412366ba479e8111fd301f7b5faece8149317bb81925979a53f520"
+dependencies = [
+ "equivalent",
+ "hashbrown",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
+
+[[package]]
+name = "jobserver"
+version = "0.1.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "js-sys"
+version = "0.3.68"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee"
+dependencies = [
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.153"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
+
+[[package]]
+name = "libm"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058"
+
+[[package]]
+name = "lock_api"
+version = "0.4.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45"
+dependencies = [
+ "autocfg",
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
+
+[[package]]
+name = "lz4"
+version = "1.24.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e9e2dd86df36ce760a60f6ff6ad526f7ba1f14ba0356f8254fb6905e6494df1"
+dependencies = [
+ "libc",
+ "lz4-sys",
+]
+
+[[package]]
+name = "lz4-sys"
+version = "1.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57d27b317e207b10f69f5e75494119e391a96f48861ae870d1da6edac98ca900"
+dependencies = [
+ "cc",
+ "libc",
+]
+
+[[package]]
+name = "memchr"
+version = "2.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
+
+[[package]]
+name = "memmap2"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f49388d20533534cd19360ad3d6a7dadc885944aa802ba3995040c5ec11288c6"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "minimal-lexical"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
+
+[[package]]
+name = "miniz_oxide"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7"
+dependencies = [
+ "adler",
+]
+
+[[package]]
+name = "mio"
+version = "0.8.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09"
+dependencies = [
+ "libc",
+ "wasi",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "multiversion"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2c7b9d7fe61760ce5ea19532ead98541f6b4c495d87247aff9826445cf6872a"
+dependencies = [
+ "multiversion-macros",
+ "target-features",
+]
+
+[[package]]
+name = "multiversion-macros"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26a83d8500ed06d68877e9de1dde76c1dbb83885dcdbda4ef44ccbc3fbda2ac8"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+ "target-features",
+]
+
+[[package]]
+name = "nix-compat"
+version = "0.1.0"
+dependencies = [
+ "bitflags 2.4.2",
+ "bstr",
+ "data-encoding",
+ "ed25519",
+ "ed25519-dalek",
+ "glob",
+ "nom",
+ "serde",
+ "serde_json",
+ "sha2",
+ "thiserror",
+]
+
+[[package]]
+name = "nom"
+version = "7.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
+dependencies = [
+ "memchr",
+ "minimal-lexical",
+]
+
+[[package]]
+name = "now"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d89e9874397a1f0a52fc1f197a8effd9735223cb2390e9dcc83ac6cd02923d0"
+dependencies = [
+ "chrono",
+]
+
+[[package]]
+name = "ntapi"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a"
+dependencies = [
+ "autocfg",
+ "libm",
+]
+
+[[package]]
+name = "num_cpus"
+version = "1.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
+dependencies = [
+ "hermit-abi",
+ "libc",
+]
+
+[[package]]
+name = "object"
+version = "0.32.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
+
+[[package]]
+name = "owning_ref"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ff55baddef9e4ad00f88b6c743a2a8062d4c6ade126c2a528644b8e444d52ce"
+dependencies = [
+ "stable_deref_trait",
+]
+
+[[package]]
+name = "parking_lot"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
+dependencies = [
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "parquet-format-safe"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1131c54b167dd4e4799ce762e1ab01549ebb94d5bdd13e6ec1b467491c378e1f"
+dependencies = [
+ "async-trait",
+ "futures",
+]
+
+[[package]]
+name = "percent-encoding"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
+[[package]]
+name = "pkcs8"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
+dependencies = [
+ "der",
+ "spki",
+]
+
+[[package]]
+name = "pkg-config"
+version = "0.3.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2900ede94e305130c13ddd391e0ab7cbaeb783945ae07a279c268cb05109c6cb"
+
+[[package]]
+name = "planus"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc1691dd09e82f428ce8d6310bd6d5da2557c82ff17694d2a32cad7242aea89f"
+dependencies = [
+ "array-init-cursor",
+]
+
+[[package]]
+name = "platforms"
+version = "3.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "626dec3cac7cc0e1577a2ec3fc496277ec2baa084bebad95bb6fdbfae235f84c"
+
+[[package]]
+name = "polars"
+version = "0.36.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "938048fcda6a8e2ace6eb168bee1b415a92423ce51e418b853bf08fc40349b6b"
+dependencies = [
+ "getrandom",
+ "polars-core",
+ "polars-io",
+ "polars-lazy",
+ "polars-ops",
+ "polars-sql",
+ "polars-time",
+ "version_check",
+]
+
+[[package]]
+name = "polars-arrow"
+version = "0.36.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce68a02f698ff7787c261aea1b4c040a8fe183a8fb200e2436d7f35d95a1b86f"
+dependencies = [
+ "ahash",
+ "arrow-format",
+ "atoi_simd",
+ "bytemuck",
+ "chrono",
+ "dyn-clone",
+ "either",
+ "ethnum",
+ "fast-float",
+ "foreign_vec",
+ "futures",
+ "getrandom",
+ "hashbrown",
+ "itoa",
+ "lz4",
+ "multiversion",
+ "num-traits",
+ "polars-error",
+ "polars-utils",
+ "ryu",
+ "simdutf8",
+ "streaming-iterator",
+ "strength_reduce",
+ "version_check",
+ "zstd",
+]
+
+[[package]]
+name = "polars-compute"
+version = "0.36.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b14fbc5f141b29b656a4cec4802632e5bff10bf801c6809c6bbfbd4078a044dd"
+dependencies = [
+ "bytemuck",
+ "num-traits",
+ "polars-arrow",
+ "polars-utils",
+ "version_check",
+]
+
+[[package]]
+name = "polars-core"
+version = "0.36.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0f5efe734b6cbe5f97ea769be8360df5324fade396f1f3f5ad7fe9360ca4a23"
+dependencies = [
+ "ahash",
+ "bitflags 2.4.2",
+ "bytemuck",
+ "chrono",
+ "comfy-table",
+ "either",
+ "hashbrown",
+ "indexmap",
+ "num-traits",
+ "once_cell",
+ "polars-arrow",
+ "polars-compute",
+ "polars-error",
+ "polars-row",
+ "polars-utils",
+ "rand",
+ "rand_distr",
+ "rayon",
+ "regex",
+ "smartstring",
+ "thiserror",
+ "version_check",
+ "xxhash-rust",
+]
+
+[[package]]
+name = "polars-error"
+version = "0.36.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6396de788f99ebfc9968e7b6f523e23000506cde4ba6dfc62ae4ce949002a886"
+dependencies = [
+ "arrow-format",
+ "regex",
+ "simdutf8",
+ "thiserror",
+]
+
+[[package]]
+name = "polars-io"
+version = "0.36.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d0458efe8946f4718fd352f230c0db5a37926bd0d2bd25af79dc24746abaaea"
+dependencies = [
+ "ahash",
+ "async-trait",
+ "atoi_simd",
+ "bytes",
+ "chrono",
+ "fast-float",
+ "futures",
+ "home",
+ "itoa",
+ "memchr",
+ "memmap2",
+ "num-traits",
+ "once_cell",
+ "percent-encoding",
+ "polars-arrow",
+ "polars-core",
+ "polars-error",
+ "polars-parquet",
+ "polars-time",
+ "polars-utils",
+ "rayon",
+ "regex",
+ "ryu",
+ "simdutf8",
+ "smartstring",
+ "tokio",
+ "tokio-util",
+]
+
+[[package]]
+name = "polars-lazy"
+version = "0.36.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d7105b40905bb38e8fc4a7fd736594b7491baa12fad3ac492969ca221a1b5d5"
+dependencies = [
+ "ahash",
+ "bitflags 2.4.2",
+ "glob",
+ "once_cell",
+ "polars-arrow",
+ "polars-core",
+ "polars-io",
+ "polars-ops",
+ "polars-pipe",
+ "polars-plan",
+ "polars-time",
+ "polars-utils",
+ "rayon",
+ "smartstring",
+ "version_check",
+]
+
+[[package]]
+name = "polars-ops"
+version = "0.36.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e09afc456ab11e75e5dcb43e00a01c71f3a46a2781e450054acb6bb096ca78e"
+dependencies = [
+ "ahash",
+ "argminmax",
+ "bytemuck",
+ "either",
+ "hashbrown",
+ "indexmap",
+ "memchr",
+ "num-traits",
+ "polars-arrow",
+ "polars-compute",
+ "polars-core",
+ "polars-error",
+ "polars-utils",
+ "rayon",
+ "regex",
+ "smartstring",
+ "version_check",
+]
+
+[[package]]
+name = "polars-parquet"
+version = "0.36.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ba24d67b1f64ab85143033dd46fa090b13c0f74acdf91b0780c16aecf005e3d"
+dependencies = [
+ "ahash",
+ "async-stream",
+ "base64",
+ "brotli",
+ "ethnum",
+ "flate2",
+ "futures",
+ "lz4",
+ "num-traits",
+ "parquet-format-safe",
+ "polars-arrow",
+ "polars-error",
+ "polars-utils",
+ "seq-macro",
+ "simdutf8",
+ "snap",
+ "streaming-decompression",
+ "zstd",
+]
+
+[[package]]
+name = "polars-pipe"
+version = "0.36.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b7ead073cc3917027d77b59861a9f071db47125de9314f8907db1a0a3e4100"
+dependencies = [
+ "crossbeam-channel",
+ "crossbeam-queue",
+ "enum_dispatch",
+ "hashbrown",
+ "num-traits",
+ "polars-arrow",
+ "polars-compute",
+ "polars-core",
+ "polars-io",
+ "polars-ops",
+ "polars-plan",
+ "polars-row",
+ "polars-utils",
+ "rayon",
+ "smartstring",
+ "version_check",
+]
+
+[[package]]
+name = "polars-plan"
+version = "0.36.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "384a175624d050c31c473ee11df9d7af5d729ae626375e522158cfb3d150acd0"
+dependencies = [
+ "ahash",
+ "bytemuck",
+ "once_cell",
+ "percent-encoding",
+ "polars-arrow",
+ "polars-core",
+ "polars-io",
+ "polars-ops",
+ "polars-parquet",
+ "polars-time",
+ "polars-utils",
+ "rayon",
+ "regex",
+ "smartstring",
+ "strum_macros",
+ "version_check",
+]
+
+[[package]]
+name = "polars-row"
+version = "0.36.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32322f7acbb83db3e9c7697dc821be73d06238da89c817dcc8bc1549a5e9c72f"
+dependencies = [
+ "polars-arrow",
+ "polars-error",
+ "polars-utils",
+]
+
+[[package]]
+name = "polars-sql"
+version = "0.36.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f0b4c6ddffdfd0453e84bc3918572c633014d661d166654399cf93752aa95b5"
+dependencies = [
+ "polars-arrow",
+ "polars-core",
+ "polars-error",
+ "polars-lazy",
+ "polars-plan",
+ "rand",
+ "serde",
+ "serde_json",
+ "sqlparser",
+]
+
+[[package]]
+name = "polars-time"
+version = "0.36.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dee2649fc96bd1b6584e0e4a4b3ca7d22ed3d117a990e63ad438ecb26f7544d0"
+dependencies = [
+ "atoi",
+ "chrono",
+ "now",
+ "once_cell",
+ "polars-arrow",
+ "polars-core",
+ "polars-error",
+ "polars-ops",
+ "polars-utils",
+ "regex",
+ "smartstring",
+]
+
+[[package]]
+name = "polars-utils"
+version = "0.36.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b174ca4a77ad47d7b91a0460aaae65bbf874c8bfbaaa5308675dadef3976bbda"
+dependencies = [
+ "ahash",
+ "bytemuck",
+ "hashbrown",
+ "indexmap",
+ "num-traits",
+ "once_cell",
+ "polars-error",
+ "rayon",
+ "smartstring",
+ "sysinfo",
+ "version_check",
+]
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.78"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "libc",
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "rand_distr"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31"
+dependencies = [
+ "num-traits",
+ "rand",
+]
+
+[[package]]
+name = "rayon"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa7237101a77a10773db45d62004a272517633fbcc3df19d96455ede1122e051"
+dependencies = [
+ "either",
+ "rayon-core",
+]
+
+[[package]]
+name = "rayon-core"
+version = "1.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
+dependencies = [
+ "crossbeam-deque",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
+dependencies = [
+ "bitflags 1.3.2",
+]
+
+[[package]]
+name = "regex"
+version = "1.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
+
+[[package]]
+name = "rustc-demangle"
+version = "0.1.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
+
+[[package]]
+name = "rustc_version"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
+dependencies = [
+ "semver",
+]
+
+[[package]]
+name = "rustversion"
+version = "1.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4"
+
+[[package]]
+name = "ryu"
+version = "1.0.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c"
+
+[[package]]
+name = "scopeguard"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+
+[[package]]
+name = "semver"
+version = "1.0.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0"
+
+[[package]]
+name = "seq-macro"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a3f0bf26fd526d2a95683cd0f87bf103b8539e2ca1ef48ce002d67aad59aa0b4"
+
+[[package]]
+name = "serde"
+version = "1.0.196"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.196"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.113"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "sha2"
+version = "0.10.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
+[[package]]
+name = "signal-hook-registry"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "signature"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
+dependencies = [
+ "rand_core",
+]
+
+[[package]]
+name = "simdutf8"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a"
+
+[[package]]
+name = "slab"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "smallvec"
+version = "1.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7"
+
+[[package]]
+name = "smartstring"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29"
+dependencies = [
+ "autocfg",
+ "static_assertions",
+ "version_check",
+]
+
+[[package]]
+name = "snap"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b6b67fb9a61334225b5b790716f609cd58395f895b3fe8b328786812a40bc3b"
+
+[[package]]
+name = "socket2"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9"
+dependencies = [
+ "libc",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "spki"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
+dependencies = [
+ "base64ct",
+ "der",
+]
+
+[[package]]
+name = "sqlparser"
+version = "0.39.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "743b4dc2cbde11890ccb254a8fc9d537fa41b36da00de2a1c5e9848c9bc42bd7"
+dependencies = [
+ "log",
+]
+
+[[package]]
+name = "stable_deref_trait"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
+
+[[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
+[[package]]
+name = "streaming-decompression"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf6cc3b19bfb128a8ad11026086e31d3ce9ad23f8ea37354b31383a187c44cf3"
+dependencies = [
+ "fallible-streaming-iterator",
+]
+
+[[package]]
+name = "streaming-iterator"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b2231b7c3057d5e4ad0156fb3dc807d900806020c5ffa3ee6ff2c8c76fb8520"
+
+[[package]]
+name = "strength_reduce"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82"
+
+[[package]]
+name = "strum"
+version = "0.25.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125"
+
+[[package]]
+name = "strum_macros"
+version = "0.25.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "rustversion",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "subtle"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
+
+[[package]]
+name = "syn"
+version = "1.0.109"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.48"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "sysinfo"
+version = "0.30.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fb4f3438c8f6389c864e61221cbc97e9bca98b4daf39a5beb7bea660f528bb2"
+dependencies = [
+ "cfg-if",
+ "core-foundation-sys",
+ "libc",
+ "ntapi",
+ "once_cell",
+ "windows",
+]
+
+[[package]]
+name = "target-features"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfb5fa503293557c5158bd215fdc225695e567a77e453f5d4452a50a193969bd"
+
+[[package]]
+name = "thiserror"
+version = "1.0.56"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.56"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "tokio"
+version = "1.36.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931"
+dependencies = [
+ "backtrace",
+ "bytes",
+ "libc",
+ "mio",
+ "num_cpus",
+ "parking_lot",
+ "pin-project-lite",
+ "signal-hook-registry",
+ "socket2",
+ "tokio-macros",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "tokio-macros"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "tokio-util"
+version = "0.7.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "futures-sink",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "typenum"
+version = "1.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+
+[[package]]
+name = "unicode-width"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
+
+[[package]]
+name = "version_check"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.91"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1e124130aee3fb58c5bdd6b639a0509486b0338acaaae0c84a5124b0f588b7f"
+dependencies = [
+ "cfg-if",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.91"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c9e7e1900c352b609c8488ad12639a311045f40a35491fb69ba8c12f758af70b"
+dependencies = [
+ "bumpalo",
+ "log",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.91"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b30af9e2d358182b5c7449424f017eba305ed32a7010509ede96cdc4696c46ed"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.91"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.91"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838"
+
+[[package]]
+name = "weave"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "hashbrown",
+ "nix-compat",
+ "owning_ref",
+ "polars",
+ "rayon",
+ "tokio",
+]
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "windows"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be"
+dependencies = [
+ "windows-core",
+ "windows-targets 0.52.0",
+]
+
+[[package]]
+name = "windows-core"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
+dependencies = [
+ "windows-targets 0.52.0",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+dependencies = [
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets 0.52.0",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
+dependencies = [
+ "windows_aarch64_gnullvm 0.48.5",
+ "windows_aarch64_msvc 0.48.5",
+ "windows_i686_gnu 0.48.5",
+ "windows_i686_msvc 0.48.5",
+ "windows_x86_64_gnu 0.48.5",
+ "windows_x86_64_gnullvm 0.48.5",
+ "windows_x86_64_msvc 0.48.5",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
+dependencies = [
+ "windows_aarch64_gnullvm 0.52.0",
+ "windows_aarch64_msvc 0.52.0",
+ "windows_i686_gnu 0.52.0",
+ "windows_i686_msvc 0.52.0",
+ "windows_x86_64_gnu 0.52.0",
+ "windows_x86_64_gnullvm 0.52.0",
+ "windows_x86_64_msvc 0.52.0",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
+
+[[package]]
+name = "xxhash-rust"
+version = "0.8.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53be06678ed9e83edb1745eb72efc0bbcd7b5c3c35711a860906aed827a13d61"
+
+[[package]]
+name = "zerocopy"
+version = "0.7.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be"
+dependencies = [
+ "zerocopy-derive",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.7.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "zeroize"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d"
+
+[[package]]
+name = "zstd"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bffb3309596d527cfcba7dfc6ed6052f1d39dfbd7c867aa2e865e4a449c10110"
+dependencies = [
+ "zstd-safe",
+]
+
+[[package]]
+name = "zstd-safe"
+version = "7.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43747c7422e2924c11144d5229878b98180ef8b06cca4ab5af37afc8a8d8ea3e"
+dependencies = [
+ "zstd-sys",
+]
+
+[[package]]
+name = "zstd-sys"
+version = "2.0.9+zstd.1.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656"
+dependencies = [
+ "cc",
+ "pkg-config",
+]
diff --git a/tvix/tools/weave/Cargo.nix b/tvix/tools/weave/Cargo.nix
new file mode 100644
index 0000000000..650b655a2d
--- /dev/null
+++ b/tvix/tools/weave/Cargo.nix
@@ -0,0 +1,8809 @@
+# This file was @generated by crate2nix 0.13.0 with the command:
+#   "generate" "--all-features"
+# See https://github.com/kolloch/crate2nix for more info.
+
+{ nixpkgs ? <nixpkgs>
+, pkgs ? import nixpkgs { config = { }; }
+, lib ? pkgs.lib
+, stdenv ? pkgs.stdenv
+, buildRustCrateForPkgs ? pkgs: pkgs.buildRustCrate
+  # This is used as the `crateOverrides` argument for `buildRustCrate`.
+, defaultCrateOverrides ? pkgs.defaultCrateOverrides
+  # The features to enable for the root_crate or the workspace_members.
+, rootFeatures ? [ "default" ]
+  # If true, throw errors instead of issueing deprecation warnings.
+, strictDeprecation ? false
+  # Used for conditional compilation based on CPU feature detection.
+, targetFeatures ? [ ]
+  # Whether to perform release builds: longer compile times, faster binaries.
+, release ? true
+  # Additional crate2nix configuration if it exists.
+, crateConfig ? if builtins.pathExists ./crate-config.nix
+  then pkgs.callPackage ./crate-config.nix { }
+  else { }
+}:
+
+rec {
+  #
+  # "public" attributes that we attempt to keep stable with new versions of crate2nix.
+  #
+
+  rootCrate = rec {
+    packageId = "weave";
+
+    # Use this attribute to refer to the derivation building your root crate package.
+    # You can override the features with rootCrate.build.override { features = [ "default" "feature1" ... ]; }.
+    build = internal.buildRustCrateWithFeatures {
+      inherit packageId;
+    };
+
+    # Debug support which might change between releases.
+    # File a bug if you depend on any for non-debug work!
+    debug = internal.debugCrate { inherit packageId; };
+  };
+  # Refer your crate build derivation by name here.
+  # You can override the features with
+  # workspaceMembers."${crateName}".build.override { features = [ "default" "feature1" ... ]; }.
+  workspaceMembers = {
+    "weave" = rec {
+      packageId = "weave";
+      build = internal.buildRustCrateWithFeatures {
+        packageId = "weave";
+      };
+
+      # Debug support which might change between releases.
+      # File a bug if you depend on any for non-debug work!
+      debug = internal.debugCrate { inherit packageId; };
+    };
+  };
+
+  # A derivation that joins the outputs of all workspace members together.
+  allWorkspaceMembers = pkgs.symlinkJoin {
+    name = "all-workspace-members";
+    paths =
+      let members = builtins.attrValues workspaceMembers;
+      in builtins.map (m: m.build) members;
+  };
+
+  #
+  # "internal" ("private") attributes that may change in every new version of crate2nix.
+  #
+
+  internal = rec {
+    # Build and dependency information for crates.
+    # Many of the fields are passed one-to-one to buildRustCrate.
+    #
+    # Noteworthy:
+    # * `dependencies`/`buildDependencies`: similar to the corresponding fields for buildRustCrate.
+    #   but with additional information which is used during dependency/feature resolution.
+    # * `resolvedDependencies`: the selected default features reported by cargo - only included for debugging.
+    # * `devDependencies` as of now not used by `buildRustCrate` but used to
+    #   inject test dependencies into the build
+
+    crates = {
+      "addr2line" = rec {
+        crateName = "addr2line";
+        version = "0.21.0";
+        edition = "2018";
+        sha256 = "1jx0k3iwyqr8klqbzk6kjvr496yd94aspis10vwsj5wy7gib4c4a";
+        dependencies = [
+          {
+            name = "gimli";
+            packageId = "gimli";
+            usesDefaultFeatures = false;
+            features = [ "read" ];
+          }
+        ];
+        features = {
+          "alloc" = [ "dep:alloc" ];
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "core" = [ "dep:core" ];
+          "cpp_demangle" = [ "dep:cpp_demangle" ];
+          "default" = [ "rustc-demangle" "cpp_demangle" "std-object" "fallible-iterator" "smallvec" "memmap2" ];
+          "fallible-iterator" = [ "dep:fallible-iterator" ];
+          "memmap2" = [ "dep:memmap2" ];
+          "object" = [ "dep:object" ];
+          "rustc-demangle" = [ "dep:rustc-demangle" ];
+          "rustc-dep-of-std" = [ "core" "alloc" "compiler_builtins" "gimli/rustc-dep-of-std" ];
+          "smallvec" = [ "dep:smallvec" ];
+          "std" = [ "gimli/std" ];
+          "std-object" = [ "std" "object" "object/std" "object/compression" "gimli/endian-reader" ];
+        };
+      };
+      "adler" = rec {
+        crateName = "adler";
+        version = "1.0.2";
+        edition = "2015";
+        sha256 = "1zim79cvzd5yrkzl3nyfx0avijwgk9fqv3yrscdy1cc79ih02qpj";
+        authors = [
+          "Jonas Schievink <jonasschievink@gmail.com>"
+        ];
+        features = {
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "core" = [ "dep:core" ];
+          "default" = [ "std" ];
+          "rustc-dep-of-std" = [ "core" "compiler_builtins" ];
+        };
+      };
+      "ahash" = rec {
+        crateName = "ahash";
+        version = "0.8.7";
+        edition = "2018";
+        sha256 = "008xw6gigwnf0q01ic4ar2y4dqfnzn3kyys6vd4cvfa3imjakhvp";
+        authors = [
+          "Tom Kaitchuck <Tom.Kaitchuck@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "getrandom";
+            packageId = "getrandom";
+            optional = true;
+          }
+          {
+            name = "once_cell";
+            packageId = "once_cell";
+            usesDefaultFeatures = false;
+            target = { target, features }: (!(("arm" == target."arch" or null) && ("none" == target."os" or null)));
+            features = [ "alloc" ];
+          }
+          {
+            name = "zerocopy";
+            packageId = "zerocopy";
+            usesDefaultFeatures = false;
+            features = [ "simd" ];
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "version_check";
+            packageId = "version_check";
+          }
+        ];
+        features = {
+          "atomic-polyfill" = [ "dep:atomic-polyfill" "once_cell/atomic-polyfill" ];
+          "compile-time-rng" = [ "const-random" ];
+          "const-random" = [ "dep:const-random" ];
+          "default" = [ "std" "runtime-rng" ];
+          "getrandom" = [ "dep:getrandom" ];
+          "runtime-rng" = [ "getrandom" ];
+          "serde" = [ "dep:serde" ];
+        };
+        resolvedDefaultFeatures = [ "default" "getrandom" "runtime-rng" "std" ];
+      };
+      "aho-corasick" = rec {
+        crateName = "aho-corasick";
+        version = "1.1.2";
+        edition = "2021";
+        sha256 = "1w510wnixvlgimkx1zjbvlxh6xps2vjgfqgwf5a6adlbjp5rv5mj";
+        libName = "aho_corasick";
+        authors = [
+          "Andrew Gallant <jamslam@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "memchr";
+            packageId = "memchr";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "default" = [ "std" "perf-literal" ];
+          "logging" = [ "dep:log" ];
+          "perf-literal" = [ "dep:memchr" ];
+          "std" = [ "memchr?/std" ];
+        };
+        resolvedDefaultFeatures = [ "perf-literal" "std" ];
+      };
+      "alloc-no-stdlib" = rec {
+        crateName = "alloc-no-stdlib";
+        version = "2.0.4";
+        edition = "2015";
+        crateBin = [ ];
+        sha256 = "1cy6r2sfv5y5cigv86vms7n5nlwhx1rbyxwcraqnmm1rxiib2yyc";
+        authors = [
+          "Daniel Reiter Horn <danielrh@dropbox.com>"
+        ];
+        features = { };
+      };
+      "alloc-stdlib" = rec {
+        crateName = "alloc-stdlib";
+        version = "0.2.2";
+        edition = "2015";
+        crateBin = [ ];
+        sha256 = "1kkfbld20ab4165p29v172h8g0wvq8i06z8vnng14whw0isq5ywl";
+        authors = [
+          "Daniel Reiter Horn <danielrh@dropbox.com>"
+        ];
+        dependencies = [
+          {
+            name = "alloc-no-stdlib";
+            packageId = "alloc-no-stdlib";
+          }
+        ];
+        features = { };
+      };
+      "allocator-api2" = rec {
+        crateName = "allocator-api2";
+        version = "0.2.16";
+        edition = "2018";
+        sha256 = "1iayppgq4wqbfbfcqmsbwgamj0s65012sskfvyx07pxavk3gyhh9";
+        authors = [
+          "Zakarum <zaq.dev@icloud.com>"
+        ];
+        features = {
+          "default" = [ "std" ];
+          "serde" = [ "dep:serde" ];
+          "std" = [ "alloc" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" ];
+      };
+      "android-tzdata" = rec {
+        crateName = "android-tzdata";
+        version = "0.1.1";
+        edition = "2018";
+        sha256 = "1w7ynjxrfs97xg3qlcdns4kgfpwcdv824g611fq32cag4cdr96g9";
+        authors = [
+          "RumovZ"
+        ];
+
+      };
+      "android_system_properties" = rec {
+        crateName = "android_system_properties";
+        version = "0.1.5";
+        edition = "2018";
+        sha256 = "04b3wrz12837j7mdczqd95b732gw5q7q66cv4yn4646lvccp57l1";
+        authors = [
+          "Nicolas Silva <nical@fastmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+          }
+        ];
+
+      };
+      "anyhow" = rec {
+        crateName = "anyhow";
+        version = "1.0.79";
+        edition = "2018";
+        sha256 = "1ji5irqiwr8yprgqj8zvnli7zd7fz9kzaiddq44jnrl2l289h3h8";
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "backtrace";
+            packageId = "backtrace";
+            optional = true;
+          }
+        ];
+        features = {
+          "backtrace" = [ "dep:backtrace" ];
+          "default" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "backtrace" "default" "std" ];
+      };
+      "argminmax" = rec {
+        crateName = "argminmax";
+        version = "0.6.1";
+        edition = "2021";
+        sha256 = "1lnvpkvdsvdbsinhik6srx5c2j3gqkaj92iz93pnbdr9cjs0h890";
+        authors = [
+          "Jeroen Van Der Donckt"
+        ];
+        dependencies = [
+          {
+            name = "num-traits";
+            packageId = "num-traits";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "arrow" = [ "dep:arrow" ];
+          "arrow2" = [ "dep:arrow2" ];
+          "default" = [ "nightly_simd" "float" ];
+          "half" = [ "dep:half" ];
+          "ndarray" = [ "dep:ndarray" ];
+        };
+        resolvedDefaultFeatures = [ "float" ];
+      };
+      "array-init-cursor" = rec {
+        crateName = "array-init-cursor";
+        version = "0.2.0";
+        edition = "2021";
+        sha256 = "0xpbqf7qkvzplpjd7f0wbcf2n1v9vygdccwxkd1amxp4il0hlzdz";
+
+      };
+      "arrow-format" = rec {
+        crateName = "arrow-format";
+        version = "0.8.1";
+        edition = "2018";
+        sha256 = "1irj67p6c224dzw86jr7j3z9r5zfid52gy6ml8rdqk4r2si4x207";
+        authors = [
+          "Jorge C. Leitao <jorgecarleitao@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "planus";
+            packageId = "planus";
+            optional = true;
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+            optional = true;
+            usesDefaultFeatures = false;
+            features = [ "derive" "std" ];
+          }
+        ];
+        features = {
+          "flight-data" = [ "prost" "prost-derive" ];
+          "flight-service" = [ "flight-data" "tonic" ];
+          "full" = [ "ipc" "flight-data" "flight-service" ];
+          "ipc" = [ "planus" "serde" ];
+          "planus" = [ "dep:planus" ];
+          "prost" = [ "dep:prost" ];
+          "prost-derive" = [ "dep:prost-derive" ];
+          "serde" = [ "dep:serde" ];
+          "tonic" = [ "dep:tonic" ];
+        };
+        resolvedDefaultFeatures = [ "default" "ipc" "planus" "serde" ];
+      };
+      "async-stream" = rec {
+        crateName = "async-stream";
+        version = "0.3.5";
+        edition = "2018";
+        sha256 = "0l8sjq1rylkb1ak0pdyjn83b3k6x36j22myngl4sqqgg7whdsmnd";
+        authors = [
+          "Carl Lerche <me@carllerche.com>"
+        ];
+        dependencies = [
+          {
+            name = "async-stream-impl";
+            packageId = "async-stream-impl";
+          }
+          {
+            name = "futures-core";
+            packageId = "futures-core";
+          }
+          {
+            name = "pin-project-lite";
+            packageId = "pin-project-lite";
+          }
+        ];
+
+      };
+      "async-stream-impl" = rec {
+        crateName = "async-stream-impl";
+        version = "0.3.5";
+        edition = "2018";
+        sha256 = "14q179j4y8p2z1d0ic6aqgy9fhwz8p9cai1ia8kpw4bw7q12mrhn";
+        procMacro = true;
+        authors = [
+          "Carl Lerche <me@carllerche.com>"
+        ];
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn 2.0.48";
+            features = [ "full" "visit-mut" ];
+          }
+        ];
+
+      };
+      "async-trait" = rec {
+        crateName = "async-trait";
+        version = "0.1.77";
+        edition = "2021";
+        sha256 = "1adf1jh2yg39rkpmqjqyr9xyd6849p0d95425i6imgbhx0syx069";
+        procMacro = true;
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn 2.0.48";
+            features = [ "full" "visit-mut" ];
+          }
+        ];
+
+      };
+      "atoi" = rec {
+        crateName = "atoi";
+        version = "2.0.0";
+        edition = "2021";
+        sha256 = "0a05h42fggmy7h0ajjv6m7z72l924i7igbx13hk9d8pyign9k3gj";
+        authors = [
+          "Markus Klein"
+        ];
+        dependencies = [
+          {
+            name = "num-traits";
+            packageId = "num-traits";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "std" = [ "num-traits/std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "atoi_simd" = rec {
+        crateName = "atoi_simd";
+        version = "0.15.6";
+        edition = "2018";
+        sha256 = "1a98kvaqyhb1shi2c6qhvklahc7ckvpmibcy319i6g1i9xqkgq4s";
+        authors = [
+          "Dmitry Rodionov <gh@rdmtr.com>"
+        ];
+        features = {
+          "default" = [ "std" ];
+          "std" = [ "arrayvec/std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "autocfg" = rec {
+        crateName = "autocfg";
+        version = "1.1.0";
+        edition = "2015";
+        sha256 = "1ylp3cb47ylzabimazvbz9ms6ap784zhb6syaz6c1jqpmcmq0s6l";
+        authors = [
+          "Josh Stone <cuviper@gmail.com>"
+        ];
+
+      };
+      "backtrace" = rec {
+        crateName = "backtrace";
+        version = "0.3.69";
+        edition = "2018";
+        sha256 = "0dsq23dhw4pfndkx2nsa1ml2g31idm7ss7ljxp8d57avygivg290";
+        authors = [
+          "The Rust Project Developers"
+        ];
+        dependencies = [
+          {
+            name = "addr2line";
+            packageId = "addr2line";
+            usesDefaultFeatures = false;
+            target = { target, features }: (!((target."windows" or false) && ("msvc" == target."env" or null) && (!("uwp" == target."vendor" or null))));
+          }
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            usesDefaultFeatures = false;
+            target = { target, features }: (!((target."windows" or false) && ("msvc" == target."env" or null) && (!("uwp" == target."vendor" or null))));
+          }
+          {
+            name = "miniz_oxide";
+            packageId = "miniz_oxide";
+            usesDefaultFeatures = false;
+            target = { target, features }: (!((target."windows" or false) && ("msvc" == target."env" or null) && (!("uwp" == target."vendor" or null))));
+          }
+          {
+            name = "object";
+            packageId = "object";
+            usesDefaultFeatures = false;
+            target = { target, features }: (!((target."windows" or false) && ("msvc" == target."env" or null) && (!("uwp" == target."vendor" or null))));
+            features = [ "read_core" "elf" "macho" "pe" "unaligned" "archive" ];
+          }
+          {
+            name = "rustc-demangle";
+            packageId = "rustc-demangle";
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "cc";
+            packageId = "cc";
+          }
+        ];
+        features = {
+          "cpp_demangle" = [ "dep:cpp_demangle" ];
+          "default" = [ "std" ];
+          "rustc-serialize" = [ "dep:rustc-serialize" ];
+          "serde" = [ "dep:serde" ];
+          "serialize-rustc" = [ "rustc-serialize" ];
+          "serialize-serde" = [ "serde" ];
+          "verify-winapi" = [ "winapi/dbghelp" "winapi/handleapi" "winapi/libloaderapi" "winapi/memoryapi" "winapi/minwindef" "winapi/processthreadsapi" "winapi/synchapi" "winapi/tlhelp32" "winapi/winbase" "winapi/winnt" ];
+          "winapi" = [ "dep:winapi" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "base64" = rec {
+        crateName = "base64";
+        version = "0.21.7";
+        edition = "2018";
+        sha256 = "0rw52yvsk75kar9wgqfwgb414kvil1gn7mqkrhn9zf1537mpsacx";
+        authors = [
+          "Alice Maz <alice@alicemaz.com>"
+          "Marshall Pierce <marshall@mpierce.org>"
+        ];
+        features = {
+          "default" = [ "std" ];
+          "std" = [ "alloc" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "std" ];
+      };
+      "base64ct" = rec {
+        crateName = "base64ct";
+        version = "1.6.0";
+        edition = "2021";
+        sha256 = "0nvdba4jb8aikv60az40x2w1y96sjdq8z3yp09rwzmkhiwv1lg4c";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        features = {
+          "std" = [ "alloc" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" ];
+      };
+      "bitflags 1.3.2" = rec {
+        crateName = "bitflags";
+        version = "1.3.2";
+        edition = "2018";
+        sha256 = "12ki6w8gn1ldq7yz9y680llwk5gmrhrzszaa17g1sbrw2r2qvwxy";
+        authors = [
+          "The Rust Project Developers"
+        ];
+        features = {
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "core" = [ "dep:core" ];
+          "rustc-dep-of-std" = [ "core" "compiler_builtins" ];
+        };
+        resolvedDefaultFeatures = [ "default" ];
+      };
+      "bitflags 2.4.2" = rec {
+        crateName = "bitflags";
+        version = "2.4.2";
+        edition = "2021";
+        sha256 = "1pqd142hyqlzr7p9djxq2ff0jx07a2sb2xp9lhw69cbf80s0jmzd";
+        authors = [
+          "The Rust Project Developers"
+        ];
+        features = {
+          "arbitrary" = [ "dep:arbitrary" ];
+          "bytemuck" = [ "dep:bytemuck" ];
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "core" = [ "dep:core" ];
+          "rustc-dep-of-std" = [ "core" "compiler_builtins" ];
+          "serde" = [ "dep:serde" ];
+        };
+      };
+      "block-buffer" = rec {
+        crateName = "block-buffer";
+        version = "0.10.4";
+        edition = "2018";
+        sha256 = "0w9sa2ypmrsqqvc20nhwr75wbb5cjr4kkyhpjm1z1lv2kdicfy1h";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        dependencies = [
+          {
+            name = "generic-array";
+            packageId = "generic-array";
+          }
+        ];
+
+      };
+      "brotli" = rec {
+        crateName = "brotli";
+        version = "3.4.0";
+        edition = "2015";
+        crateBin = [ ];
+        sha256 = "03qhcq09a6f8y4gm0bmsn7jrq5804cwpkcx3fyay1g7lgsj78q2i";
+        authors = [
+          "Daniel Reiter Horn <danielrh@dropbox.com>"
+          "The Brotli Authors"
+        ];
+        dependencies = [
+          {
+            name = "alloc-no-stdlib";
+            packageId = "alloc-no-stdlib";
+          }
+          {
+            name = "alloc-stdlib";
+            packageId = "alloc-stdlib";
+            optional = true;
+          }
+          {
+            name = "brotli-decompressor";
+            packageId = "brotli-decompressor";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "alloc-stdlib" = [ "dep:alloc-stdlib" ];
+          "benchmark" = [ "brotli-decompressor/benchmark" ];
+          "default" = [ "std" "ffi-api" ];
+          "disable-timer" = [ "brotli-decompressor/disable-timer" ];
+          "seccomp" = [ "brotli-decompressor/seccomp" ];
+          "sha2" = [ "dep:sha2" ];
+          "std" = [ "alloc-stdlib" "brotli-decompressor/std" ];
+          "validation" = [ "sha2" ];
+        };
+        resolvedDefaultFeatures = [ "alloc-stdlib" "default" "ffi-api" "std" ];
+      };
+      "brotli-decompressor" = rec {
+        crateName = "brotli-decompressor";
+        version = "2.5.1";
+        edition = "2015";
+        crateBin = [ ];
+        sha256 = "0kyyh9701dwqzwvn2frff4ww0zibikqd1s1xvl7n1pfpc3z4lbjf";
+        authors = [
+          "Daniel Reiter Horn <danielrh@dropbox.com>"
+          "The Brotli Authors"
+        ];
+        dependencies = [
+          {
+            name = "alloc-no-stdlib";
+            packageId = "alloc-no-stdlib";
+          }
+          {
+            name = "alloc-stdlib";
+            packageId = "alloc-stdlib";
+            optional = true;
+          }
+        ];
+        features = {
+          "alloc-stdlib" = [ "dep:alloc-stdlib" ];
+          "default" = [ "std" ];
+          "std" = [ "alloc-stdlib" ];
+          "unsafe" = [ "alloc-no-stdlib/unsafe" "alloc-stdlib/unsafe" ];
+        };
+        resolvedDefaultFeatures = [ "alloc-stdlib" "std" ];
+      };
+      "bstr" = rec {
+        crateName = "bstr";
+        version = "1.9.0";
+        edition = "2021";
+        sha256 = "1p6hzf3wqwwynv6w4pn17jg21amfafph9kb5sfvf1idlli8h13y4";
+        authors = [
+          "Andrew Gallant <jamslam@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "memchr";
+            packageId = "memchr";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "regex-automata";
+            packageId = "regex-automata";
+            optional = true;
+            usesDefaultFeatures = false;
+            features = [ "dfa-search" ];
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "alloc" = [ "memchr/alloc" "serde?/alloc" ];
+          "default" = [ "std" "unicode" ];
+          "serde" = [ "dep:serde" ];
+          "std" = [ "alloc" "memchr/std" "serde?/std" ];
+          "unicode" = [ "dep:regex-automata" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "serde" "std" "unicode" ];
+      };
+      "bumpalo" = rec {
+        crateName = "bumpalo";
+        version = "3.14.0";
+        edition = "2021";
+        sha256 = "1v4arnv9kwk54v5d0qqpv4vyw2sgr660nk0w3apzixi1cm3yfc3z";
+        authors = [
+          "Nick Fitzgerald <fitzgen@gmail.com>"
+        ];
+        features = {
+          "allocator-api2" = [ "dep:allocator-api2" ];
+        };
+        resolvedDefaultFeatures = [ "default" ];
+      };
+      "bytemuck" = rec {
+        crateName = "bytemuck";
+        version = "1.14.2";
+        edition = "2018";
+        sha256 = "0aylwb0l3zx2c212k2nwik4zmbhw5826y7icav0w2ja9vadxccga";
+        authors = [
+          "Lokathor <zefria@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "bytemuck_derive";
+            packageId = "bytemuck_derive";
+            optional = true;
+          }
+        ];
+        features = {
+          "bytemuck_derive" = [ "dep:bytemuck_derive" ];
+          "derive" = [ "bytemuck_derive" ];
+          "extern_crate_std" = [ "extern_crate_alloc" ];
+        };
+        resolvedDefaultFeatures = [ "bytemuck_derive" "derive" "extern_crate_alloc" ];
+      };
+      "bytemuck_derive" = rec {
+        crateName = "bytemuck_derive";
+        version = "1.5.0";
+        edition = "2018";
+        sha256 = "1cgj75df2v32l4fmvnp25xxkkz4lp6hz76f7hfhd55wgbzmvfnln";
+        procMacro = true;
+        authors = [
+          "Lokathor <zefria@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn 2.0.48";
+          }
+        ];
+
+      };
+      "bytes" = rec {
+        crateName = "bytes";
+        version = "1.5.0";
+        edition = "2018";
+        sha256 = "08w2i8ac912l8vlvkv3q51cd4gr09pwlg3sjsjffcizlrb0i5gd2";
+        authors = [
+          "Carl Lerche <me@carllerche.com>"
+          "Sean McArthur <sean@seanmonstar.com>"
+        ];
+        features = {
+          "default" = [ "std" ];
+          "serde" = [ "dep:serde" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "cc" = rec {
+        crateName = "cc";
+        version = "1.0.83";
+        edition = "2018";
+        crateBin = [ ];
+        sha256 = "1l643zidlb5iy1dskc5ggqs4wqa29a02f44piczqc8zcnsq4y5zi";
+        authors = [
+          "Alex Crichton <alex@alexcrichton.com>"
+        ];
+        dependencies = [
+          {
+            name = "jobserver";
+            packageId = "jobserver";
+            optional = true;
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            usesDefaultFeatures = false;
+            target = { target, features }: (target."unix" or false);
+          }
+        ];
+        features = {
+          "jobserver" = [ "dep:jobserver" ];
+          "parallel" = [ "jobserver" ];
+        };
+        resolvedDefaultFeatures = [ "jobserver" "parallel" ];
+      };
+      "cfg-if" = rec {
+        crateName = "cfg-if";
+        version = "1.0.0";
+        edition = "2018";
+        sha256 = "1za0vb97n4brpzpv8lsbnzmq5r8f2b0cpqqr0sy8h5bn751xxwds";
+        authors = [
+          "Alex Crichton <alex@alexcrichton.com>"
+        ];
+        features = {
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "core" = [ "dep:core" ];
+          "rustc-dep-of-std" = [ "core" "compiler_builtins" ];
+        };
+      };
+      "chrono" = rec {
+        crateName = "chrono";
+        version = "0.4.33";
+        edition = "2021";
+        sha256 = "1szr180x4srkwvmzq5ahqnf3m7yjjllfmgp7k3hsrr556l76j4wz";
+        dependencies = [
+          {
+            name = "android-tzdata";
+            packageId = "android-tzdata";
+            optional = true;
+            target = { target, features }: ("android" == target."os" or null);
+          }
+          {
+            name = "iana-time-zone";
+            packageId = "iana-time-zone";
+            optional = true;
+            target = { target, features }: (target."unix" or false);
+            features = [ "fallback" ];
+          }
+          {
+            name = "num-traits";
+            packageId = "num-traits";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "windows-targets";
+            packageId = "windows-targets 0.52.0";
+            optional = true;
+            target = { target, features }: (target."windows" or false);
+          }
+        ];
+        features = {
+          "android-tzdata" = [ "dep:android-tzdata" ];
+          "arbitrary" = [ "dep:arbitrary" ];
+          "clock" = [ "winapi" "iana-time-zone" "android-tzdata" "now" ];
+          "default" = [ "clock" "std" "oldtime" "wasmbind" ];
+          "iana-time-zone" = [ "dep:iana-time-zone" ];
+          "js-sys" = [ "dep:js-sys" ];
+          "now" = [ "std" ];
+          "pure-rust-locales" = [ "dep:pure-rust-locales" ];
+          "rkyv" = [ "dep:rkyv" "rkyv/size_32" ];
+          "rkyv-16" = [ "dep:rkyv" "rkyv?/size_16" ];
+          "rkyv-32" = [ "dep:rkyv" "rkyv?/size_32" ];
+          "rkyv-64" = [ "dep:rkyv" "rkyv?/size_64" ];
+          "rkyv-validation" = [ "rkyv?/validation" ];
+          "rustc-serialize" = [ "dep:rustc-serialize" ];
+          "serde" = [ "dep:serde" ];
+          "std" = [ "alloc" ];
+          "unstable-locales" = [ "pure-rust-locales" ];
+          "wasm-bindgen" = [ "dep:wasm-bindgen" ];
+          "wasmbind" = [ "wasm-bindgen" "js-sys" ];
+          "winapi" = [ "windows-targets" ];
+          "windows-targets" = [ "dep:windows-targets" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "android-tzdata" "clock" "iana-time-zone" "now" "std" "winapi" "windows-targets" ];
+      };
+      "comfy-table" = rec {
+        crateName = "comfy-table";
+        version = "7.1.0";
+        edition = "2021";
+        sha256 = "11i6sm6vznv9982hqpbrba43vfd7vv7zqzlywdc4qykvdhyh8r3w";
+        authors = [
+          "Arne Beer <contact@arne.beer>"
+        ];
+        dependencies = [
+          {
+            name = "crossterm";
+            packageId = "crossterm";
+            optional = true;
+            usesDefaultFeatures = false;
+            target = { target, features }: (!(target."windows" or false));
+          }
+          {
+            name = "crossterm";
+            packageId = "crossterm";
+            optional = true;
+            usesDefaultFeatures = false;
+            target = { target, features }: (target."windows" or false);
+            features = [ "windows" ];
+          }
+          {
+            name = "strum";
+            packageId = "strum";
+          }
+          {
+            name = "strum_macros";
+            packageId = "strum_macros";
+          }
+          {
+            name = "unicode-width";
+            packageId = "unicode-width";
+          }
+        ];
+        features = {
+          "console" = [ "dep:console" ];
+          "crossterm" = [ "dep:crossterm" ];
+          "custom_styling" = [ "console" ];
+          "default" = [ "tty" ];
+          "reexport_crossterm" = [ "tty" ];
+          "tty" = [ "crossterm" ];
+        };
+        resolvedDefaultFeatures = [ "crossterm" "tty" ];
+      };
+      "const-oid" = rec {
+        crateName = "const-oid";
+        version = "0.9.6";
+        edition = "2021";
+        sha256 = "1y0jnqaq7p2wvspnx7qj76m7hjcqpz73qzvr9l2p9n2s51vr6if2";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        features = {
+          "arbitrary" = [ "dep:arbitrary" ];
+        };
+      };
+      "core-foundation-sys" = rec {
+        crateName = "core-foundation-sys";
+        version = "0.8.6";
+        edition = "2018";
+        sha256 = "13w6sdf06r0hn7bx2b45zxsg1mm2phz34jikm6xc5qrbr6djpsh6";
+        authors = [
+          "The Servo Project Developers"
+        ];
+        features = {
+          "default" = [ "link" ];
+        };
+        resolvedDefaultFeatures = [ "default" "link" ];
+      };
+      "cpufeatures" = rec {
+        crateName = "cpufeatures";
+        version = "0.2.12";
+        edition = "2018";
+        sha256 = "012m7rrak4girqlii3jnqwrr73gv1i980q4wra5yyyhvzwk5xzjk";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (pkgs.rust.lib.toRustTarget stdenv.hostPlatform == "aarch64-linux-android");
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (("aarch64" == target."arch" or null) && ("linux" == target."os" or null));
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (("aarch64" == target."arch" or null) && ("apple" == target."vendor" or null));
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (("loongarch64" == target."arch" or null) && ("linux" == target."os" or null));
+          }
+        ];
+
+      };
+      "crc32fast" = rec {
+        crateName = "crc32fast";
+        version = "1.3.2";
+        edition = "2015";
+        sha256 = "03c8f29yx293yf43xar946xbls1g60c207m9drf8ilqhr25vsh5m";
+        authors = [
+          "Sam Rijs <srijs@airpost.net>"
+          "Alex Crichton <alex@alexcrichton.com>"
+        ];
+        dependencies = [
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "crossbeam-channel" = rec {
+        crateName = "crossbeam-channel";
+        version = "0.5.11";
+        edition = "2021";
+        sha256 = "16v48qdflpw3hgdik70bhsj7hympna79q7ci47rw0mlgnxsw2v8p";
+        dependencies = [
+          {
+            name = "crossbeam-utils";
+            packageId = "crossbeam-utils";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "std" = [ "crossbeam-utils/std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "crossbeam-deque" = rec {
+        crateName = "crossbeam-deque";
+        version = "0.8.5";
+        edition = "2021";
+        sha256 = "03bp38ljx4wj6vvy4fbhx41q8f585zyqix6pncz1mkz93z08qgv1";
+        dependencies = [
+          {
+            name = "crossbeam-epoch";
+            packageId = "crossbeam-epoch";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "crossbeam-utils";
+            packageId = "crossbeam-utils";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "std" = [ "crossbeam-epoch/std" "crossbeam-utils/std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "crossbeam-epoch" = rec {
+        crateName = "crossbeam-epoch";
+        version = "0.9.18";
+        edition = "2021";
+        sha256 = "03j2np8llwf376m3fxqx859mgp9f83hj1w34153c7a9c7i5ar0jv";
+        dependencies = [
+          {
+            name = "crossbeam-utils";
+            packageId = "crossbeam-utils";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "loom" = [ "loom-crate" "crossbeam-utils/loom" ];
+          "loom-crate" = [ "dep:loom-crate" ];
+          "nightly" = [ "crossbeam-utils/nightly" ];
+          "std" = [ "alloc" "crossbeam-utils/std" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "std" ];
+      };
+      "crossbeam-queue" = rec {
+        crateName = "crossbeam-queue";
+        version = "0.3.11";
+        edition = "2021";
+        sha256 = "0d8y8y3z48r9javzj67v3p2yfswd278myz1j9vzc4sp7snslc0yz";
+        dependencies = [
+          {
+            name = "crossbeam-utils";
+            packageId = "crossbeam-utils";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "nightly" = [ "crossbeam-utils/nightly" ];
+          "std" = [ "alloc" "crossbeam-utils/std" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "std" ];
+      };
+      "crossbeam-utils" = rec {
+        crateName = "crossbeam-utils";
+        version = "0.8.19";
+        edition = "2021";
+        sha256 = "0iakrb1b8fjqrag7wphl94d10irhbh2fw1g444xslsywqyn3p3i4";
+        features = {
+          "default" = [ "std" ];
+          "loom" = [ "dep:loom" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "crossterm" = rec {
+        crateName = "crossterm";
+        version = "0.27.0";
+        edition = "2021";
+        sha256 = "1pr413ki440xgddlmkrc4j1bfx1h8rpmll87zn8ykja1bm2gwxpl";
+        authors = [
+          "T. Post"
+        ];
+        dependencies = [
+          {
+            name = "bitflags";
+            packageId = "bitflags 2.4.2";
+          }
+          {
+            name = "crossterm_winapi";
+            packageId = "crossterm_winapi";
+            optional = true;
+            target = { target, features }: (target."windows" or false);
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (target."unix" or false);
+          }
+          {
+            name = "parking_lot";
+            packageId = "parking_lot";
+          }
+          {
+            name = "winapi";
+            packageId = "winapi";
+            optional = true;
+            target = { target, features }: (target."windows" or false);
+            features = [ "winuser" "winerror" ];
+          }
+        ];
+        features = {
+          "default" = [ "bracketed-paste" "windows" "events" ];
+          "event-stream" = [ "dep:futures-core" "events" ];
+          "events" = [ "dep:mio" "dep:signal-hook" "dep:signal-hook-mio" ];
+          "filedescriptor" = [ "dep:filedescriptor" ];
+          "serde" = [ "dep:serde" "bitflags/serde" ];
+          "use-dev-tty" = [ "filedescriptor" ];
+          "windows" = [ "dep:winapi" "dep:crossterm_winapi" ];
+        };
+        resolvedDefaultFeatures = [ "windows" ];
+      };
+      "crossterm_winapi" = rec {
+        crateName = "crossterm_winapi";
+        version = "0.9.1";
+        edition = "2018";
+        sha256 = "0axbfb2ykbwbpf1hmxwpawwfs8wvmkcka5m561l7yp36ldi7rpdc";
+        authors = [
+          "T. Post"
+        ];
+        dependencies = [
+          {
+            name = "winapi";
+            packageId = "winapi";
+            target = { target, features }: (target."windows" or false);
+            features = [ "winbase" "consoleapi" "processenv" "handleapi" "synchapi" "impl-default" ];
+          }
+        ];
+
+      };
+      "crypto-common" = rec {
+        crateName = "crypto-common";
+        version = "0.1.6";
+        edition = "2018";
+        sha256 = "1cvby95a6xg7kxdz5ln3rl9xh66nz66w46mm3g56ri1z5x815yqv";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        dependencies = [
+          {
+            name = "generic-array";
+            packageId = "generic-array";
+            features = [ "more_lengths" ];
+          }
+          {
+            name = "typenum";
+            packageId = "typenum";
+          }
+        ];
+        features = {
+          "getrandom" = [ "rand_core/getrandom" ];
+          "rand_core" = [ "dep:rand_core" ];
+        };
+        resolvedDefaultFeatures = [ "std" ];
+      };
+      "curve25519-dalek" = rec {
+        crateName = "curve25519-dalek";
+        version = "4.1.2";
+        edition = "2021";
+        sha256 = "0j7kqchcgycs4a11gvlda93h9w2jr05nn4hjpfyh2kn94a4pnrqa";
+        authors = [
+          "Isis Lovecruft <isis@patternsinthevoid.net>"
+          "Henry de Valence <hdevalence@hdevalence.ca>"
+        ];
+        dependencies = [
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "cpufeatures";
+            packageId = "cpufeatures";
+            target = { target, features }: ("x86_64" == target."arch" or null);
+          }
+          {
+            name = "curve25519-dalek-derive";
+            packageId = "curve25519-dalek-derive";
+            target = { target, features }: ((!("fiat" == target."curve25519_dalek_backend" or null)) && (!("serial" == target."curve25519_dalek_backend" or null)) && ("x86_64" == target."arch" or null));
+          }
+          {
+            name = "digest";
+            packageId = "digest";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "fiat-crypto";
+            packageId = "fiat-crypto";
+            usesDefaultFeatures = false;
+            target = { target, features }: ("fiat" == target."curve25519_dalek_backend" or null);
+          }
+          {
+            name = "subtle";
+            packageId = "subtle";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "zeroize";
+            packageId = "zeroize";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "platforms";
+            packageId = "platforms";
+          }
+          {
+            name = "rustc_version";
+            packageId = "rustc_version";
+          }
+        ];
+        features = {
+          "alloc" = [ "zeroize?/alloc" ];
+          "default" = [ "alloc" "precomputed-tables" "zeroize" ];
+          "digest" = [ "dep:digest" ];
+          "ff" = [ "dep:ff" ];
+          "group" = [ "dep:group" "rand_core" ];
+          "group-bits" = [ "group" "ff/bits" ];
+          "rand_core" = [ "dep:rand_core" ];
+          "serde" = [ "dep:serde" ];
+          "zeroize" = [ "dep:zeroize" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "digest" "precomputed-tables" "zeroize" ];
+      };
+      "curve25519-dalek-derive" = rec {
+        crateName = "curve25519-dalek-derive";
+        version = "0.1.1";
+        edition = "2021";
+        sha256 = "1cry71xxrr0mcy5my3fb502cwfxy6822k4pm19cwrilrg7hq4s7l";
+        procMacro = true;
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn 2.0.48";
+            features = [ "full" ];
+          }
+        ];
+
+      };
+      "data-encoding" = rec {
+        crateName = "data-encoding";
+        version = "2.5.0";
+        edition = "2018";
+        sha256 = "1rcbnwfmfxhlshzbn3r7srm3azqha3mn33yxyqxkzz2wpqcjm5ky";
+        authors = [
+          "Julien Cretin <git@ia0.eu>"
+        ];
+        features = {
+          "default" = [ "std" ];
+          "std" = [ "alloc" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "std" ];
+      };
+      "der" = rec {
+        crateName = "der";
+        version = "0.7.8";
+        edition = "2021";
+        sha256 = "070bwiyr80800h31c5zd96ckkgagfjgnrrdmz3dzg2lccsd3dypz";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        dependencies = [
+          {
+            name = "const-oid";
+            packageId = "const-oid";
+            optional = true;
+          }
+          {
+            name = "zeroize";
+            packageId = "zeroize";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "alloc" = [ "zeroize?/alloc" ];
+          "arbitrary" = [ "dep:arbitrary" "const-oid?/arbitrary" "std" ];
+          "bytes" = [ "dep:bytes" "alloc" ];
+          "derive" = [ "dep:der_derive" ];
+          "flagset" = [ "dep:flagset" ];
+          "oid" = [ "dep:const-oid" ];
+          "pem" = [ "dep:pem-rfc7468" "alloc" "zeroize" ];
+          "std" = [ "alloc" ];
+          "time" = [ "dep:time" ];
+          "zeroize" = [ "dep:zeroize" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "oid" "std" "zeroize" ];
+      };
+      "digest" = rec {
+        crateName = "digest";
+        version = "0.10.7";
+        edition = "2018";
+        sha256 = "14p2n6ih29x81akj097lvz7wi9b6b9hvls0lwrv7b6xwyy0s5ncy";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        dependencies = [
+          {
+            name = "block-buffer";
+            packageId = "block-buffer";
+            optional = true;
+          }
+          {
+            name = "crypto-common";
+            packageId = "crypto-common";
+          }
+        ];
+        features = {
+          "blobby" = [ "dep:blobby" ];
+          "block-buffer" = [ "dep:block-buffer" ];
+          "const-oid" = [ "dep:const-oid" ];
+          "core-api" = [ "block-buffer" ];
+          "default" = [ "core-api" ];
+          "dev" = [ "blobby" ];
+          "mac" = [ "subtle" ];
+          "oid" = [ "const-oid" ];
+          "rand_core" = [ "crypto-common/rand_core" ];
+          "std" = [ "alloc" "crypto-common/std" ];
+          "subtle" = [ "dep:subtle" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "block-buffer" "core-api" "default" "std" ];
+      };
+      "dyn-clone" = rec {
+        crateName = "dyn-clone";
+        version = "1.0.16";
+        edition = "2018";
+        sha256 = "0pa9kas6a241pbx0q82ipwi4f7m7wwyzkkc725caky24gl4j4nsl";
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+
+      };
+      "ed25519" = rec {
+        crateName = "ed25519";
+        version = "2.2.3";
+        edition = "2021";
+        sha256 = "0lydzdf26zbn82g7xfczcac9d7mzm3qgx934ijjrd5hjpjx32m8i";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        dependencies = [
+          {
+            name = "pkcs8";
+            packageId = "pkcs8";
+            optional = true;
+          }
+          {
+            name = "signature";
+            packageId = "signature";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "alloc" = [ "pkcs8?/alloc" ];
+          "default" = [ "std" ];
+          "pem" = [ "alloc" "pkcs8/pem" ];
+          "pkcs8" = [ "dep:pkcs8" ];
+          "serde" = [ "dep:serde" ];
+          "serde_bytes" = [ "serde" "dep:serde_bytes" ];
+          "std" = [ "pkcs8?/std" "signature/std" ];
+          "zeroize" = [ "dep:zeroize" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "std" ];
+      };
+      "ed25519-dalek" = rec {
+        crateName = "ed25519-dalek";
+        version = "2.1.1";
+        edition = "2021";
+        sha256 = "0w88cafwglg9hjizldbmlza0ns3hls81zk1bcih3m5m3h67algaa";
+        authors = [
+          "isis lovecruft <isis@patternsinthevoid.net>"
+          "Tony Arcieri <bascule@gmail.com>"
+          "Michael Rosenberg <michael@mrosenberg.pub>"
+        ];
+        dependencies = [
+          {
+            name = "curve25519-dalek";
+            packageId = "curve25519-dalek";
+            usesDefaultFeatures = false;
+            features = [ "digest" ];
+          }
+          {
+            name = "ed25519";
+            packageId = "ed25519";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "sha2";
+            packageId = "sha2";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "subtle";
+            packageId = "subtle";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "zeroize";
+            packageId = "zeroize";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+        ];
+        devDependencies = [
+          {
+            name = "curve25519-dalek";
+            packageId = "curve25519-dalek";
+            usesDefaultFeatures = false;
+            features = [ "digest" "rand_core" ];
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+            features = [ "derive" ];
+          }
+        ];
+        features = {
+          "alloc" = [ "curve25519-dalek/alloc" "ed25519/alloc" "serde?/alloc" "zeroize/alloc" ];
+          "asm" = [ "sha2/asm" ];
+          "batch" = [ "alloc" "merlin" "rand_core" ];
+          "default" = [ "fast" "std" "zeroize" ];
+          "digest" = [ "signature/digest" ];
+          "fast" = [ "curve25519-dalek/precomputed-tables" ];
+          "legacy_compatibility" = [ "curve25519-dalek/legacy_compatibility" ];
+          "merlin" = [ "dep:merlin" ];
+          "pem" = [ "alloc" "ed25519/pem" "pkcs8" ];
+          "pkcs8" = [ "ed25519/pkcs8" ];
+          "rand_core" = [ "dep:rand_core" ];
+          "serde" = [ "dep:serde" "ed25519/serde" ];
+          "signature" = [ "dep:signature" ];
+          "std" = [ "alloc" "ed25519/std" "serde?/std" "sha2/std" ];
+          "zeroize" = [ "dep:zeroize" "curve25519-dalek/zeroize" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "fast" "std" "zeroize" ];
+      };
+      "either" = rec {
+        crateName = "either";
+        version = "1.9.0";
+        edition = "2018";
+        sha256 = "01qy3anr7jal5lpc20791vxrw0nl6vksb5j7x56q2fycgcyy8sm2";
+        authors = [
+          "bluss"
+        ];
+        features = {
+          "default" = [ "use_std" ];
+          "serde" = [ "dep:serde" ];
+        };
+        resolvedDefaultFeatures = [ "default" "use_std" ];
+      };
+      "enum_dispatch" = rec {
+        crateName = "enum_dispatch";
+        version = "0.3.12";
+        edition = "2018";
+        sha256 = "03l998igqfzkykmj8i5qlbwhv2id9jn98fkkl82lv3dvg0q32cwg";
+        procMacro = true;
+        authors = [
+          "Anton Lazarev <https://antonok.com>"
+        ];
+        dependencies = [
+          {
+            name = "once_cell";
+            packageId = "once_cell";
+          }
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn 2.0.48";
+            features = [ "full" ];
+          }
+        ];
+
+      };
+      "equivalent" = rec {
+        crateName = "equivalent";
+        version = "1.0.1";
+        edition = "2015";
+        sha256 = "1malmx5f4lkfvqasz319lq6gb3ddg19yzf9s8cykfsgzdmyq0hsl";
+
+      };
+      "ethnum" = rec {
+        crateName = "ethnum";
+        version = "1.5.0";
+        edition = "2021";
+        sha256 = "0b68ngvisb0d40vc6h30zlhghbb3mc8wlxjbf8gnmavk1dca435r";
+        authors = [
+          "Nicholas Rodrigues Lordello <nlordell@gmail.com>"
+        ];
+        features = {
+          "ethnum-intrinsics" = [ "dep:ethnum-intrinsics" ];
+          "llvm-intrinsics" = [ "ethnum-intrinsics" ];
+          "serde" = [ "dep:serde" ];
+        };
+      };
+      "fallible-streaming-iterator" = rec {
+        crateName = "fallible-streaming-iterator";
+        version = "0.1.9";
+        edition = "2015";
+        sha256 = "0nj6j26p71bjy8h42x6jahx1hn0ng6mc2miwpgwnp8vnwqf4jq3k";
+        authors = [
+          "Steven Fackler <sfackler@gmail.com>"
+        ];
+        features = { };
+      };
+      "fast-float" = rec {
+        crateName = "fast-float";
+        version = "0.2.0";
+        edition = "2018";
+        sha256 = "0g7kfll3xyh99kc7r352lhljnwvgayxxa6saifb6725inikmyxlm";
+        authors = [
+          "Ivan Smirnov <i.s.smirnov@gmail.com>"
+        ];
+        features = {
+          "default" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "fiat-crypto" = rec {
+        crateName = "fiat-crypto";
+        version = "0.2.6";
+        edition = "2018";
+        sha256 = "10hkkkjynhibvchznkxx81gwxqarn9i5sgz40d6xxb8xzhsz8xhn";
+        authors = [
+          "Fiat Crypto library authors <jgross@mit.edu>"
+        ];
+        features = {
+          "default" = [ "std" ];
+        };
+      };
+      "flate2" = rec {
+        crateName = "flate2";
+        version = "1.0.28";
+        edition = "2018";
+        sha256 = "03llhsh4gqdirnfxxb9g2w9n0721dyn4yjir3pz7z4vjaxb3yc26";
+        authors = [
+          "Alex Crichton <alex@alexcrichton.com>"
+          "Josh Triplett <josh@joshtriplett.org>"
+        ];
+        dependencies = [
+          {
+            name = "crc32fast";
+            packageId = "crc32fast";
+          }
+          {
+            name = "miniz_oxide";
+            packageId = "miniz_oxide";
+            optional = true;
+            usesDefaultFeatures = false;
+            features = [ "with-alloc" ];
+          }
+          {
+            name = "miniz_oxide";
+            packageId = "miniz_oxide";
+            usesDefaultFeatures = false;
+            target = { target, features }: (("wasm32" == target."arch" or null) && (!("emscripten" == target."os" or null)));
+            features = [ "with-alloc" ];
+          }
+        ];
+        features = {
+          "any_zlib" = [ "any_impl" ];
+          "cloudflare-zlib-sys" = [ "dep:cloudflare-zlib-sys" ];
+          "cloudflare_zlib" = [ "any_zlib" "cloudflare-zlib-sys" ];
+          "default" = [ "rust_backend" ];
+          "libz-ng-sys" = [ "dep:libz-ng-sys" ];
+          "libz-sys" = [ "dep:libz-sys" ];
+          "miniz-sys" = [ "rust_backend" ];
+          "miniz_oxide" = [ "dep:miniz_oxide" ];
+          "rust_backend" = [ "miniz_oxide" "any_impl" ];
+          "zlib" = [ "any_zlib" "libz-sys" ];
+          "zlib-default" = [ "any_zlib" "libz-sys/default" ];
+          "zlib-ng" = [ "any_zlib" "libz-ng-sys" ];
+          "zlib-ng-compat" = [ "zlib" "libz-sys/zlib-ng" ];
+        };
+        resolvedDefaultFeatures = [ "any_impl" "miniz_oxide" "rust_backend" ];
+      };
+      "foreign_vec" = rec {
+        crateName = "foreign_vec";
+        version = "0.1.0";
+        edition = "2021";
+        sha256 = "0wv6p8yfahcqbdg2wg7wxgj4dm32g2b6spa5sg5sxg34v35ha6zf";
+        authors = [
+          "Jorge C. Leitao <jorgecarleitao@gmail.com>"
+        ];
+
+      };
+      "futures" = rec {
+        crateName = "futures";
+        version = "0.3.30";
+        edition = "2018";
+        sha256 = "1c04g14bccmprwsvx2j9m2blhwrynq7vhl151lsvcv4gi0b6jp34";
+        dependencies = [
+          {
+            name = "futures-channel";
+            packageId = "futures-channel";
+            usesDefaultFeatures = false;
+            features = [ "sink" ];
+          }
+          {
+            name = "futures-core";
+            packageId = "futures-core";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-executor";
+            packageId = "futures-executor";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-io";
+            packageId = "futures-io";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-sink";
+            packageId = "futures-sink";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-task";
+            packageId = "futures-task";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-util";
+            packageId = "futures-util";
+            usesDefaultFeatures = false;
+            features = [ "sink" ];
+          }
+        ];
+        features = {
+          "alloc" = [ "futures-core/alloc" "futures-task/alloc" "futures-sink/alloc" "futures-channel/alloc" "futures-util/alloc" ];
+          "async-await" = [ "futures-util/async-await" "futures-util/async-await-macro" ];
+          "bilock" = [ "futures-util/bilock" ];
+          "compat" = [ "std" "futures-util/compat" ];
+          "default" = [ "std" "async-await" "executor" ];
+          "executor" = [ "std" "futures-executor/std" ];
+          "futures-executor" = [ "dep:futures-executor" ];
+          "io-compat" = [ "compat" "futures-util/io-compat" ];
+          "std" = [ "alloc" "futures-core/std" "futures-task/std" "futures-io/std" "futures-sink/std" "futures-util/std" "futures-util/io" "futures-util/channel" ];
+          "thread-pool" = [ "executor" "futures-executor/thread-pool" ];
+          "unstable" = [ "futures-core/unstable" "futures-task/unstable" "futures-channel/unstable" "futures-io/unstable" "futures-util/unstable" ];
+          "write-all-vectored" = [ "futures-util/write-all-vectored" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "async-await" "default" "executor" "futures-executor" "std" ];
+      };
+      "futures-channel" = rec {
+        crateName = "futures-channel";
+        version = "0.3.30";
+        edition = "2018";
+        sha256 = "0y6b7xxqdjm9hlcjpakcg41qfl7lihf6gavk8fyqijsxhvbzgj7a";
+        dependencies = [
+          {
+            name = "futures-core";
+            packageId = "futures-core";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-sink";
+            packageId = "futures-sink";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "alloc" = [ "futures-core/alloc" ];
+          "default" = [ "std" ];
+          "futures-sink" = [ "dep:futures-sink" ];
+          "sink" = [ "futures-sink" ];
+          "std" = [ "alloc" "futures-core/std" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "futures-sink" "sink" "std" ];
+      };
+      "futures-core" = rec {
+        crateName = "futures-core";
+        version = "0.3.30";
+        edition = "2018";
+        sha256 = "07aslayrn3lbggj54kci0ishmd1pr367fp7iks7adia1p05miinz";
+        features = {
+          "default" = [ "std" ];
+          "portable-atomic" = [ "dep:portable-atomic" ];
+          "std" = [ "alloc" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "std" ];
+      };
+      "futures-executor" = rec {
+        crateName = "futures-executor";
+        version = "0.3.30";
+        edition = "2018";
+        sha256 = "07dh08gs9vfll2h36kq32q9xd86xm6lyl9xikmmwlkqnmrrgqxm5";
+        dependencies = [
+          {
+            name = "futures-core";
+            packageId = "futures-core";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-task";
+            packageId = "futures-task";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-util";
+            packageId = "futures-util";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "num_cpus" = [ "dep:num_cpus" ];
+          "std" = [ "futures-core/std" "futures-task/std" "futures-util/std" ];
+          "thread-pool" = [ "std" "num_cpus" ];
+        };
+        resolvedDefaultFeatures = [ "std" ];
+      };
+      "futures-io" = rec {
+        crateName = "futures-io";
+        version = "0.3.30";
+        edition = "2018";
+        sha256 = "1hgh25isvsr4ybibywhr4dpys8mjnscw4wfxxwca70cn1gi26im4";
+        features = {
+          "default" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "std" ];
+      };
+      "futures-macro" = rec {
+        crateName = "futures-macro";
+        version = "0.3.30";
+        edition = "2018";
+        sha256 = "1b49qh9d402y8nka4q6wvvj0c88qq91wbr192mdn5h54nzs0qxc7";
+        procMacro = true;
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn 2.0.48";
+            features = [ "full" ];
+          }
+        ];
+
+      };
+      "futures-sink" = rec {
+        crateName = "futures-sink";
+        version = "0.3.30";
+        edition = "2018";
+        sha256 = "1dag8xyyaya8n8mh8smx7x6w2dpmafg2din145v973a3hw7f1f4z";
+        features = {
+          "default" = [ "std" ];
+          "std" = [ "alloc" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "std" ];
+      };
+      "futures-task" = rec {
+        crateName = "futures-task";
+        version = "0.3.30";
+        edition = "2018";
+        sha256 = "013h1724454hj8qczp8vvs10qfiqrxr937qsrv6rhii68ahlzn1q";
+        features = {
+          "default" = [ "std" ];
+          "std" = [ "alloc" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "std" ];
+      };
+      "futures-util" = rec {
+        crateName = "futures-util";
+        version = "0.3.30";
+        edition = "2018";
+        sha256 = "0j0xqhcir1zf2dcbpd421kgw6wvsk0rpxflylcysn1rlp3g02r1x";
+        dependencies = [
+          {
+            name = "futures-channel";
+            packageId = "futures-channel";
+            optional = true;
+            usesDefaultFeatures = false;
+            features = [ "std" ];
+          }
+          {
+            name = "futures-core";
+            packageId = "futures-core";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-io";
+            packageId = "futures-io";
+            optional = true;
+            usesDefaultFeatures = false;
+            features = [ "std" ];
+          }
+          {
+            name = "futures-macro";
+            packageId = "futures-macro";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-sink";
+            packageId = "futures-sink";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures-task";
+            packageId = "futures-task";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "memchr";
+            packageId = "memchr";
+            optional = true;
+          }
+          {
+            name = "pin-project-lite";
+            packageId = "pin-project-lite";
+          }
+          {
+            name = "pin-utils";
+            packageId = "pin-utils";
+          }
+          {
+            name = "slab";
+            packageId = "slab";
+            optional = true;
+          }
+        ];
+        features = {
+          "alloc" = [ "futures-core/alloc" "futures-task/alloc" ];
+          "async-await-macro" = [ "async-await" "futures-macro" ];
+          "channel" = [ "std" "futures-channel" ];
+          "compat" = [ "std" "futures_01" ];
+          "default" = [ "std" "async-await" "async-await-macro" ];
+          "futures-channel" = [ "dep:futures-channel" ];
+          "futures-io" = [ "dep:futures-io" ];
+          "futures-macro" = [ "dep:futures-macro" ];
+          "futures-sink" = [ "dep:futures-sink" ];
+          "futures_01" = [ "dep:futures_01" ];
+          "io" = [ "std" "futures-io" "memchr" ];
+          "io-compat" = [ "io" "compat" "tokio-io" ];
+          "memchr" = [ "dep:memchr" ];
+          "portable-atomic" = [ "futures-core/portable-atomic" ];
+          "sink" = [ "futures-sink" ];
+          "slab" = [ "dep:slab" ];
+          "std" = [ "alloc" "futures-core/std" "futures-task/std" "slab" ];
+          "tokio-io" = [ "dep:tokio-io" ];
+          "unstable" = [ "futures-core/unstable" "futures-task/unstable" ];
+          "write-all-vectored" = [ "io" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "async-await" "async-await-macro" "channel" "futures-channel" "futures-io" "futures-macro" "futures-sink" "io" "memchr" "sink" "slab" "std" ];
+      };
+      "generic-array" = rec {
+        crateName = "generic-array";
+        version = "0.14.7";
+        edition = "2015";
+        sha256 = "16lyyrzrljfq424c3n8kfwkqihlimmsg5nhshbbp48np3yjrqr45";
+        libName = "generic_array";
+        authors = [
+          "Bartłomiej Kamiński <fizyk20@gmail.com>"
+          "Aaron Trent <novacrazy@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "typenum";
+            packageId = "typenum";
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "version_check";
+            packageId = "version_check";
+          }
+        ];
+        features = {
+          "serde" = [ "dep:serde" ];
+          "zeroize" = [ "dep:zeroize" ];
+        };
+        resolvedDefaultFeatures = [ "more_lengths" ];
+      };
+      "getrandom" = rec {
+        crateName = "getrandom";
+        version = "0.2.12";
+        edition = "2018";
+        sha256 = "1d8jb9bv38nkwlqqdjcav6gxckgwc9g30pm3qq506rvncpm9400r";
+        authors = [
+          "The Rand Project Developers"
+        ];
+        dependencies = [
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "js-sys";
+            packageId = "js-sys";
+            optional = true;
+            target = { target, features }: ((("wasm32" == target."arch" or null) || ("wasm64" == target."arch" or null)) && ("unknown" == target."os" or null));
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            usesDefaultFeatures = false;
+            target = { target, features }: (target."unix" or false);
+          }
+          {
+            name = "wasi";
+            packageId = "wasi";
+            usesDefaultFeatures = false;
+            target = { target, features }: ("wasi" == target."os" or null);
+          }
+          {
+            name = "wasm-bindgen";
+            packageId = "wasm-bindgen";
+            optional = true;
+            usesDefaultFeatures = false;
+            target = { target, features }: ((("wasm32" == target."arch" or null) || ("wasm64" == target."arch" or null)) && ("unknown" == target."os" or null));
+          }
+        ];
+        features = {
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "core" = [ "dep:core" ];
+          "js" = [ "wasm-bindgen" "js-sys" ];
+          "js-sys" = [ "dep:js-sys" ];
+          "rustc-dep-of-std" = [ "compiler_builtins" "core" "libc/rustc-dep-of-std" "wasi/rustc-dep-of-std" ];
+          "wasm-bindgen" = [ "dep:wasm-bindgen" ];
+        };
+        resolvedDefaultFeatures = [ "js" "js-sys" "std" "wasm-bindgen" ];
+      };
+      "gimli" = rec {
+        crateName = "gimli";
+        version = "0.28.1";
+        edition = "2018";
+        sha256 = "0lv23wc8rxvmjia3mcxc6hj9vkqnv1bqq0h8nzjcgf71mrxx6wa2";
+        features = {
+          "default" = [ "read-all" "write" ];
+          "endian-reader" = [ "read" "dep:stable_deref_trait" ];
+          "fallible-iterator" = [ "dep:fallible-iterator" ];
+          "read" = [ "read-core" ];
+          "read-all" = [ "read" "std" "fallible-iterator" "endian-reader" ];
+          "rustc-dep-of-std" = [ "dep:core" "dep:alloc" "dep:compiler_builtins" ];
+          "std" = [ "fallible-iterator?/std" "stable_deref_trait?/std" ];
+          "write" = [ "dep:indexmap" ];
+        };
+        resolvedDefaultFeatures = [ "read" "read-core" ];
+      };
+      "glob" = rec {
+        crateName = "glob";
+        version = "0.3.1";
+        edition = "2015";
+        sha256 = "16zca52nglanv23q5qrwd5jinw3d3as5ylya6y1pbx47vkxvrynj";
+        authors = [
+          "The Rust Project Developers"
+        ];
+
+      };
+      "hashbrown" = rec {
+        crateName = "hashbrown";
+        version = "0.14.3";
+        edition = "2021";
+        sha256 = "012nywlg0lj9kwanh69my5x67vjlfmzfi9a0rq4qvis2j8fil3r9";
+        authors = [
+          "Amanieu d'Antras <amanieu@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "ahash";
+            packageId = "ahash";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "allocator-api2";
+            packageId = "allocator-api2";
+            optional = true;
+            usesDefaultFeatures = false;
+            features = [ "alloc" ];
+          }
+          {
+            name = "rayon";
+            packageId = "rayon";
+            optional = true;
+          }
+        ];
+        devDependencies = [
+          {
+            name = "rayon";
+            packageId = "rayon";
+          }
+        ];
+        features = {
+          "ahash" = [ "dep:ahash" ];
+          "alloc" = [ "dep:alloc" ];
+          "allocator-api2" = [ "dep:allocator-api2" ];
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "core" = [ "dep:core" ];
+          "default" = [ "ahash" "inline-more" "allocator-api2" ];
+          "equivalent" = [ "dep:equivalent" ];
+          "nightly" = [ "allocator-api2?/nightly" "bumpalo/allocator_api" ];
+          "rayon" = [ "dep:rayon" ];
+          "rkyv" = [ "dep:rkyv" ];
+          "rustc-dep-of-std" = [ "nightly" "core" "compiler_builtins" "alloc" "rustc-internal-api" ];
+          "serde" = [ "dep:serde" ];
+        };
+        resolvedDefaultFeatures = [ "ahash" "allocator-api2" "default" "inline-more" "raw" "rayon" ];
+      };
+      "heck" = rec {
+        crateName = "heck";
+        version = "0.4.1";
+        edition = "2018";
+        sha256 = "1a7mqsnycv5z4z5vnv1k34548jzmc0ajic7c1j8jsaspnhw5ql4m";
+        authors = [
+          "Without Boats <woboats@gmail.com>"
+        ];
+        features = {
+          "unicode" = [ "unicode-segmentation" ];
+          "unicode-segmentation" = [ "dep:unicode-segmentation" ];
+        };
+        resolvedDefaultFeatures = [ "default" ];
+      };
+      "hermit-abi" = rec {
+        crateName = "hermit-abi";
+        version = "0.3.5";
+        edition = "2021";
+        sha256 = "1hw2bxkzyvr0rbnpj0lkasi8h8qf3lyb63hp760cn22fjqaj3inh";
+        authors = [
+          "Stefan Lankes"
+        ];
+        features = {
+          "alloc" = [ "dep:alloc" ];
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "core" = [ "dep:core" ];
+          "rustc-dep-of-std" = [ "core" "alloc" "compiler_builtins/rustc-dep-of-std" ];
+        };
+        resolvedDefaultFeatures = [ "default" ];
+      };
+      "home" = rec {
+        crateName = "home";
+        version = "0.5.9";
+        edition = "2021";
+        sha256 = "19grxyg35rqfd802pcc9ys1q3lafzlcjcv2pl2s5q8xpyr5kblg3";
+        authors = [
+          "Brian Anderson <andersrb@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "windows-sys";
+            packageId = "windows-sys 0.52.0";
+            target = { target, features }: (target."windows" or false);
+            features = [ "Win32_Foundation" "Win32_UI_Shell" "Win32_System_Com" ];
+          }
+        ];
+
+      };
+      "iana-time-zone" = rec {
+        crateName = "iana-time-zone";
+        version = "0.1.60";
+        edition = "2018";
+        sha256 = "0hdid5xz3jznm04lysjm3vi93h3c523w0hcc3xba47jl3ddbpzz7";
+        authors = [
+          "Andrew Straw <strawman@astraw.com>"
+          "René Kijewski <rene.kijewski@fu-berlin.de>"
+          "Ryan Lopopolo <rjl@hyperbo.la>"
+        ];
+        dependencies = [
+          {
+            name = "android_system_properties";
+            packageId = "android_system_properties";
+            target = { target, features }: ("android" == target."os" or null);
+          }
+          {
+            name = "core-foundation-sys";
+            packageId = "core-foundation-sys";
+            target = { target, features }: (("macos" == target."os" or null) || ("ios" == target."os" or null));
+          }
+          {
+            name = "iana-time-zone-haiku";
+            packageId = "iana-time-zone-haiku";
+            target = { target, features }: ("haiku" == target."os" or null);
+          }
+          {
+            name = "js-sys";
+            packageId = "js-sys";
+            target = { target, features }: ("wasm32" == target."arch" or null);
+          }
+          {
+            name = "wasm-bindgen";
+            packageId = "wasm-bindgen";
+            target = { target, features }: ("wasm32" == target."arch" or null);
+          }
+          {
+            name = "windows-core";
+            packageId = "windows-core";
+            target = { target, features }: ("windows" == target."os" or null);
+          }
+        ];
+        features = { };
+        resolvedDefaultFeatures = [ "fallback" ];
+      };
+      "iana-time-zone-haiku" = rec {
+        crateName = "iana-time-zone-haiku";
+        version = "0.1.2";
+        edition = "2018";
+        sha256 = "17r6jmj31chn7xs9698r122mapq85mfnv98bb4pg6spm0si2f67k";
+        authors = [
+          "René Kijewski <crates.io@k6i.de>"
+        ];
+        buildDependencies = [
+          {
+            name = "cc";
+            packageId = "cc";
+          }
+        ];
+
+      };
+      "indexmap" = rec {
+        crateName = "indexmap";
+        version = "2.2.2";
+        edition = "2021";
+        sha256 = "087mafd9f98rp1xk2jc1rsp5yyqz63yi30cy8yx6c8s14bj2ljw2";
+        dependencies = [
+          {
+            name = "equivalent";
+            packageId = "equivalent";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "hashbrown";
+            packageId = "hashbrown";
+            usesDefaultFeatures = false;
+            features = [ "raw" ];
+          }
+        ];
+        features = {
+          "arbitrary" = [ "dep:arbitrary" ];
+          "default" = [ "std" ];
+          "quickcheck" = [ "dep:quickcheck" ];
+          "rayon" = [ "dep:rayon" ];
+          "rustc-rayon" = [ "dep:rustc-rayon" ];
+          "serde" = [ "dep:serde" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "itoa" = rec {
+        crateName = "itoa";
+        version = "1.0.10";
+        edition = "2018";
+        sha256 = "0k7xjfki7mnv6yzjrbnbnjllg86acmbnk4izz2jmm1hx2wd6v95i";
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        features = {
+          "no-panic" = [ "dep:no-panic" ];
+        };
+      };
+      "jobserver" = rec {
+        crateName = "jobserver";
+        version = "0.1.27";
+        edition = "2018";
+        sha256 = "0z9w6vfqwbr6hfk9yaw7kydlh6f7k39xdlszxlh39in4acwzcdwc";
+        authors = [
+          "Alex Crichton <alex@alexcrichton.com>"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (target."unix" or false);
+          }
+        ];
+
+      };
+      "js-sys" = rec {
+        crateName = "js-sys";
+        version = "0.3.68";
+        edition = "2018";
+        sha256 = "1vm98fhnhs4w6yakchi9ip7ar95900k9vkr24a21qlwd6r5xlv20";
+        authors = [
+          "The wasm-bindgen Developers"
+        ];
+        dependencies = [
+          {
+            name = "wasm-bindgen";
+            packageId = "wasm-bindgen";
+          }
+        ];
+
+      };
+      "libc" = rec {
+        crateName = "libc";
+        version = "0.2.153";
+        edition = "2015";
+        sha256 = "1gg7m1ils5dms5miq9fyllrcp0jxnbpgkx71chd2i0lafa8qy6cw";
+        authors = [
+          "The Rust Project Developers"
+        ];
+        features = {
+          "default" = [ "std" ];
+          "rustc-dep-of-std" = [ "align" "rustc-std-workspace-core" ];
+          "rustc-std-workspace-core" = [ "dep:rustc-std-workspace-core" ];
+          "use_std" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "libm" = rec {
+        crateName = "libm";
+        version = "0.2.8";
+        edition = "2018";
+        sha256 = "0n4hk1rs8pzw8hdfmwn96c4568s93kfxqgcqswr7sajd2diaihjf";
+        authors = [
+          "Jorge Aparicio <jorge@japaric.io>"
+        ];
+        features = {
+          "musl-reference-tests" = [ "rand" ];
+          "rand" = [ "dep:rand" ];
+        };
+        resolvedDefaultFeatures = [ "default" ];
+      };
+      "lock_api" = rec {
+        crateName = "lock_api";
+        version = "0.4.11";
+        edition = "2018";
+        sha256 = "0iggx0h4jx63xm35861106af3jkxq06fpqhpkhgw0axi2n38y5iw";
+        authors = [
+          "Amanieu d'Antras <amanieu@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "scopeguard";
+            packageId = "scopeguard";
+            usesDefaultFeatures = false;
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "autocfg";
+            packageId = "autocfg";
+          }
+        ];
+        features = {
+          "default" = [ "atomic_usize" ];
+          "owning_ref" = [ "dep:owning_ref" ];
+          "serde" = [ "dep:serde" ];
+        };
+        resolvedDefaultFeatures = [ "atomic_usize" "default" ];
+      };
+      "log" = rec {
+        crateName = "log";
+        version = "0.4.20";
+        edition = "2015";
+        sha256 = "13rf7wphnwd61vazpxr7fiycin6cb1g8fmvgqg18i464p0y1drmm";
+        authors = [
+          "The Rust Project Developers"
+        ];
+        features = {
+          "kv_unstable" = [ "value-bag" ];
+          "kv_unstable_serde" = [ "kv_unstable_std" "value-bag/serde" "serde" ];
+          "kv_unstable_std" = [ "std" "kv_unstable" "value-bag/error" ];
+          "kv_unstable_sval" = [ "kv_unstable" "value-bag/sval" "sval" "sval_ref" ];
+          "serde" = [ "dep:serde" ];
+          "sval" = [ "dep:sval" ];
+          "sval_ref" = [ "dep:sval_ref" ];
+          "value-bag" = [ "dep:value-bag" ];
+        };
+      };
+      "lz4" = rec {
+        crateName = "lz4";
+        version = "1.24.0";
+        edition = "2018";
+        crateBin = [ ];
+        sha256 = "1wad97k0asgvaj16ydd09gqs2yvgaanzcvqglrhffv7kdpc2v7ky";
+        authors = [
+          "Jens Heyens <jens.heyens@ewetel.net>"
+          "Artem V. Navrotskiy <bozaro@buzzsoft.ru>"
+          "Patrick Marks <pmarks@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+          }
+          {
+            name = "lz4-sys";
+            packageId = "lz4-sys";
+          }
+        ];
+
+      };
+      "lz4-sys" = rec {
+        crateName = "lz4-sys";
+        version = "1.9.4";
+        edition = "2015";
+        links = "lz4";
+        sha256 = "0059ik4xlvnss5qfh6l691psk4g3350ljxaykzv10yr0gqqppljp";
+        authors = [
+          "Jens Heyens <jens.heyens@ewetel.net>"
+          "Artem V. Navrotskiy <bozaro@buzzsoft.ru>"
+          "Patrick Marks <pmarks@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "cc";
+            packageId = "cc";
+          }
+        ];
+
+      };
+      "memchr" = rec {
+        crateName = "memchr";
+        version = "2.7.1";
+        edition = "2021";
+        sha256 = "0jf1kicqa4vs9lyzj4v4y1p90q0dh87hvhsdd5xvhnp527sw8gaj";
+        authors = [
+          "Andrew Gallant <jamslam@gmail.com>"
+          "bluss"
+        ];
+        features = {
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "core" = [ "dep:core" ];
+          "default" = [ "std" ];
+          "logging" = [ "dep:log" ];
+          "rustc-dep-of-std" = [ "core" "compiler_builtins" ];
+          "std" = [ "alloc" ];
+          "use_std" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "std" ];
+      };
+      "memmap2" = rec {
+        crateName = "memmap2";
+        version = "0.7.1";
+        edition = "2018";
+        sha256 = "1il82b0mw304jlwvl0m89aa8bj5dgmm3vbb0jg8lqlrk0p98i4zl";
+        authors = [
+          "Dan Burkert <dan@danburkert.com>"
+          "Yevhenii Reizner <razrfalcon@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (target."unix" or false);
+          }
+        ];
+        features = {
+          "stable_deref_trait" = [ "dep:stable_deref_trait" ];
+        };
+      };
+      "minimal-lexical" = rec {
+        crateName = "minimal-lexical";
+        version = "0.2.1";
+        edition = "2018";
+        sha256 = "16ppc5g84aijpri4jzv14rvcnslvlpphbszc7zzp6vfkddf4qdb8";
+        authors = [
+          "Alex Huszagh <ahuszagh@gmail.com>"
+        ];
+        features = {
+          "default" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "std" ];
+      };
+      "miniz_oxide" = rec {
+        crateName = "miniz_oxide";
+        version = "0.7.2";
+        edition = "2018";
+        sha256 = "19qlxb21s6kabgqq61mk7kd1qk2invyygj076jz6i1gj2lz1z0cx";
+        authors = [
+          "Frommi <daniil.liferenko@gmail.com>"
+          "oyvindln <oyvindln@users.noreply.github.com>"
+        ];
+        dependencies = [
+          {
+            name = "adler";
+            packageId = "adler";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "alloc" = [ "dep:alloc" ];
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "core" = [ "dep:core" ];
+          "default" = [ "with-alloc" ];
+          "rustc-dep-of-std" = [ "core" "alloc" "compiler_builtins" "adler/rustc-dep-of-std" ];
+          "simd" = [ "simd-adler32" ];
+          "simd-adler32" = [ "dep:simd-adler32" ];
+        };
+        resolvedDefaultFeatures = [ "with-alloc" ];
+      };
+      "mio" = rec {
+        crateName = "mio";
+        version = "0.8.10";
+        edition = "2018";
+        sha256 = "02gyaxvaia9zzi4drrw59k9s0j6pa5d1y2kv7iplwjipdqlhngcg";
+        authors = [
+          "Carl Lerche <me@carllerche.com>"
+          "Thomas de Zeeuw <thomasdezeeuw@gmail.com>"
+          "Tokio Contributors <team@tokio.rs>"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: ("wasi" == target."os" or null);
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (target."unix" or false);
+          }
+          {
+            name = "wasi";
+            packageId = "wasi";
+            target = { target, features }: ("wasi" == target."os" or null);
+          }
+          {
+            name = "windows-sys";
+            packageId = "windows-sys 0.48.0";
+            target = { target, features }: (target."windows" or false);
+            features = [ "Win32_Foundation" "Win32_Networking_WinSock" "Win32_Storage_FileSystem" "Win32_System_IO" "Win32_System_WindowsProgramming" ];
+          }
+        ];
+        features = {
+          "default" = [ "log" ];
+          "log" = [ "dep:log" ];
+          "os-ext" = [ "os-poll" "windows-sys/Win32_System_Pipes" "windows-sys/Win32_Security" ];
+        };
+        resolvedDefaultFeatures = [ "net" "os-ext" "os-poll" ];
+      };
+      "multiversion" = rec {
+        crateName = "multiversion";
+        version = "0.7.3";
+        edition = "2021";
+        sha256 = "0al7yrf489lqzxx291sx9566n7slk2njwlqrxbjhqxk1zvbvkixj";
+        authors = [
+          "Caleb Zulawski <caleb.zulawski@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "multiversion-macros";
+            packageId = "multiversion-macros";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "target-features";
+            packageId = "target-features";
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "std" = [ "multiversion-macros/std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "multiversion-macros" = rec {
+        crateName = "multiversion-macros";
+        version = "0.7.3";
+        edition = "2021";
+        sha256 = "1j1avbxw7jscyi7dmnywhlwbiny1fvg1vpp9fy4dc1pd022kva16";
+        procMacro = true;
+        authors = [
+          "Caleb Zulawski <caleb.zulawski@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn 1.0.109";
+            features = [ "full" "extra-traits" "visit-mut" ];
+          }
+          {
+            name = "target-features";
+            packageId = "target-features";
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "std" ];
+      };
+      "nix-compat" = rec {
+        crateName = "nix-compat";
+        version = "0.1.0";
+        edition = "2021";
+        crateBin = [ ];
+        # We can't filter paths with references in Nix 2.4
+        # See https://github.com/NixOS/nix/issues/5410
+        src =
+          if ((lib.versionOlder builtins.nixVersion "2.4pre20211007") || (lib.versionOlder "2.5" builtins.nixVersion))
+          then lib.cleanSourceWith { filter = sourceFilter; src = ../../nix-compat; }
+          else ../../nix-compat;
+        dependencies = [
+          {
+            name = "bitflags";
+            packageId = "bitflags 2.4.2";
+          }
+          {
+            name = "bstr";
+            packageId = "bstr";
+            features = [ "alloc" "unicode" "serde" ];
+          }
+          {
+            name = "data-encoding";
+            packageId = "data-encoding";
+          }
+          {
+            name = "ed25519";
+            packageId = "ed25519";
+          }
+          {
+            name = "ed25519-dalek";
+            packageId = "ed25519-dalek";
+          }
+          {
+            name = "glob";
+            packageId = "glob";
+          }
+          {
+            name = "nom";
+            packageId = "nom";
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+            features = [ "derive" ];
+          }
+          {
+            name = "serde_json";
+            packageId = "serde_json";
+          }
+          {
+            name = "sha2";
+            packageId = "sha2";
+          }
+          {
+            name = "thiserror";
+            packageId = "thiserror";
+          }
+        ];
+        devDependencies = [
+          {
+            name = "serde_json";
+            packageId = "serde_json";
+          }
+        ];
+        features = {
+          "async" = [ "futures-util" ];
+          "futures-util" = [ "dep:futures-util" ];
+        };
+      };
+      "nom" = rec {
+        crateName = "nom";
+        version = "7.1.3";
+        edition = "2018";
+        sha256 = "0jha9901wxam390jcf5pfa0qqfrgh8li787jx2ip0yk5b8y9hwyj";
+        authors = [
+          "contact@geoffroycouprie.com"
+        ];
+        dependencies = [
+          {
+            name = "memchr";
+            packageId = "memchr";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "minimal-lexical";
+            packageId = "minimal-lexical";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "std" = [ "alloc" "memchr/std" "minimal-lexical/std" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "std" ];
+      };
+      "now" = rec {
+        crateName = "now";
+        version = "0.1.3";
+        edition = "2018";
+        sha256 = "1l135786rb43rjfhwfdj7hi3b5zxxyl9gwf15yjz18cp8f3yk2bd";
+        authors = [
+          "Kilerd <blove694@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "chrono";
+            packageId = "chrono";
+            usesDefaultFeatures = false;
+            features = [ "clock" "std" ];
+          }
+        ];
+
+      };
+      "ntapi" = rec {
+        crateName = "ntapi";
+        version = "0.4.1";
+        edition = "2018";
+        sha256 = "1r38zhbwdvkis2mzs6671cm1p6djgsl49i7bwxzrvhwicdf8k8z8";
+        authors = [
+          "MSxDOS <melcodos@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "winapi";
+            packageId = "winapi";
+            features = [ "cfg" "evntrace" "in6addr" "inaddr" "minwinbase" "ntsecapi" "windef" "winioctl" ];
+          }
+        ];
+        features = {
+          "default" = [ "user" ];
+          "impl-default" = [ "winapi/impl-default" ];
+        };
+        resolvedDefaultFeatures = [ "default" "user" ];
+      };
+      "num-traits" = rec {
+        crateName = "num-traits";
+        version = "0.2.18";
+        edition = "2018";
+        sha256 = "0yjib8p2p9kzmaz48xwhs69w5dh1wipph9jgnillzd2x33jz03fs";
+        authors = [
+          "The Rust Project Developers"
+        ];
+        dependencies = [
+          {
+            name = "libm";
+            packageId = "libm";
+            optional = true;
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "autocfg";
+            packageId = "autocfg";
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "libm" = [ "dep:libm" ];
+        };
+        resolvedDefaultFeatures = [ "default" "libm" "std" ];
+      };
+      "num_cpus" = rec {
+        crateName = "num_cpus";
+        version = "1.16.0";
+        edition = "2015";
+        sha256 = "0hra6ihpnh06dvfvz9ipscys0xfqa9ca9hzp384d5m02ssvgqqa1";
+        authors = [
+          "Sean McArthur <sean@seanmonstar.com>"
+        ];
+        dependencies = [
+          {
+            name = "hermit-abi";
+            packageId = "hermit-abi";
+            target = { target, features }: ("hermit" == target."os" or null);
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (!(target."windows" or false));
+          }
+        ];
+
+      };
+      "object" = rec {
+        crateName = "object";
+        version = "0.32.2";
+        edition = "2018";
+        sha256 = "0hc4cjwyngiy6k51hlzrlsxgv5z25vv7c2cp0ky1lckfic0259m6";
+        dependencies = [
+          {
+            name = "memchr";
+            packageId = "memchr";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "all" = [ "read" "write" "std" "compression" "wasm" ];
+          "alloc" = [ "dep:alloc" ];
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "compression" = [ "dep:flate2" "dep:ruzstd" "std" ];
+          "core" = [ "dep:core" ];
+          "default" = [ "read" "compression" ];
+          "doc" = [ "read_core" "write_std" "std" "compression" "archive" "coff" "elf" "macho" "pe" "wasm" "xcoff" ];
+          "pe" = [ "coff" ];
+          "read" = [ "read_core" "archive" "coff" "elf" "macho" "pe" "xcoff" "unaligned" ];
+          "rustc-dep-of-std" = [ "core" "compiler_builtins" "alloc" "memchr/rustc-dep-of-std" ];
+          "std" = [ "memchr/std" ];
+          "unstable-all" = [ "all" "unstable" ];
+          "wasm" = [ "dep:wasmparser" ];
+          "write" = [ "write_std" "coff" "elf" "macho" "pe" "xcoff" ];
+          "write_core" = [ "dep:crc32fast" "dep:indexmap" "dep:hashbrown" ];
+          "write_std" = [ "write_core" "std" "indexmap?/std" "crc32fast?/std" ];
+        };
+        resolvedDefaultFeatures = [ "archive" "coff" "elf" "macho" "pe" "read_core" "unaligned" ];
+      };
+      "once_cell" = rec {
+        crateName = "once_cell";
+        version = "1.19.0";
+        edition = "2021";
+        sha256 = "14kvw7px5z96dk4dwdm1r9cqhhy2cyj1l5n5b29mynbb8yr15nrz";
+        authors = [
+          "Aleksey Kladov <aleksey.kladov@gmail.com>"
+        ];
+        features = {
+          "alloc" = [ "race" ];
+          "atomic-polyfill" = [ "critical-section" ];
+          "critical-section" = [ "dep:critical-section" "portable-atomic" ];
+          "default" = [ "std" ];
+          "parking_lot" = [ "dep:parking_lot_core" ];
+          "portable-atomic" = [ "dep:portable-atomic" ];
+          "std" = [ "alloc" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "race" "std" ];
+      };
+      "owning_ref" = rec {
+        crateName = "owning_ref";
+        version = "0.4.1";
+        edition = "2015";
+        sha256 = "1kjj9m28wjv452jw49p1mp3d8ql058x78v4bz00avr7rvsnmpxbg";
+        authors = [
+          "Marvin Löbel <loebel.marvin@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "stable_deref_trait";
+            packageId = "stable_deref_trait";
+          }
+        ];
+
+      };
+      "parking_lot" = rec {
+        crateName = "parking_lot";
+        version = "0.12.1";
+        edition = "2018";
+        sha256 = "13r2xk7mnxfc5g0g6dkdxqdqad99j7s7z8zhzz4npw5r0g0v4hip";
+        authors = [
+          "Amanieu d'Antras <amanieu@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "lock_api";
+            packageId = "lock_api";
+          }
+          {
+            name = "parking_lot_core";
+            packageId = "parking_lot_core";
+          }
+        ];
+        features = {
+          "arc_lock" = [ "lock_api/arc_lock" ];
+          "deadlock_detection" = [ "parking_lot_core/deadlock_detection" ];
+          "nightly" = [ "parking_lot_core/nightly" "lock_api/nightly" ];
+          "owning_ref" = [ "lock_api/owning_ref" ];
+          "serde" = [ "lock_api/serde" ];
+        };
+        resolvedDefaultFeatures = [ "default" ];
+      };
+      "parking_lot_core" = rec {
+        crateName = "parking_lot_core";
+        version = "0.9.9";
+        edition = "2018";
+        sha256 = "13h0imw1aq86wj28gxkblhkzx6z1gk8q18n0v76qmmj6cliajhjc";
+        authors = [
+          "Amanieu d'Antras <amanieu@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (target."unix" or false);
+          }
+          {
+            name = "redox_syscall";
+            packageId = "redox_syscall";
+            target = { target, features }: ("redox" == target."os" or null);
+          }
+          {
+            name = "smallvec";
+            packageId = "smallvec";
+          }
+          {
+            name = "windows-targets";
+            packageId = "windows-targets 0.48.5";
+            target = { target, features }: (target."windows" or false);
+          }
+        ];
+        features = {
+          "backtrace" = [ "dep:backtrace" ];
+          "deadlock_detection" = [ "petgraph" "thread-id" "backtrace" ];
+          "petgraph" = [ "dep:petgraph" ];
+          "thread-id" = [ "dep:thread-id" ];
+        };
+      };
+      "parquet-format-safe" = rec {
+        crateName = "parquet-format-safe";
+        version = "0.2.4";
+        edition = "2021";
+        sha256 = "07wf6wf4jrxlq5p3xldxsnabp7jl06my2qp7kiwy9m3x2r5wac8i";
+        authors = [
+          "Apache Thrift contributors <dev@thrift.apache.org>"
+          "Jorge Leitao <jorgecarleitao@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "async-trait";
+            packageId = "async-trait";
+            optional = true;
+          }
+          {
+            name = "futures";
+            packageId = "futures";
+            optional = true;
+          }
+        ];
+        features = {
+          "async" = [ "futures" "async-trait" ];
+          "async-trait" = [ "dep:async-trait" ];
+          "full" = [ "async" ];
+          "futures" = [ "dep:futures" ];
+        };
+        resolvedDefaultFeatures = [ "async" "async-trait" "default" "futures" ];
+      };
+      "percent-encoding" = rec {
+        crateName = "percent-encoding";
+        version = "2.3.1";
+        edition = "2018";
+        sha256 = "0gi8wgx0dcy8rnv1kywdv98lwcx67hz0a0zwpib5v2i08r88y573";
+        authors = [
+          "The rust-url developers"
+        ];
+        features = {
+          "default" = [ "std" ];
+          "std" = [ "alloc" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "std" ];
+      };
+      "pin-project-lite" = rec {
+        crateName = "pin-project-lite";
+        version = "0.2.13";
+        edition = "2018";
+        sha256 = "0n0bwr5qxlf0mhn2xkl36sy55118s9qmvx2yl5f3ixkb007lbywa";
+
+      };
+      "pin-utils" = rec {
+        crateName = "pin-utils";
+        version = "0.1.0";
+        edition = "2018";
+        sha256 = "117ir7vslsl2z1a7qzhws4pd01cg2d3338c47swjyvqv2n60v1wb";
+        authors = [
+          "Josef Brandl <mail@josefbrandl.de>"
+        ];
+
+      };
+      "pkcs8" = rec {
+        crateName = "pkcs8";
+        version = "0.10.2";
+        edition = "2021";
+        sha256 = "1dx7w21gvn07azszgqd3ryjhyphsrjrmq5mmz1fbxkj5g0vv4l7r";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        dependencies = [
+          {
+            name = "der";
+            packageId = "der";
+            features = [ "oid" ];
+          }
+          {
+            name = "spki";
+            packageId = "spki";
+          }
+        ];
+        features = {
+          "3des" = [ "encryption" "pkcs5/3des" ];
+          "alloc" = [ "der/alloc" "der/zeroize" "spki/alloc" ];
+          "des-insecure" = [ "encryption" "pkcs5/des-insecure" ];
+          "encryption" = [ "alloc" "pkcs5/alloc" "pkcs5/pbes2" "rand_core" ];
+          "getrandom" = [ "rand_core/getrandom" ];
+          "pem" = [ "alloc" "der/pem" "spki/pem" ];
+          "pkcs5" = [ "dep:pkcs5" ];
+          "rand_core" = [ "dep:rand_core" ];
+          "sha1-insecure" = [ "encryption" "pkcs5/sha1-insecure" ];
+          "std" = [ "alloc" "der/std" "spki/std" ];
+          "subtle" = [ "dep:subtle" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "std" ];
+      };
+      "pkg-config" = rec {
+        crateName = "pkg-config";
+        version = "0.3.29";
+        edition = "2015";
+        sha256 = "1jy6158v1316khkpmq2sjj1vgbnbnw51wffx7p0k0l9h9vlys019";
+        authors = [
+          "Alex Crichton <alex@alexcrichton.com>"
+        ];
+
+      };
+      "planus" = rec {
+        crateName = "planus";
+        version = "0.3.1";
+        edition = "2021";
+        sha256 = "17x8mr175b9clg998xpi5z45f9fsspb0ncfnx2644bz817fr25pw";
+        dependencies = [
+          {
+            name = "array-init-cursor";
+            packageId = "array-init-cursor";
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "platforms" = rec {
+        crateName = "platforms";
+        version = "3.3.0";
+        edition = "2018";
+        sha256 = "0k7q6pigmnvgpfasvssb12m2pv3pc94zrhrfg9by3h3wmhyfqvb2";
+        authors = [
+          "Tony Arcieri <bascule@gmail.com>"
+          "Sergey \"Shnatsel\" Davidoff <shnatsel@gmail.com>"
+        ];
+        features = {
+          "default" = [ "std" ];
+          "serde" = [ "dep:serde" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "polars" = rec {
+        crateName = "polars";
+        version = "0.36.2";
+        edition = "2021";
+        sha256 = "0swv6i0gq25zafw1ir2irqij9a8mnkhvws5idv72m3kavby4i04k";
+        authors = [
+          "Ritchie Vink <ritchie46@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "getrandom";
+            packageId = "getrandom";
+            target = { target, features }: (builtins.elem "wasm" target."family");
+            features = [ "js" ];
+          }
+          {
+            name = "polars-core";
+            packageId = "polars-core";
+            usesDefaultFeatures = false;
+            features = [ "algorithm_group_by" ];
+          }
+          {
+            name = "polars-io";
+            packageId = "polars-io";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-lazy";
+            packageId = "polars-lazy";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-ops";
+            packageId = "polars-ops";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-sql";
+            packageId = "polars-sql";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-time";
+            packageId = "polars-time";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "version_check";
+            packageId = "version_check";
+          }
+        ];
+        features = {
+          "abs" = [ "polars-ops/abs" "polars-lazy?/abs" ];
+          "approx_unique" = [ "polars-lazy?/approx_unique" "polars-ops/approx_unique" ];
+          "arg_where" = [ "polars-lazy?/arg_where" ];
+          "array_any_all" = [ "polars-lazy?/array_any_all" "dtype-array" ];
+          "asof_join" = [ "polars-core/asof_join" "polars-lazy?/asof_join" "polars-ops/asof_join" ];
+          "async" = [ "polars-lazy?/async" ];
+          "avro" = [ "polars-io" "polars-io/avro" ];
+          "avx512" = [ "polars-core/avx512" ];
+          "aws" = [ "async" "cloud" "polars-io/aws" ];
+          "azure" = [ "async" "cloud" "polars-io/azure" ];
+          "bench" = [ "lazy" ];
+          "bigidx" = [ "polars-core/bigidx" "polars-lazy?/bigidx" "polars-ops/big_idx" ];
+          "binary_encoding" = [ "polars-ops/binary_encoding" "polars-lazy?/binary_encoding" ];
+          "checked_arithmetic" = [ "polars-core/checked_arithmetic" ];
+          "chunked_ids" = [ "polars-lazy?/chunked_ids" "polars-core/chunked_ids" "polars-ops/chunked_ids" ];
+          "cloud" = [ "polars-lazy?/cloud" "polars-io/cloud" ];
+          "cloud_write" = [ "cloud" "polars-lazy?/cloud_write" ];
+          "coalesce" = [ "polars-lazy?/coalesce" ];
+          "concat_str" = [ "polars-lazy?/concat_str" ];
+          "cov" = [ "polars-lazy/cov" ];
+          "cross_join" = [ "polars-lazy?/cross_join" "polars-ops/cross_join" ];
+          "cse" = [ "polars-lazy?/cse" ];
+          "csv" = [ "polars-io" "polars-io/csv" "polars-lazy?/csv" "polars-sql?/csv" ];
+          "cum_agg" = [ "polars-ops/cum_agg" "polars-lazy?/cum_agg" ];
+          "cumulative_eval" = [ "polars-lazy?/cumulative_eval" ];
+          "cutqcut" = [ "polars-lazy?/cutqcut" ];
+          "dataframe_arithmetic" = [ "polars-core/dataframe_arithmetic" ];
+          "date_offset" = [ "polars-lazy?/date_offset" ];
+          "decompress" = [ "polars-io/decompress" ];
+          "decompress-fast" = [ "polars-io/decompress-fast" ];
+          "default" = [ "docs" "zip_with" "csv" "temporal" "fmt" "dtype-slim" ];
+          "describe" = [ "polars-core/describe" ];
+          "diagonal_concat" = [ "polars-core/diagonal_concat" "polars-lazy?/diagonal_concat" "polars-sql?/diagonal_concat" ];
+          "diff" = [ "polars-ops/diff" "polars-lazy?/diff" ];
+          "docs" = [ "polars-core/docs" ];
+          "docs-selection" = [ "csv" "json" "parquet" "ipc" "ipc_streaming" "dtype-full" "is_in" "rows" "docs" "strings" "object" "lazy" "temporal" "random" "zip_with" "round_series" "checked_arithmetic" "ndarray" "repeat_by" "is_first_distinct" "is_last_distinct" "asof_join" "cross_join" "concat_str" "string_reverse" "string_to_integer" "decompress" "mode" "take_opt_iter" "cum_agg" "rolling_window" "interpolate" "diff" "rank" "range" "diagonal_concat" "horizontal_concat" "abs" "dot_diagram" "string_encoding" "product" "to_dummies" "describe" "list_eval" "cumulative_eval" "timezones" "arg_where" "propagate_nans" "coalesce" "dynamic_group_by" "extract_groups" "replace" ];
+          "dot_diagram" = [ "polars-lazy?/dot_diagram" ];
+          "dot_product" = [ "polars-core/dot_product" ];
+          "dtype-array" = [ "polars-core/dtype-array" "polars-lazy?/dtype-array" "polars-ops/dtype-array" ];
+          "dtype-categorical" = [ "polars-core/dtype-categorical" "polars-io/dtype-categorical" "polars-lazy?/dtype-categorical" "polars-ops/dtype-categorical" ];
+          "dtype-date" = [ "polars-core/dtype-date" "polars-lazy?/dtype-date" "polars-io/dtype-date" "polars-time?/dtype-date" "polars-core/dtype-date" "polars-ops/dtype-date" ];
+          "dtype-datetime" = [ "polars-core/dtype-datetime" "polars-lazy?/dtype-datetime" "polars-io/dtype-datetime" "polars-time?/dtype-datetime" "polars-ops/dtype-datetime" ];
+          "dtype-decimal" = [ "polars-core/dtype-decimal" "polars-lazy?/dtype-decimal" "polars-ops/dtype-decimal" "polars-io/dtype-decimal" ];
+          "dtype-duration" = [ "polars-core/dtype-duration" "polars-lazy?/dtype-duration" "polars-time?/dtype-duration" "polars-core/dtype-duration" "polars-ops/dtype-duration" ];
+          "dtype-full" = [ "dtype-date" "dtype-datetime" "dtype-duration" "dtype-time" "dtype-array" "dtype-i8" "dtype-i16" "dtype-decimal" "dtype-u8" "dtype-u16" "dtype-categorical" "dtype-struct" ];
+          "dtype-i16" = [ "polars-core/dtype-i16" "polars-lazy?/dtype-i16" "polars-ops/dtype-i16" ];
+          "dtype-i8" = [ "polars-core/dtype-i8" "polars-lazy?/dtype-i8" "polars-ops/dtype-i8" ];
+          "dtype-slim" = [ "dtype-date" "dtype-datetime" "dtype-duration" ];
+          "dtype-struct" = [ "polars-core/dtype-struct" "polars-lazy?/dtype-struct" "polars-ops/dtype-struct" "polars-io/dtype-struct" ];
+          "dtype-time" = [ "polars-core/dtype-time" "polars-io/dtype-time" "polars-time?/dtype-time" "polars-ops/dtype-time" ];
+          "dtype-u16" = [ "polars-core/dtype-u16" "polars-lazy?/dtype-u16" "polars-ops/dtype-u16" ];
+          "dtype-u8" = [ "polars-core/dtype-u8" "polars-lazy?/dtype-u8" "polars-ops/dtype-u8" ];
+          "dynamic_group_by" = [ "polars-core/dynamic_group_by" "polars-lazy?/dynamic_group_by" ];
+          "ewma" = [ "polars-ops/ewma" "polars-lazy?/ewma" ];
+          "extract_groups" = [ "polars-lazy?/extract_groups" ];
+          "extract_jsonpath" = [ "polars-core/strings" "polars-ops/extract_jsonpath" "polars-ops/strings" "polars-lazy?/extract_jsonpath" ];
+          "find_many" = [ "polars-plan/find_many" ];
+          "fmt" = [ "polars-core/fmt" ];
+          "fmt_no_tty" = [ "polars-core/fmt_no_tty" ];
+          "fused" = [ "polars-ops/fused" "polars-lazy?/fused" ];
+          "gcp" = [ "async" "cloud" "polars-io/gcp" ];
+          "group_by_list" = [ "polars-core/group_by_list" "polars-ops/group_by_list" ];
+          "hist" = [ "polars-ops/hist" "polars-lazy/hist" ];
+          "horizontal_concat" = [ "polars-core/horizontal_concat" "polars-lazy?/horizontal_concat" ];
+          "http" = [ "async" "cloud" "polars-io/http" ];
+          "interpolate" = [ "polars-ops/interpolate" "polars-lazy?/interpolate" ];
+          "ipc" = [ "polars-io" "polars-io/ipc" "polars-lazy?/ipc" "polars-sql?/ipc" ];
+          "ipc_streaming" = [ "polars-io" "polars-io/ipc_streaming" "polars-lazy?/ipc" ];
+          "is_first_distinct" = [ "polars-lazy?/is_first_distinct" "polars-ops/is_first_distinct" ];
+          "is_in" = [ "polars-lazy?/is_in" ];
+          "is_last_distinct" = [ "polars-lazy?/is_last_distinct" "polars-ops/is_last_distinct" ];
+          "is_unique" = [ "polars-lazy?/is_unique" "polars-ops/is_unique" ];
+          "json" = [ "polars-io" "polars-io/json" "polars-lazy?/json" "polars-sql?/json" "dtype-struct" ];
+          "lazy" = [ "polars-core/lazy" "polars-lazy" ];
+          "lazy_regex" = [ "polars-lazy?/regex" ];
+          "list_any_all" = [ "polars-lazy?/list_any_all" ];
+          "list_count" = [ "polars-ops/list_count" "polars-lazy?/list_count" ];
+          "list_drop_nulls" = [ "polars-lazy?/list_drop_nulls" ];
+          "list_eval" = [ "polars-lazy?/list_eval" ];
+          "list_gather" = [ "polars-ops/list_gather" "polars-lazy?/list_gather" ];
+          "list_sample" = [ "polars-lazy?/list_sample" ];
+          "list_sets" = [ "polars-lazy?/list_sets" ];
+          "list_to_struct" = [ "polars-ops/list_to_struct" "polars-lazy?/list_to_struct" ];
+          "log" = [ "polars-ops/log" "polars-lazy?/log" ];
+          "merge_sorted" = [ "polars-lazy?/merge_sorted" ];
+          "meta" = [ "polars-lazy?/meta" ];
+          "mode" = [ "polars-ops/mode" "polars-lazy?/mode" ];
+          "moment" = [ "polars-ops/moment" "polars-lazy?/moment" ];
+          "ndarray" = [ "polars-core/ndarray" ];
+          "nightly" = [ "polars-core/nightly" "polars-ops?/nightly" "simd" "polars-lazy?/nightly" "polars-sql/nightly" ];
+          "object" = [ "polars-core/object" "polars-lazy?/object" "polars-io/object" ];
+          "parquet" = [ "polars-io" "polars-lazy?/parquet" "polars-io/parquet" "polars-sql?/parquet" ];
+          "partition_by" = [ "polars-core/partition_by" ];
+          "pct_change" = [ "polars-ops/pct_change" "polars-lazy?/pct_change" ];
+          "peaks" = [ "polars-lazy/peaks" ];
+          "performant" = [ "polars-core/performant" "chunked_ids" "dtype-u8" "dtype-u16" "dtype-struct" "cse" "polars-ops/performant" "streaming" "fused" ];
+          "pivot" = [ "polars-lazy?/pivot" ];
+          "polars-io" = [ "dep:polars-io" ];
+          "polars-lazy" = [ "dep:polars-lazy" ];
+          "polars-ops" = [ "dep:polars-ops" ];
+          "polars-plan" = [ "dep:polars-plan" ];
+          "polars-sql" = [ "dep:polars-sql" ];
+          "polars-time" = [ "dep:polars-time" ];
+          "product" = [ "polars-core/product" ];
+          "propagate_nans" = [ "polars-lazy?/propagate_nans" ];
+          "random" = [ "polars-core/random" "polars-lazy?/random" "polars-ops/random" ];
+          "range" = [ "polars-lazy?/range" ];
+          "rank" = [ "polars-lazy?/rank" "polars-ops/rank" ];
+          "reinterpret" = [ "polars-core/reinterpret" ];
+          "repeat_by" = [ "polars-ops/repeat_by" "polars-lazy?/repeat_by" ];
+          "replace" = [ "polars-ops/replace" "polars-lazy?/replace" ];
+          "rle" = [ "polars-lazy?/rle" ];
+          "rolling_window" = [ "polars-core/rolling_window" "polars-lazy?/rolling_window" "polars-time/rolling_window" ];
+          "round_series" = [ "polars-ops/round_series" "polars-lazy?/round_series" ];
+          "row_hash" = [ "polars-core/row_hash" "polars-lazy?/row_hash" ];
+          "rows" = [ "polars-core/rows" ];
+          "search_sorted" = [ "polars-lazy?/search_sorted" ];
+          "semi_anti_join" = [ "polars-lazy?/semi_anti_join" "polars-ops/semi_anti_join" "polars-sql?/semi_anti_join" ];
+          "serde" = [ "polars-core/serde" ];
+          "serde-lazy" = [ "polars-core/serde-lazy" "polars-lazy?/serde" "polars-time?/serde" "polars-io?/serde" "polars-ops?/serde" ];
+          "sign" = [ "polars-lazy?/sign" ];
+          "simd" = [ "polars-core/simd" "polars-io/simd" "polars-ops?/simd" ];
+          "sql" = [ "polars-sql" ];
+          "streaming" = [ "polars-lazy?/streaming" ];
+          "string_encoding" = [ "polars-ops/string_encoding" "polars-lazy?/string_encoding" "polars-core/strings" ];
+          "string_pad" = [ "polars-lazy?/string_pad" "polars-ops/string_pad" ];
+          "string_reverse" = [ "polars-lazy?/string_reverse" "polars-ops/string_reverse" ];
+          "string_to_integer" = [ "polars-lazy?/string_to_integer" "polars-ops/string_to_integer" ];
+          "strings" = [ "polars-core/strings" "polars-lazy?/strings" "polars-ops/strings" ];
+          "take_opt_iter" = [ "polars-core/take_opt_iter" ];
+          "temporal" = [ "polars-core/temporal" "polars-lazy?/temporal" "polars-io/temporal" "polars-time" ];
+          "test" = [ "lazy" "rolling_window" "rank" "round_series" "csv" "dtype-categorical" "cum_agg" "fmt" "diff" "abs" "parquet" "ipc" "ipc_streaming" "json" ];
+          "timezones" = [ "polars-core/timezones" "polars-lazy?/timezones" "polars-io/timezones" ];
+          "to_dummies" = [ "polars-ops/to_dummies" ];
+          "top_k" = [ "polars-lazy?/top_k" ];
+          "trigonometry" = [ "polars-lazy?/trigonometry" ];
+          "true_div" = [ "polars-lazy?/true_div" ];
+          "unique_counts" = [ "polars-ops/unique_counts" "polars-lazy?/unique_counts" ];
+          "zip_with" = [ "polars-core/zip_with" ];
+        };
+        resolvedDefaultFeatures = [ "csv" "default" "docs" "dtype-date" "dtype-datetime" "dtype-duration" "dtype-slim" "fmt" "parquet" "polars-io" "polars-ops" "polars-time" "temporal" "zip_with" ];
+      };
+      "polars-arrow" = rec {
+        crateName = "polars-arrow";
+        version = "0.36.2";
+        edition = "2021";
+        sha256 = "0vxql6amvwyp6qj0w87vm21y33qa0i61pshs4ry7ixwgd4ps0s6f";
+        authors = [
+          "Jorge C. Leitao <jorgecarleitao@gmail.com>"
+          "Apache Arrow <dev@arrow.apache.org>"
+          "Ritchie Vink <ritchie46@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "ahash";
+            packageId = "ahash";
+          }
+          {
+            name = "arrow-format";
+            packageId = "arrow-format";
+            optional = true;
+            features = [ "ipc" ];
+          }
+          {
+            name = "atoi_simd";
+            packageId = "atoi_simd";
+            optional = true;
+          }
+          {
+            name = "bytemuck";
+            packageId = "bytemuck";
+            features = [ "derive" "extern_crate_alloc" ];
+          }
+          {
+            name = "chrono";
+            packageId = "chrono";
+            usesDefaultFeatures = false;
+            features = [ "std" ];
+          }
+          {
+            name = "dyn-clone";
+            packageId = "dyn-clone";
+          }
+          {
+            name = "either";
+            packageId = "either";
+          }
+          {
+            name = "ethnum";
+            packageId = "ethnum";
+          }
+          {
+            name = "fast-float";
+            packageId = "fast-float";
+            optional = true;
+          }
+          {
+            name = "foreign_vec";
+            packageId = "foreign_vec";
+          }
+          {
+            name = "futures";
+            packageId = "futures";
+            optional = true;
+          }
+          {
+            name = "getrandom";
+            packageId = "getrandom";
+            target = { target, features }: (pkgs.rust.lib.toRustTarget stdenv.hostPlatform == "wasm32-unknown-unknown");
+            features = [ "js" ];
+          }
+          {
+            name = "hashbrown";
+            packageId = "hashbrown";
+            features = [ "rayon" "ahash" ];
+          }
+          {
+            name = "itoa";
+            packageId = "itoa";
+            optional = true;
+          }
+          {
+            name = "lz4";
+            packageId = "lz4";
+            optional = true;
+          }
+          {
+            name = "multiversion";
+            packageId = "multiversion";
+            optional = true;
+          }
+          {
+            name = "num-traits";
+            packageId = "num-traits";
+          }
+          {
+            name = "polars-error";
+            packageId = "polars-error";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-utils";
+            packageId = "polars-utils";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "ryu";
+            packageId = "ryu";
+            optional = true;
+          }
+          {
+            name = "simdutf8";
+            packageId = "simdutf8";
+          }
+          {
+            name = "streaming-iterator";
+            packageId = "streaming-iterator";
+          }
+          {
+            name = "strength_reduce";
+            packageId = "strength_reduce";
+            optional = true;
+          }
+          {
+            name = "zstd";
+            packageId = "zstd";
+            optional = true;
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "version_check";
+            packageId = "version_check";
+          }
+        ];
+        features = {
+          "arrow-array" = [ "dep:arrow-array" ];
+          "arrow-buffer" = [ "dep:arrow-buffer" ];
+          "arrow-data" = [ "dep:arrow-data" ];
+          "arrow-format" = [ "dep:arrow-format" ];
+          "arrow-schema" = [ "dep:arrow-schema" ];
+          "arrow_rs" = [ "arrow-buffer" "arrow-schema" "arrow-data" "arrow-array" ];
+          "async-stream" = [ "dep:async-stream" ];
+          "atoi" = [ "dep:atoi" ];
+          "atoi_simd" = [ "dep:atoi_simd" ];
+          "avro-schema" = [ "dep:avro-schema" ];
+          "chrono-tz" = [ "dep:chrono-tz" ];
+          "compute" = [ "compute_aggregate" "compute_arithmetics" "compute_bitwise" "compute_boolean" "compute_boolean_kleene" "compute_cast" "compute_comparison" "compute_concatenate" "compute_filter" "compute_hash" "compute_if_then_else" "compute_take" "compute_temporal" ];
+          "compute_aggregate" = [ "multiversion" ];
+          "compute_arithmetics" = [ "strength_reduce" "compute_arithmetics_decimal" ];
+          "compute_arithmetics_decimal" = [ "strength_reduce" ];
+          "compute_cast" = [ "compute_take" "ryu" "atoi_simd" "itoa" "fast-float" ];
+          "compute_comparison" = [ "compute_take" "compute_boolean" ];
+          "compute_hash" = [ "multiversion" ];
+          "dtype-decimal" = [ "atoi" ];
+          "fast-float" = [ "dep:fast-float" ];
+          "full" = [ "arrow_rs" "io_ipc" "io_flight" "io_ipc_write_async" "io_ipc_read_async" "io_ipc_compression" "io_avro" "io_avro_compression" "io_avro_async" "regex-syntax" "compute" "chrono-tz" ];
+          "futures" = [ "dep:futures" ];
+          "hex" = [ "dep:hex" ];
+          "indexmap" = [ "dep:indexmap" ];
+          "io_avro" = [ "avro-schema" "polars-error/avro-schema" ];
+          "io_avro_async" = [ "avro-schema/async" ];
+          "io_avro_compression" = [ "avro-schema/compression" ];
+          "io_flight" = [ "io_ipc" "arrow-format/flight-data" ];
+          "io_ipc" = [ "arrow-format" "polars-error/arrow-format" ];
+          "io_ipc_compression" = [ "lz4" "zstd" ];
+          "io_ipc_read_async" = [ "io_ipc" "futures" "async-stream" ];
+          "io_ipc_write_async" = [ "io_ipc" "futures" ];
+          "itoa" = [ "dep:itoa" ];
+          "lz4" = [ "dep:lz4" ];
+          "multiversion" = [ "dep:multiversion" ];
+          "regex" = [ "dep:regex" ];
+          "regex-syntax" = [ "dep:regex-syntax" ];
+          "ryu" = [ "dep:ryu" ];
+          "serde" = [ "dep:serde" ];
+          "strength_reduce" = [ "dep:strength_reduce" ];
+          "zstd" = [ "dep:zstd" ];
+        };
+        resolvedDefaultFeatures = [ "arrow-format" "atoi_simd" "compute" "compute_aggregate" "compute_arithmetics" "compute_arithmetics_decimal" "compute_bitwise" "compute_boolean" "compute_boolean_kleene" "compute_cast" "compute_comparison" "compute_concatenate" "compute_filter" "compute_hash" "compute_if_then_else" "compute_take" "compute_temporal" "fast-float" "futures" "io_ipc" "io_ipc_compression" "io_ipc_write_async" "itoa" "lz4" "multiversion" "ryu" "strength_reduce" "strings" "temporal" "zstd" ];
+      };
+      "polars-compute" = rec {
+        crateName = "polars-compute";
+        version = "0.36.2";
+        edition = "2021";
+        sha256 = "1pa4l1w41gdzdff81ih1z05z3gz568k81i6flibbca8v2igvqkxi";
+        authors = [
+          "Ritchie Vink <ritchie46@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "bytemuck";
+            packageId = "bytemuck";
+            features = [ "derive" "extern_crate_alloc" ];
+          }
+          {
+            name = "num-traits";
+            packageId = "num-traits";
+          }
+          {
+            name = "polars-arrow";
+            packageId = "polars-arrow";
+            rename = "arrow";
+            usesDefaultFeatures = false;
+            features = [ "compute_aggregate" "compute_arithmetics" "compute_boolean" "compute_boolean_kleene" "compute_cast" "compute_comparison" "compute_concatenate" "compute_filter" "compute_if_then_else" ];
+          }
+          {
+            name = "polars-utils";
+            packageId = "polars-utils";
+            usesDefaultFeatures = false;
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "version_check";
+            packageId = "version_check";
+          }
+        ];
+        features = { };
+      };
+      "polars-core" = rec {
+        crateName = "polars-core";
+        version = "0.36.2";
+        edition = "2021";
+        sha256 = "08sar9h97znpb8ziyvrrvvx28lyzc21vwsd7gvwybjxn6kkyzxfh";
+        authors = [
+          "Ritchie Vink <ritchie46@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "ahash";
+            packageId = "ahash";
+          }
+          {
+            name = "bitflags";
+            packageId = "bitflags 2.4.2";
+          }
+          {
+            name = "bytemuck";
+            packageId = "bytemuck";
+            features = [ "derive" "extern_crate_alloc" ];
+          }
+          {
+            name = "chrono";
+            packageId = "chrono";
+            optional = true;
+            usesDefaultFeatures = false;
+            features = [ "std" ];
+          }
+          {
+            name = "comfy-table";
+            packageId = "comfy-table";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "either";
+            packageId = "either";
+          }
+          {
+            name = "hashbrown";
+            packageId = "hashbrown";
+            features = [ "rayon" "ahash" ];
+          }
+          {
+            name = "indexmap";
+            packageId = "indexmap";
+            features = [ "std" ];
+          }
+          {
+            name = "num-traits";
+            packageId = "num-traits";
+          }
+          {
+            name = "once_cell";
+            packageId = "once_cell";
+          }
+          {
+            name = "polars-arrow";
+            packageId = "polars-arrow";
+            rename = "arrow";
+            usesDefaultFeatures = false;
+            features = [ "compute_aggregate" "compute_arithmetics" "compute_boolean" "compute_boolean_kleene" "compute_cast" "compute_comparison" "compute_concatenate" "compute_filter" "compute_if_then_else" ];
+          }
+          {
+            name = "polars-compute";
+            packageId = "polars-compute";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-error";
+            packageId = "polars-error";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-row";
+            packageId = "polars-row";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-utils";
+            packageId = "polars-utils";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "rand";
+            packageId = "rand";
+            optional = true;
+            features = [ "small_rng" "std" ];
+          }
+          {
+            name = "rand_distr";
+            packageId = "rand_distr";
+            optional = true;
+          }
+          {
+            name = "rayon";
+            packageId = "rayon";
+          }
+          {
+            name = "regex";
+            packageId = "regex";
+            optional = true;
+          }
+          {
+            name = "smartstring";
+            packageId = "smartstring";
+          }
+          {
+            name = "thiserror";
+            packageId = "thiserror";
+          }
+          {
+            name = "xxhash-rust";
+            packageId = "xxhash-rust";
+            features = [ "xxh3" ];
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "version_check";
+            packageId = "version_check";
+          }
+        ];
+        features = {
+          "arrow-array" = [ "dep:arrow-array" ];
+          "arrow_rs" = [ "arrow-array" "arrow/arrow_rs" ];
+          "bigidx" = [ "arrow/bigidx" "polars-utils/bigidx" ];
+          "chrono" = [ "dep:chrono" ];
+          "chrono-tz" = [ "dep:chrono-tz" ];
+          "comfy-table" = [ "dep:comfy-table" ];
+          "default" = [ "algorithm_group_by" ];
+          "docs-selection" = [ "ndarray" "rows" "docs" "strings" "object" "lazy" "temporal" "random" "zip_with" "checked_arithmetic" "is_first_distinct" "is_last_distinct" "asof_join" "dot_product" "row_hash" "rolling_window" "dtype-categorical" "dtype-decimal" "diagonal_concat" "horizontal_concat" "dataframe_arithmetic" "product" "describe" "chunked_ids" "partition_by" "algorithm_group_by" ];
+          "dtype-array" = [ "arrow/dtype-array" "polars-compute/dtype-array" ];
+          "dtype-date" = [ "temporal" ];
+          "dtype-datetime" = [ "temporal" ];
+          "dtype-decimal" = [ "dep:itoap" "arrow/dtype-decimal" ];
+          "dtype-duration" = [ "temporal" ];
+          "dtype-time" = [ "temporal" ];
+          "dynamic_group_by" = [ "dtype-datetime" "dtype-date" ];
+          "fmt" = [ "comfy-table/tty" ];
+          "fmt_no_tty" = [ "comfy-table" ];
+          "ndarray" = [ "dep:ndarray" ];
+          "nightly" = [ "simd" "hashbrown/nightly" "polars-utils/nightly" "arrow/nightly" ];
+          "object" = [ "serde_json" ];
+          "performant" = [ "arrow/performant" "reinterpret" ];
+          "rand" = [ "dep:rand" ];
+          "rand_distr" = [ "dep:rand_distr" ];
+          "random" = [ "rand" "rand_distr" ];
+          "regex" = [ "dep:regex" ];
+          "serde" = [ "dep:serde" "smartstring/serde" "bitflags/serde" ];
+          "serde-lazy" = [ "serde" "arrow/serde" "indexmap/serde" "smartstring/serde" "chrono/serde" ];
+          "serde_json" = [ "dep:serde_json" ];
+          "simd" = [ "arrow/simd" "polars-compute/simd" ];
+          "strings" = [ "regex" "arrow/strings" "polars-error/regex" ];
+          "temporal" = [ "regex" "chrono" "polars-error/regex" ];
+          "timezones" = [ "chrono-tz" "arrow/chrono-tz" "arrow/timezones" ];
+        };
+        resolvedDefaultFeatures = [ "algorithm_group_by" "chrono" "chunked_ids" "comfy-table" "docs" "dtype-date" "dtype-datetime" "dtype-duration" "dtype-i16" "dtype-i8" "dtype-time" "fmt" "lazy" "rand" "rand_distr" "random" "regex" "reinterpret" "rows" "strings" "temporal" "zip_with" ];
+      };
+      "polars-error" = rec {
+        crateName = "polars-error";
+        version = "0.36.2";
+        edition = "2021";
+        sha256 = "11m80a899kp45b3dz9jbvrn5001hw8izbdp7d2czrswrixwdx5k3";
+        authors = [
+          "Ritchie Vink <ritchie46@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "arrow-format";
+            packageId = "arrow-format";
+            optional = true;
+          }
+          {
+            name = "regex";
+            packageId = "regex";
+            optional = true;
+          }
+          {
+            name = "simdutf8";
+            packageId = "simdutf8";
+          }
+          {
+            name = "thiserror";
+            packageId = "thiserror";
+          }
+        ];
+        features = {
+          "arrow-format" = [ "dep:arrow-format" ];
+          "avro-schema" = [ "dep:avro-schema" ];
+          "object_store" = [ "dep:object_store" ];
+          "regex" = [ "dep:regex" ];
+        };
+        resolvedDefaultFeatures = [ "arrow-format" "regex" ];
+      };
+      "polars-io" = rec {
+        crateName = "polars-io";
+        version = "0.36.2";
+        edition = "2021";
+        sha256 = "1smamd34ghlxyxdd4aqdplk7k8xm1l626brmzlc4fvwlx3pmh13x";
+        authors = [
+          "Ritchie Vink <ritchie46@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "ahash";
+            packageId = "ahash";
+          }
+          {
+            name = "async-trait";
+            packageId = "async-trait";
+            optional = true;
+          }
+          {
+            name = "atoi_simd";
+            packageId = "atoi_simd";
+            optional = true;
+          }
+          {
+            name = "bytes";
+            packageId = "bytes";
+          }
+          {
+            name = "chrono";
+            packageId = "chrono";
+            optional = true;
+            usesDefaultFeatures = false;
+            features = [ "std" ];
+          }
+          {
+            name = "fast-float";
+            packageId = "fast-float";
+            optional = true;
+          }
+          {
+            name = "futures";
+            packageId = "futures";
+            optional = true;
+          }
+          {
+            name = "home";
+            packageId = "home";
+            target = { target, features }: (!(builtins.elem "wasm" target."family"));
+          }
+          {
+            name = "itoa";
+            packageId = "itoa";
+            optional = true;
+          }
+          {
+            name = "memchr";
+            packageId = "memchr";
+          }
+          {
+            name = "memmap2";
+            packageId = "memmap2";
+            rename = "memmap";
+          }
+          {
+            name = "num-traits";
+            packageId = "num-traits";
+          }
+          {
+            name = "once_cell";
+            packageId = "once_cell";
+          }
+          {
+            name = "percent-encoding";
+            packageId = "percent-encoding";
+          }
+          {
+            name = "polars-arrow";
+            packageId = "polars-arrow";
+            rename = "arrow";
+            usesDefaultFeatures = false;
+            features = [ "compute_aggregate" "compute_arithmetics" "compute_boolean" "compute_boolean_kleene" "compute_cast" "compute_comparison" "compute_concatenate" "compute_filter" "compute_if_then_else" ];
+          }
+          {
+            name = "polars-core";
+            packageId = "polars-core";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-error";
+            packageId = "polars-error";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-parquet";
+            packageId = "polars-parquet";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-time";
+            packageId = "polars-time";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-utils";
+            packageId = "polars-utils";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "rayon";
+            packageId = "rayon";
+          }
+          {
+            name = "regex";
+            packageId = "regex";
+          }
+          {
+            name = "ryu";
+            packageId = "ryu";
+            optional = true;
+          }
+          {
+            name = "simdutf8";
+            packageId = "simdutf8";
+            optional = true;
+          }
+          {
+            name = "smartstring";
+            packageId = "smartstring";
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+            optional = true;
+            features = [ "net" "rt-multi-thread" "time" "sync" ];
+          }
+          {
+            name = "tokio-util";
+            packageId = "tokio-util";
+            optional = true;
+            features = [ "io" "io-util" ];
+          }
+        ];
+        features = {
+          "async" = [ "async-trait" "futures" "tokio" "tokio-util" "arrow/io_ipc_write_async" "polars-error/regex" "polars-parquet?/async" ];
+          "async-trait" = [ "dep:async-trait" ];
+          "atoi_simd" = [ "dep:atoi_simd" ];
+          "avro" = [ "arrow/io_avro" "arrow/io_avro_compression" ];
+          "aws" = [ "object_store/aws" "cloud" "reqwest" ];
+          "azure" = [ "object_store/azure" "cloud" ];
+          "chrono" = [ "dep:chrono" ];
+          "chrono-tz" = [ "dep:chrono-tz" ];
+          "cloud" = [ "object_store" "async" "polars-error/object_store" "url" ];
+          "csv" = [ "atoi_simd" "polars-core/rows" "itoa" "ryu" "fast-float" "simdutf8" ];
+          "decompress" = [ "flate2/rust_backend" "zstd" ];
+          "decompress-fast" = [ "flate2/zlib-ng" "zstd" ];
+          "default" = [ "decompress" ];
+          "dtype-categorical" = [ "polars-core/dtype-categorical" ];
+          "dtype-date" = [ "polars-core/dtype-date" "polars-time/dtype-date" ];
+          "dtype-datetime" = [ "polars-core/dtype-datetime" "polars-core/temporal" "polars-time/dtype-datetime" "chrono" ];
+          "dtype-decimal" = [ "polars-core/dtype-decimal" ];
+          "dtype-struct" = [ "polars-core/dtype-struct" ];
+          "dtype-time" = [ "polars-core/dtype-time" "polars-core/temporal" "polars-time/dtype-time" ];
+          "fast-float" = [ "dep:fast-float" ];
+          "flate2" = [ "dep:flate2" ];
+          "fmt" = [ "polars-core/fmt" ];
+          "futures" = [ "dep:futures" ];
+          "gcp" = [ "object_store/gcp" "cloud" ];
+          "http" = [ "object_store/http" "cloud" ];
+          "ipc" = [ "arrow/io_ipc" "arrow/io_ipc_compression" ];
+          "ipc_streaming" = [ "arrow/io_ipc" "arrow/io_ipc_compression" ];
+          "itoa" = [ "dep:itoa" ];
+          "json" = [ "polars-json" "simd-json" "atoi_simd" "serde_json" "dtype-struct" "csv" ];
+          "object_store" = [ "dep:object_store" ];
+          "parquet" = [ "polars-parquet" "polars-parquet/compression" ];
+          "partition" = [ "polars-core/partition_by" ];
+          "polars-json" = [ "dep:polars-json" ];
+          "polars-parquet" = [ "dep:polars-parquet" ];
+          "polars-time" = [ "dep:polars-time" ];
+          "python" = [ "polars-error/python" ];
+          "reqwest" = [ "dep:reqwest" ];
+          "ryu" = [ "dep:ryu" ];
+          "serde" = [ "dep:serde" "polars-core/serde-lazy" ];
+          "serde_json" = [ "dep:serde_json" ];
+          "simd-json" = [ "dep:simd-json" ];
+          "simdutf8" = [ "dep:simdutf8" ];
+          "temporal" = [ "dtype-datetime" "dtype-date" "dtype-time" ];
+          "timezones" = [ "chrono-tz" "dtype-datetime" ];
+          "tokio" = [ "dep:tokio" ];
+          "tokio-util" = [ "dep:tokio-util" ];
+          "url" = [ "dep:url" ];
+          "zstd" = [ "dep:zstd" ];
+        };
+        resolvedDefaultFeatures = [ "async" "async-trait" "atoi_simd" "chrono" "csv" "dtype-date" "dtype-datetime" "dtype-time" "fast-float" "futures" "ipc" "itoa" "lazy" "parquet" "polars-parquet" "polars-time" "ryu" "simdutf8" "temporal" "tokio" "tokio-util" ];
+      };
+      "polars-lazy" = rec {
+        crateName = "polars-lazy";
+        version = "0.36.2";
+        edition = "2021";
+        sha256 = "1mdml4hs574njb23mb9gl6x92x2bb4vdfzsazkl3ifq516s0awcx";
+        authors = [
+          "Ritchie Vink <ritchie46@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "ahash";
+            packageId = "ahash";
+          }
+          {
+            name = "bitflags";
+            packageId = "bitflags 2.4.2";
+          }
+          {
+            name = "glob";
+            packageId = "glob";
+          }
+          {
+            name = "once_cell";
+            packageId = "once_cell";
+          }
+          {
+            name = "polars-arrow";
+            packageId = "polars-arrow";
+            rename = "arrow";
+            usesDefaultFeatures = false;
+            features = [ "compute_aggregate" "compute_arithmetics" "compute_boolean" "compute_boolean_kleene" "compute_cast" "compute_comparison" "compute_concatenate" "compute_filter" "compute_if_then_else" ];
+          }
+          {
+            name = "polars-core";
+            packageId = "polars-core";
+            usesDefaultFeatures = false;
+            features = [ "lazy" "zip_with" "random" ];
+          }
+          {
+            name = "polars-io";
+            packageId = "polars-io";
+            usesDefaultFeatures = false;
+            features = [ "lazy" ];
+          }
+          {
+            name = "polars-ops";
+            packageId = "polars-ops";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-pipe";
+            packageId = "polars-pipe";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-plan";
+            packageId = "polars-plan";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-time";
+            packageId = "polars-time";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-utils";
+            packageId = "polars-utils";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "rayon";
+            packageId = "rayon";
+          }
+          {
+            name = "smartstring";
+            packageId = "smartstring";
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "version_check";
+            packageId = "version_check";
+          }
+        ];
+        features = {
+          "abs" = [ "polars-plan/abs" ];
+          "approx_unique" = [ "polars-plan/approx_unique" ];
+          "arg_where" = [ "polars-plan/arg_where" ];
+          "array_any_all" = [ "polars-ops/array_any_all" "polars-plan/array_any_all" "dtype-array" ];
+          "asof_join" = [ "polars-plan/asof_join" "polars-time" "polars-ops/asof_join" ];
+          "async" = [ "polars-plan/async" "polars-io/cloud" "polars-pipe?/async" ];
+          "bigidx" = [ "polars-plan/bigidx" ];
+          "binary_encoding" = [ "polars-plan/binary_encoding" ];
+          "chunked_ids" = [ "polars-plan/chunked_ids" "polars-core/chunked_ids" "polars-ops/chunked_ids" ];
+          "cloud" = [ "async" "polars-pipe?/cloud" "polars-plan/cloud" "tokio" "futures" ];
+          "cloud_write" = [ "cloud" ];
+          "coalesce" = [ "polars-plan/coalesce" ];
+          "concat_str" = [ "polars-plan/concat_str" ];
+          "cov" = [ "polars-ops/cov" "polars-plan/cov" ];
+          "cross_join" = [ "polars-plan/cross_join" "polars-pipe?/cross_join" "polars-ops/cross_join" ];
+          "cse" = [ "polars-plan/cse" ];
+          "csv" = [ "polars-io/csv" "polars-plan/csv" "polars-pipe?/csv" ];
+          "cum_agg" = [ "polars-plan/cum_agg" ];
+          "cutqcut" = [ "polars-plan/cutqcut" "polars-ops/cutqcut" ];
+          "date_offset" = [ "polars-plan/date_offset" ];
+          "diff" = [ "polars-plan/diff" "polars-plan/diff" ];
+          "dot_diagram" = [ "polars-plan/dot_diagram" ];
+          "dtype-array" = [ "polars-plan/dtype-array" "polars-pipe?/dtype-array" "polars-ops/dtype-array" ];
+          "dtype-categorical" = [ "polars-plan/dtype-categorical" "polars-pipe?/dtype-categorical" ];
+          "dtype-date" = [ "polars-plan/dtype-date" "polars-time/dtype-date" "temporal" ];
+          "dtype-datetime" = [ "polars-plan/dtype-datetime" "polars-time/dtype-datetime" "temporal" ];
+          "dtype-decimal" = [ "polars-plan/dtype-decimal" "polars-pipe?/dtype-decimal" ];
+          "dtype-duration" = [ "polars-plan/dtype-duration" "polars-time/dtype-duration" "temporal" ];
+          "dtype-i16" = [ "polars-plan/dtype-i16" "polars-pipe?/dtype-i16" ];
+          "dtype-i8" = [ "polars-plan/dtype-i8" "polars-pipe?/dtype-i8" ];
+          "dtype-struct" = [ "polars-plan/dtype-struct" ];
+          "dtype-time" = [ "polars-core/dtype-time" "temporal" ];
+          "dtype-u16" = [ "polars-plan/dtype-u16" "polars-pipe?/dtype-u16" ];
+          "dtype-u8" = [ "polars-plan/dtype-u8" "polars-pipe?/dtype-u8" ];
+          "dynamic_group_by" = [ "polars-plan/dynamic_group_by" "polars-time" "temporal" ];
+          "ewma" = [ "polars-plan/ewma" ];
+          "extract_groups" = [ "polars-plan/extract_groups" ];
+          "extract_jsonpath" = [ "polars-plan/extract_jsonpath" "polars-ops/extract_jsonpath" ];
+          "fmt" = [ "polars-core/fmt" "polars-plan/fmt" ];
+          "fused" = [ "polars-plan/fused" "polars-ops/fused" ];
+          "futures" = [ "dep:futures" ];
+          "hist" = [ "polars-plan/hist" ];
+          "horizontal_concat" = [ "polars-plan/horizontal_concat" "polars-core/horizontal_concat" ];
+          "interpolate" = [ "polars-plan/interpolate" ];
+          "ipc" = [ "polars-io/ipc" "polars-plan/ipc" "polars-pipe?/ipc" ];
+          "is_first_distinct" = [ "polars-plan/is_first_distinct" ];
+          "is_in" = [ "polars-plan/is_in" "polars-ops/is_in" ];
+          "is_last_distinct" = [ "polars-plan/is_last_distinct" ];
+          "is_unique" = [ "polars-plan/is_unique" ];
+          "json" = [ "polars-io/json" "polars-plan/json" "polars-json" "polars-pipe/json" ];
+          "list_any_all" = [ "polars-ops/list_any_all" "polars-plan/list_any_all" ];
+          "list_count" = [ "polars-ops/list_count" "polars-plan/list_count" ];
+          "list_drop_nulls" = [ "polars-ops/list_drop_nulls" "polars-plan/list_drop_nulls" ];
+          "list_gather" = [ "polars-ops/list_gather" "polars-plan/list_gather" ];
+          "list_sample" = [ "polars-ops/list_sample" "polars-plan/list_sample" ];
+          "list_sets" = [ "polars-plan/list_sets" "polars-ops/list_sets" ];
+          "list_to_struct" = [ "polars-plan/list_to_struct" ];
+          "log" = [ "polars-plan/log" ];
+          "merge_sorted" = [ "polars-plan/merge_sorted" ];
+          "meta" = [ "polars-plan/meta" ];
+          "mode" = [ "polars-plan/mode" ];
+          "moment" = [ "polars-plan/moment" "polars-ops/moment" ];
+          "nightly" = [ "polars-core/nightly" "polars-pipe?/nightly" "polars-plan/nightly" ];
+          "object" = [ "polars-plan/object" ];
+          "panic_on_schema" = [ "polars-plan/panic_on_schema" ];
+          "parquet" = [ "polars-io/parquet" "polars-plan/parquet" "polars-pipe?/parquet" ];
+          "pct_change" = [ "polars-plan/pct_change" ];
+          "peaks" = [ "polars-plan/peaks" ];
+          "pivot" = [ "polars-core/rows" "polars-ops/pivot" ];
+          "polars-json" = [ "dep:polars-json" ];
+          "polars-pipe" = [ "dep:polars-pipe" ];
+          "polars-time" = [ "dep:polars-time" ];
+          "propagate_nans" = [ "polars-plan/propagate_nans" ];
+          "pyo3" = [ "dep:pyo3" ];
+          "python" = [ "pyo3" "polars-plan/python" "polars-core/python" "polars-io/python" ];
+          "random" = [ "polars-plan/random" ];
+          "range" = [ "polars-plan/range" ];
+          "rank" = [ "polars-plan/rank" ];
+          "regex" = [ "polars-plan/regex" ];
+          "repeat_by" = [ "polars-plan/repeat_by" ];
+          "replace" = [ "polars-plan/replace" ];
+          "rle" = [ "polars-plan/rle" "polars-ops/rle" ];
+          "rolling_window" = [ "polars-plan/rolling_window" "polars-time/rolling_window" ];
+          "round_series" = [ "polars-plan/round_series" "polars-ops/round_series" ];
+          "row_hash" = [ "polars-plan/row_hash" ];
+          "search_sorted" = [ "polars-plan/search_sorted" ];
+          "semi_anti_join" = [ "polars-plan/semi_anti_join" ];
+          "serde" = [ "polars-plan/serde" "arrow/serde" "polars-core/serde-lazy" "polars-time?/serde" "polars-io/serde" "polars-ops/serde" ];
+          "sign" = [ "polars-plan/sign" ];
+          "streaming" = [ "chunked_ids" "polars-pipe" "polars-plan/streaming" "polars-ops/chunked_ids" ];
+          "string_encoding" = [ "polars-plan/string_encoding" ];
+          "string_pad" = [ "polars-plan/string_pad" ];
+          "string_reverse" = [ "polars-plan/string_reverse" ];
+          "string_to_integer" = [ "polars-plan/string_to_integer" ];
+          "strings" = [ "polars-plan/strings" ];
+          "temporal" = [ "dtype-datetime" "dtype-date" "dtype-time" "dtype-i8" "dtype-i16" "dtype-duration" "polars-plan/temporal" ];
+          "test" = [ "polars-plan/debugging" "panic_on_schema" "rolling_window" "rank" "round_series" "csv" "dtype-categorical" "cum_agg" "regex" "polars-core/fmt" "diff" "abs" "parquet" "ipc" "dtype-date" ];
+          "test_all" = [ "test" "strings" "regex" "ipc" "row_hash" "string_pad" "string_to_integer" "search_sorted" "top_k" "pivot" "semi_anti_join" "cse" ];
+          "timezones" = [ "polars-plan/timezones" ];
+          "tokio" = [ "dep:tokio" ];
+          "top_k" = [ "polars-plan/top_k" ];
+          "trigonometry" = [ "polars-plan/trigonometry" ];
+          "true_div" = [ "polars-plan/true_div" ];
+          "unique_counts" = [ "polars-plan/unique_counts" ];
+        };
+        resolvedDefaultFeatures = [ "abs" "cross_join" "csv" "cum_agg" "dtype-date" "dtype-datetime" "dtype-duration" "dtype-i16" "dtype-i8" "dtype-time" "is_in" "log" "meta" "parquet" "polars-time" "regex" "round_series" "strings" "temporal" "trigonometry" ];
+      };
+      "polars-ops" = rec {
+        crateName = "polars-ops";
+        version = "0.36.2";
+        edition = "2021";
+        sha256 = "13m7dh4vpdmcah04a7kql933l7y7045f0hybbmgff4dbav2ay29f";
+        authors = [
+          "Ritchie Vink <ritchie46@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "ahash";
+            packageId = "ahash";
+          }
+          {
+            name = "argminmax";
+            packageId = "argminmax";
+            usesDefaultFeatures = false;
+            features = [ "float" ];
+          }
+          {
+            name = "bytemuck";
+            packageId = "bytemuck";
+            features = [ "derive" "extern_crate_alloc" ];
+          }
+          {
+            name = "either";
+            packageId = "either";
+          }
+          {
+            name = "hashbrown";
+            packageId = "hashbrown";
+            features = [ "rayon" "ahash" ];
+          }
+          {
+            name = "indexmap";
+            packageId = "indexmap";
+            features = [ "std" ];
+          }
+          {
+            name = "memchr";
+            packageId = "memchr";
+          }
+          {
+            name = "num-traits";
+            packageId = "num-traits";
+          }
+          {
+            name = "polars-arrow";
+            packageId = "polars-arrow";
+            rename = "arrow";
+            usesDefaultFeatures = false;
+            features = [ "compute_aggregate" "compute_arithmetics" "compute_boolean" "compute_boolean_kleene" "compute_cast" "compute_comparison" "compute_concatenate" "compute_filter" "compute_if_then_else" ];
+          }
+          {
+            name = "polars-compute";
+            packageId = "polars-compute";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-core";
+            packageId = "polars-core";
+            usesDefaultFeatures = false;
+            features = [ "algorithm_group_by" "zip_with" ];
+          }
+          {
+            name = "polars-error";
+            packageId = "polars-error";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-utils";
+            packageId = "polars-utils";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "rayon";
+            packageId = "rayon";
+          }
+          {
+            name = "regex";
+            packageId = "regex";
+          }
+          {
+            name = "smartstring";
+            packageId = "smartstring";
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "version_check";
+            packageId = "version_check";
+          }
+        ];
+        features = {
+          "aho-corasick" = [ "dep:aho-corasick" ];
+          "array_any_all" = [ "dtype-array" ];
+          "asof_join" = [ "polars-core/asof_join" ];
+          "base64" = [ "dep:base64" ];
+          "big_idx" = [ "polars-core/bigidx" ];
+          "binary_encoding" = [ "base64" "hex" ];
+          "chrono" = [ "dep:chrono" ];
+          "chrono-tz" = [ "dep:chrono-tz" ];
+          "chunked_ids" = [ "polars-core/chunked_ids" ];
+          "cutqcut" = [ "dtype-categorical" "dtype-struct" ];
+          "dtype-array" = [ "polars-core/dtype-array" ];
+          "dtype-categorical" = [ "polars-core/dtype-categorical" ];
+          "dtype-date" = [ "polars-core/dtype-date" "polars-core/temporal" ];
+          "dtype-datetime" = [ "polars-core/dtype-datetime" "polars-core/temporal" ];
+          "dtype-decimal" = [ "polars-core/dtype-decimal" ];
+          "dtype-duration" = [ "polars-core/dtype-duration" "polars-core/temporal" ];
+          "dtype-i16" = [ "polars-core/dtype-i16" ];
+          "dtype-i8" = [ "polars-core/dtype-i8" ];
+          "dtype-struct" = [ "polars-core/dtype-struct" "polars-core/temporal" ];
+          "dtype-time" = [ "polars-core/dtype-time" "polars-core/temporal" ];
+          "dtype-u16" = [ "polars-core/dtype-u16" ];
+          "dtype-u8" = [ "polars-core/dtype-u8" ];
+          "extract_groups" = [ "dtype-struct" "polars-core/regex" ];
+          "extract_jsonpath" = [ "serde_json" "jsonpath_lib" "polars-json" ];
+          "find_many" = [ "aho-corasick" ];
+          "group_by_list" = [ "polars-core/group_by_list" ];
+          "hex" = [ "dep:hex" ];
+          "hist" = [ "dtype-categorical" "dtype-struct" ];
+          "is_in" = [ "polars-core/reinterpret" ];
+          "jsonpath_lib" = [ "dep:jsonpath_lib" ];
+          "list_to_struct" = [ "polars-core/dtype-struct" ];
+          "nightly" = [ "polars-utils/nightly" ];
+          "object" = [ "polars-core/object" ];
+          "pct_change" = [ "diff" ];
+          "performant" = [ "polars-core/performant" "fused" ];
+          "pivot" = [ "polars-core/reinterpret" ];
+          "polars-json" = [ "dep:polars-json" ];
+          "rand" = [ "dep:rand" ];
+          "rand_distr" = [ "dep:rand_distr" ];
+          "random" = [ "rand" "rand_distr" ];
+          "rank" = [ "rand" ];
+          "replace" = [ "is_in" ];
+          "rle" = [ "dtype-struct" ];
+          "rolling_window" = [ "polars-core/rolling_window" ];
+          "serde" = [ "dep:serde" ];
+          "serde_json" = [ "dep:serde_json" ];
+          "simd" = [ "argminmax/nightly_simd" ];
+          "string_encoding" = [ "base64" "hex" ];
+          "string_pad" = [ "polars-core/strings" ];
+          "string_reverse" = [ "polars-core/strings" "unicode-reverse" ];
+          "string_to_integer" = [ "polars-core/strings" ];
+          "strings" = [ "polars-core/strings" ];
+          "timezones" = [ "chrono-tz" "chrono" ];
+          "unicode-reverse" = [ "dep:unicode-reverse" ];
+        };
+        resolvedDefaultFeatures = [ "abs" "cross_join" "cum_agg" "dtype-date" "dtype-datetime" "dtype-duration" "is_in" "log" "round_series" "search_sorted" "strings" ];
+      };
+      "polars-parquet" = rec {
+        crateName = "polars-parquet";
+        version = "0.36.2";
+        edition = "2021";
+        sha256 = "0gay037sw5hcg2q93pxcfh7krcchl1px8g838d8vhjpnn5klv8kv";
+        authors = [
+          "Jorge C. Leitao <jorgecarleitao@gmail.com>"
+          "Apache Arrow <dev@arrow.apache.org>"
+          "Ritchie Vink <ritchie46@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "ahash";
+            packageId = "ahash";
+          }
+          {
+            name = "async-stream";
+            packageId = "async-stream";
+            optional = true;
+          }
+          {
+            name = "base64";
+            packageId = "base64";
+          }
+          {
+            name = "brotli";
+            packageId = "brotli";
+            optional = true;
+          }
+          {
+            name = "ethnum";
+            packageId = "ethnum";
+          }
+          {
+            name = "flate2";
+            packageId = "flate2";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "futures";
+            packageId = "futures";
+            optional = true;
+          }
+          {
+            name = "lz4";
+            packageId = "lz4";
+            optional = true;
+          }
+          {
+            name = "num-traits";
+            packageId = "num-traits";
+          }
+          {
+            name = "parquet-format-safe";
+            packageId = "parquet-format-safe";
+          }
+          {
+            name = "polars-arrow";
+            packageId = "polars-arrow";
+            rename = "arrow";
+            usesDefaultFeatures = false;
+            features = [ "compute_aggregate" "compute_arithmetics" "compute_boolean" "compute_boolean_kleene" "compute_cast" "compute_comparison" "compute_concatenate" "compute_filter" "compute_if_then_else" "io_ipc" ];
+          }
+          {
+            name = "polars-error";
+            packageId = "polars-error";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-utils";
+            packageId = "polars-utils";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "seq-macro";
+            packageId = "seq-macro";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "simdutf8";
+            packageId = "simdutf8";
+          }
+          {
+            name = "snap";
+            packageId = "snap";
+            optional = true;
+          }
+          {
+            name = "streaming-decompression";
+            packageId = "streaming-decompression";
+          }
+          {
+            name = "zstd";
+            packageId = "zstd";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "async" = [ "async-stream" "futures" "parquet-format-safe/async" ];
+          "async-stream" = [ "dep:async-stream" ];
+          "bloom_filter" = [ "xxhash-rust" ];
+          "brotli" = [ "dep:brotli" ];
+          "compression" = [ "zstd" "gzip" "snappy" "lz4" "brotli" ];
+          "fallible-streaming-iterator" = [ "dep:fallible-streaming-iterator" ];
+          "flate2" = [ "dep:flate2" ];
+          "futures" = [ "dep:futures" ];
+          "gzip" = [ "flate2/rust_backend" ];
+          "gzip_zlib_ng" = [ "flate2/zlib-ng" ];
+          "lz4" = [ "dep:lz4" ];
+          "serde" = [ "dep:serde" ];
+          "serde_types" = [ "serde" ];
+          "snap" = [ "dep:snap" ];
+          "snappy" = [ "snap" ];
+          "xxhash-rust" = [ "dep:xxhash-rust" ];
+          "zstd" = [ "dep:zstd" ];
+        };
+        resolvedDefaultFeatures = [ "async" "async-stream" "brotli" "compression" "flate2" "futures" "gzip" "lz4" "snap" "snappy" "zstd" ];
+      };
+      "polars-pipe" = rec {
+        crateName = "polars-pipe";
+        version = "0.36.2";
+        edition = "2021";
+        sha256 = "00217q51mnq7i57k3sax293xnwghm5hridbpgl11fffcfg8fmdyr";
+        authors = [
+          "Ritchie Vink <ritchie46@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "crossbeam-channel";
+            packageId = "crossbeam-channel";
+          }
+          {
+            name = "crossbeam-queue";
+            packageId = "crossbeam-queue";
+          }
+          {
+            name = "enum_dispatch";
+            packageId = "enum_dispatch";
+          }
+          {
+            name = "hashbrown";
+            packageId = "hashbrown";
+            features = [ "rayon" "ahash" ];
+          }
+          {
+            name = "num-traits";
+            packageId = "num-traits";
+          }
+          {
+            name = "polars-arrow";
+            packageId = "polars-arrow";
+            rename = "arrow";
+            usesDefaultFeatures = false;
+            features = [ "compute_aggregate" "compute_arithmetics" "compute_boolean" "compute_boolean_kleene" "compute_cast" "compute_comparison" "compute_concatenate" "compute_filter" "compute_if_then_else" ];
+          }
+          {
+            name = "polars-compute";
+            packageId = "polars-compute";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-core";
+            packageId = "polars-core";
+            usesDefaultFeatures = false;
+            features = [ "lazy" "zip_with" "random" "rows" "chunked_ids" ];
+          }
+          {
+            name = "polars-io";
+            packageId = "polars-io";
+            usesDefaultFeatures = false;
+            features = [ "ipc" ];
+          }
+          {
+            name = "polars-ops";
+            packageId = "polars-ops";
+            usesDefaultFeatures = false;
+            features = [ "search_sorted" ];
+          }
+          {
+            name = "polars-plan";
+            packageId = "polars-plan";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-row";
+            packageId = "polars-row";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-utils";
+            packageId = "polars-utils";
+            usesDefaultFeatures = false;
+            features = [ "sysinfo" ];
+          }
+          {
+            name = "rayon";
+            packageId = "rayon";
+          }
+          {
+            name = "smartstring";
+            packageId = "smartstring";
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "version_check";
+            packageId = "version_check";
+          }
+        ];
+        features = {
+          "async" = [ "polars-plan/async" "polars-io/async" ];
+          "cloud" = [ "async" "polars-io/cloud" "polars-plan/cloud" "tokio" "futures" ];
+          "cross_join" = [ "polars-ops/cross_join" ];
+          "csv" = [ "polars-plan/csv" "polars-io/csv" ];
+          "dtype-array" = [ "polars-core/dtype-array" ];
+          "dtype-categorical" = [ "polars-core/dtype-categorical" ];
+          "dtype-decimal" = [ "polars-core/dtype-decimal" ];
+          "dtype-i16" = [ "polars-core/dtype-i16" ];
+          "dtype-i8" = [ "polars-core/dtype-i8" ];
+          "dtype-u16" = [ "polars-core/dtype-u16" ];
+          "dtype-u8" = [ "polars-core/dtype-u8" ];
+          "futures" = [ "dep:futures" ];
+          "ipc" = [ "polars-plan/ipc" "polars-io/ipc" ];
+          "json" = [ "polars-plan/json" "polars-io/json" ];
+          "nightly" = [ "polars-core/nightly" "polars-utils/nightly" "hashbrown/nightly" ];
+          "parquet" = [ "polars-plan/parquet" "polars-io/parquet" "polars-io/async" ];
+          "test" = [ "polars-core/chunked_ids" ];
+          "tokio" = [ "dep:tokio" ];
+        };
+        resolvedDefaultFeatures = [ "cross_join" "csv" "dtype-i16" "dtype-i8" "parquet" ];
+      };
+      "polars-plan" = rec {
+        crateName = "polars-plan";
+        version = "0.36.2";
+        edition = "2021";
+        sha256 = "1l5ca38v7ksq4595wdr6wsd74pdgszwivq9y8wfc6l6h4ib1fjiq";
+        authors = [
+          "Ritchie Vink <ritchie46@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "ahash";
+            packageId = "ahash";
+          }
+          {
+            name = "bytemuck";
+            packageId = "bytemuck";
+            features = [ "derive" "extern_crate_alloc" ];
+          }
+          {
+            name = "once_cell";
+            packageId = "once_cell";
+          }
+          {
+            name = "percent-encoding";
+            packageId = "percent-encoding";
+          }
+          {
+            name = "polars-arrow";
+            packageId = "polars-arrow";
+            rename = "arrow";
+            usesDefaultFeatures = false;
+            features = [ "compute_aggregate" "compute_arithmetics" "compute_boolean" "compute_boolean_kleene" "compute_cast" "compute_comparison" "compute_concatenate" "compute_filter" "compute_if_then_else" ];
+          }
+          {
+            name = "polars-core";
+            packageId = "polars-core";
+            usesDefaultFeatures = false;
+            features = [ "lazy" "zip_with" "random" ];
+          }
+          {
+            name = "polars-io";
+            packageId = "polars-io";
+            usesDefaultFeatures = false;
+            features = [ "lazy" ];
+          }
+          {
+            name = "polars-ops";
+            packageId = "polars-ops";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-parquet";
+            packageId = "polars-parquet";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-time";
+            packageId = "polars-time";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-utils";
+            packageId = "polars-utils";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "rayon";
+            packageId = "rayon";
+          }
+          {
+            name = "regex";
+            packageId = "regex";
+            optional = true;
+          }
+          {
+            name = "smartstring";
+            packageId = "smartstring";
+          }
+          {
+            name = "strum_macros";
+            packageId = "strum_macros";
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "version_check";
+            packageId = "version_check";
+          }
+        ];
+        features = {
+          "abs" = [ "polars-ops/abs" ];
+          "approx_unique" = [ "polars-ops/approx_unique" ];
+          "array_any_all" = [ "polars-ops/array_any_all" "dtype-array" ];
+          "asof_join" = [ "polars-core/asof_join" "polars-time" "polars-ops/asof_join" ];
+          "async" = [ "polars-io/async" ];
+          "bigidx" = [ "polars-core/bigidx" ];
+          "binary_encoding" = [ "polars-ops/binary_encoding" ];
+          "chrono" = [ "dep:chrono" ];
+          "chrono-tz" = [ "dep:chrono-tz" ];
+          "chunked_ids" = [ "polars-core/chunked_ids" ];
+          "ciborium" = [ "dep:ciborium" ];
+          "cloud" = [ "async" "polars-io/cloud" ];
+          "cov" = [ "polars-ops/cov" ];
+          "cross_join" = [ "polars-ops/cross_join" ];
+          "csv" = [ "polars-io/csv" ];
+          "cum_agg" = [ "polars-ops/cum_agg" ];
+          "cutqcut" = [ "polars-ops/cutqcut" ];
+          "date_offset" = [ "polars-time" "chrono" ];
+          "diff" = [ "polars-ops/diff" ];
+          "dtype-array" = [ "polars-core/dtype-array" "polars-ops/dtype-array" ];
+          "dtype-categorical" = [ "polars-core/dtype-categorical" ];
+          "dtype-date" = [ "polars-core/dtype-date" "polars-time/dtype-date" "temporal" ];
+          "dtype-datetime" = [ "polars-core/dtype-datetime" "polars-time/dtype-datetime" "temporal" ];
+          "dtype-decimal" = [ "polars-core/dtype-decimal" ];
+          "dtype-duration" = [ "polars-core/dtype-duration" "polars-time/dtype-duration" "temporal" ];
+          "dtype-i16" = [ "polars-core/dtype-i16" ];
+          "dtype-i8" = [ "polars-core/dtype-i8" ];
+          "dtype-struct" = [ "polars-core/dtype-struct" ];
+          "dtype-time" = [ "polars-core/dtype-time" "polars-time/dtype-time" ];
+          "dtype-u16" = [ "polars-core/dtype-u16" ];
+          "dtype-u8" = [ "polars-core/dtype-u8" ];
+          "dynamic_group_by" = [ "polars-core/dynamic_group_by" ];
+          "ewma" = [ "polars-ops/ewma" ];
+          "extract_groups" = [ "regex" "dtype-struct" "polars-ops/extract_groups" ];
+          "extract_jsonpath" = [ "polars-ops/extract_jsonpath" ];
+          "ffi_plugin" = [ "libloading" "polars-ffi" ];
+          "find_many" = [ "polars-ops/find_many" ];
+          "fmt" = [ "polars-core/fmt" ];
+          "fused" = [ "polars-ops/fused" ];
+          "futures" = [ "dep:futures" ];
+          "hist" = [ "polars-ops/hist" ];
+          "interpolate" = [ "polars-ops/interpolate" ];
+          "ipc" = [ "polars-io/ipc" ];
+          "is_first_distinct" = [ "polars-core/is_first_distinct" "polars-ops/is_first_distinct" ];
+          "is_in" = [ "polars-ops/is_in" ];
+          "is_last_distinct" = [ "polars-core/is_last_distinct" "polars-ops/is_last_distinct" ];
+          "is_unique" = [ "polars-ops/is_unique" ];
+          "json" = [ "polars-io/json" "polars-json" ];
+          "libloading" = [ "dep:libloading" ];
+          "list_any_all" = [ "polars-ops/list_any_all" ];
+          "list_count" = [ "polars-ops/list_count" ];
+          "list_drop_nulls" = [ "polars-ops/list_drop_nulls" ];
+          "list_gather" = [ "polars-ops/list_gather" ];
+          "list_sample" = [ "polars-ops/list_sample" ];
+          "list_sets" = [ "polars-ops/list_sets" ];
+          "list_to_struct" = [ "polars-ops/list_to_struct" ];
+          "log" = [ "polars-ops/log" ];
+          "merge_sorted" = [ "polars-ops/merge_sorted" ];
+          "mode" = [ "polars-ops/mode" ];
+          "moment" = [ "polars-ops/moment" ];
+          "nightly" = [ "polars-utils/nightly" "polars-ops/nightly" ];
+          "object" = [ "polars-core/object" ];
+          "parquet" = [ "polars-io/parquet" "polars-parquet" ];
+          "pct_change" = [ "polars-ops/pct_change" ];
+          "peaks" = [ "polars-ops/peaks" ];
+          "pivot" = [ "polars-core/rows" "polars-ops/pivot" ];
+          "polars-ffi" = [ "dep:polars-ffi" ];
+          "polars-json" = [ "dep:polars-json" ];
+          "polars-parquet" = [ "dep:polars-parquet" ];
+          "polars-time" = [ "dep:polars-time" ];
+          "propagate_nans" = [ "polars-ops/propagate_nans" ];
+          "python" = [ "dep:pyo3" "ciborium" ];
+          "random" = [ "polars-core/random" ];
+          "rank" = [ "polars-ops/rank" ];
+          "regex" = [ "dep:regex" ];
+          "repeat_by" = [ "polars-ops/repeat_by" ];
+          "replace" = [ "polars-ops/replace" ];
+          "rle" = [ "polars-ops/rle" ];
+          "rolling_window" = [ "polars-core/rolling_window" "polars-time/rolling_window" "polars-ops/rolling_window" "polars-time/rolling_window" ];
+          "round_series" = [ "polars-ops/round_series" ];
+          "row_hash" = [ "polars-core/row_hash" "polars-ops/hash" ];
+          "search_sorted" = [ "polars-ops/search_sorted" ];
+          "semi_anti_join" = [ "polars-ops/semi_anti_join" ];
+          "serde" = [ "dep:serde" "polars-core/serde-lazy" "polars-time/serde" "polars-io/serde" "polars-ops/serde" ];
+          "string_encoding" = [ "polars-ops/string_encoding" ];
+          "string_pad" = [ "polars-ops/string_pad" ];
+          "string_reverse" = [ "polars-ops/string_reverse" ];
+          "string_to_integer" = [ "polars-ops/string_to_integer" ];
+          "strings" = [ "polars-core/strings" "polars-ops/strings" ];
+          "temporal" = [ "polars-core/temporal" "dtype-date" "dtype-datetime" "dtype-time" "dtype-i8" "dtype-i16" ];
+          "timezones" = [ "chrono-tz" "polars-time/timezones" "polars-core/timezones" "regex" ];
+          "top_k" = [ "polars-ops/top_k" ];
+          "unique_counts" = [ "polars-ops/unique_counts" ];
+        };
+        resolvedDefaultFeatures = [ "abs" "cross_join" "csv" "cum_agg" "dtype-date" "dtype-datetime" "dtype-duration" "dtype-i16" "dtype-i8" "dtype-time" "is_in" "log" "meta" "parquet" "polars-parquet" "polars-time" "regex" "round_series" "strings" "temporal" "trigonometry" ];
+      };
+      "polars-row" = rec {
+        crateName = "polars-row";
+        version = "0.36.2";
+        edition = "2021";
+        sha256 = "0by7x6jlj5dwr3f1gj49v8w65l3kpqhwhzb9qzlv6gdqrdx2ycij";
+        authors = [
+          "Ritchie Vink <ritchie46@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "polars-arrow";
+            packageId = "polars-arrow";
+            rename = "arrow";
+            usesDefaultFeatures = false;
+            features = [ "compute_aggregate" "compute_arithmetics" "compute_boolean" "compute_boolean_kleene" "compute_cast" "compute_comparison" "compute_concatenate" "compute_filter" "compute_if_then_else" ];
+          }
+          {
+            name = "polars-error";
+            packageId = "polars-error";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-utils";
+            packageId = "polars-utils";
+            usesDefaultFeatures = false;
+          }
+        ];
+
+      };
+      "polars-sql" = rec {
+        crateName = "polars-sql";
+        version = "0.36.2";
+        edition = "2021";
+        sha256 = "1dcmm993gycw75a6c5hxcr6h2cy6fa2r3hsbx19h9zgxvxnlq2wz";
+        authors = [
+          "Ritchie Vink <ritchie46@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "polars-arrow";
+            packageId = "polars-arrow";
+            rename = "arrow";
+            usesDefaultFeatures = false;
+            features = [ "compute_aggregate" "compute_arithmetics" "compute_boolean" "compute_boolean_kleene" "compute_cast" "compute_comparison" "compute_concatenate" "compute_filter" "compute_if_then_else" ];
+          }
+          {
+            name = "polars-core";
+            packageId = "polars-core";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-error";
+            packageId = "polars-error";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-lazy";
+            packageId = "polars-lazy";
+            usesDefaultFeatures = false;
+            features = [ "strings" "cross_join" "trigonometry" "abs" "round_series" "log" "regex" "is_in" "meta" "cum_agg" "dtype-date" ];
+          }
+          {
+            name = "polars-plan";
+            packageId = "polars-plan";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "rand";
+            packageId = "rand";
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+          }
+          {
+            name = "serde_json";
+            packageId = "serde_json";
+          }
+          {
+            name = "sqlparser";
+            packageId = "sqlparser";
+          }
+        ];
+        features = {
+          "csv" = [ "polars-lazy/csv" ];
+          "diagonal_concat" = [ "polars-lazy/diagonal_concat" ];
+          "ipc" = [ "polars-lazy/ipc" ];
+          "json" = [ "polars-lazy/json" ];
+          "parquet" = [ "polars-lazy/parquet" ];
+          "semi_anti_join" = [ "polars-lazy/semi_anti_join" ];
+        };
+        resolvedDefaultFeatures = [ "csv" "parquet" ];
+      };
+      "polars-time" = rec {
+        crateName = "polars-time";
+        version = "0.36.2";
+        edition = "2021";
+        sha256 = "1l24fmpv5v1qshxfd4592z8x6bnjlwy4njhf9rcbdlbbr6gn9qny";
+        authors = [
+          "Ritchie Vink <ritchie46@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "atoi";
+            packageId = "atoi";
+          }
+          {
+            name = "chrono";
+            packageId = "chrono";
+            usesDefaultFeatures = false;
+            features = [ "std" ];
+          }
+          {
+            name = "now";
+            packageId = "now";
+          }
+          {
+            name = "once_cell";
+            packageId = "once_cell";
+          }
+          {
+            name = "polars-arrow";
+            packageId = "polars-arrow";
+            rename = "arrow";
+            usesDefaultFeatures = false;
+            features = [ "compute_aggregate" "compute_arithmetics" "compute_boolean" "compute_boolean_kleene" "compute_cast" "compute_comparison" "compute_concatenate" "compute_filter" "compute_if_then_else" "compute" "temporal" ];
+          }
+          {
+            name = "polars-core";
+            packageId = "polars-core";
+            usesDefaultFeatures = false;
+            features = [ "dtype-datetime" "dtype-duration" "dtype-time" "dtype-date" ];
+          }
+          {
+            name = "polars-error";
+            packageId = "polars-error";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-ops";
+            packageId = "polars-ops";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "polars-utils";
+            packageId = "polars-utils";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "regex";
+            packageId = "regex";
+          }
+          {
+            name = "smartstring";
+            packageId = "smartstring";
+          }
+        ];
+        features = {
+          "chrono-tz" = [ "dep:chrono-tz" ];
+          "dtype-date" = [ "polars-core/dtype-date" "polars-core/temporal" ];
+          "dtype-datetime" = [ "polars-core/dtype-date" "polars-core/temporal" ];
+          "dtype-duration" = [ "polars-core/dtype-duration" "polars-core/temporal" ];
+          "dtype-time" = [ "polars-core/dtype-time" "polars-core/temporal" ];
+          "fmt" = [ "polars-core/fmt" ];
+          "rolling_window" = [ "polars-core/rolling_window" "dtype-duration" ];
+          "serde" = [ "dep:serde" ];
+          "test" = [ "dtype-date" "dtype-datetime" "polars-core/fmt" ];
+          "timezones" = [ "chrono-tz" "dtype-datetime" "polars-core/timezones" "arrow/timezones" "polars-ops/timezones" ];
+        };
+        resolvedDefaultFeatures = [ "dtype-date" "dtype-datetime" "dtype-duration" "dtype-time" ];
+      };
+      "polars-utils" = rec {
+        crateName = "polars-utils";
+        version = "0.36.2";
+        edition = "2021";
+        sha256 = "1nmvfqwyzbaxcw457amspz479y5vcnpalq043awxfixdfx5clx5i";
+        authors = [
+          "Ritchie Vink <ritchie46@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "ahash";
+            packageId = "ahash";
+          }
+          {
+            name = "bytemuck";
+            packageId = "bytemuck";
+            features = [ "derive" "extern_crate_alloc" ];
+          }
+          {
+            name = "hashbrown";
+            packageId = "hashbrown";
+            features = [ "rayon" "ahash" ];
+          }
+          {
+            name = "indexmap";
+            packageId = "indexmap";
+            features = [ "std" ];
+          }
+          {
+            name = "num-traits";
+            packageId = "num-traits";
+          }
+          {
+            name = "once_cell";
+            packageId = "once_cell";
+          }
+          {
+            name = "polars-error";
+            packageId = "polars-error";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "rayon";
+            packageId = "rayon";
+          }
+          {
+            name = "smartstring";
+            packageId = "smartstring";
+          }
+          {
+            name = "sysinfo";
+            packageId = "sysinfo";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "version_check";
+            packageId = "version_check";
+          }
+        ];
+        features = {
+          "sysinfo" = [ "dep:sysinfo" ];
+        };
+        resolvedDefaultFeatures = [ "sysinfo" ];
+      };
+      "ppv-lite86" = rec {
+        crateName = "ppv-lite86";
+        version = "0.2.17";
+        edition = "2018";
+        sha256 = "1pp6g52aw970adv3x2310n7glqnji96z0a9wiamzw89ibf0ayh2v";
+        authors = [
+          "The CryptoCorrosion Contributors"
+        ];
+        features = {
+          "default" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "simd" "std" ];
+      };
+      "proc-macro2" = rec {
+        crateName = "proc-macro2";
+        version = "1.0.78";
+        edition = "2021";
+        sha256 = "1bjak27pqdn4f4ih1c9nr3manzyavsgqmf76ygw9k76q8pb2lhp2";
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+          "Alex Crichton <alex@alexcrichton.com>"
+        ];
+        dependencies = [
+          {
+            name = "unicode-ident";
+            packageId = "unicode-ident";
+          }
+        ];
+        features = {
+          "default" = [ "proc-macro" ];
+        };
+        resolvedDefaultFeatures = [ "default" "proc-macro" ];
+      };
+      "quote" = rec {
+        crateName = "quote";
+        version = "1.0.35";
+        edition = "2018";
+        sha256 = "1vv8r2ncaz4pqdr78x7f138ka595sp2ncr1sa2plm4zxbsmwj7i9";
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "default" = [ "proc-macro" ];
+          "proc-macro" = [ "proc-macro2/proc-macro" ];
+        };
+        resolvedDefaultFeatures = [ "default" "proc-macro" ];
+      };
+      "rand" = rec {
+        crateName = "rand";
+        version = "0.8.5";
+        edition = "2018";
+        sha256 = "013l6931nn7gkc23jz5mm3qdhf93jjf0fg64nz2lp4i51qd8vbrl";
+        authors = [
+          "The Rand Project Developers"
+          "The Rust Project Developers"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+            optional = true;
+            usesDefaultFeatures = false;
+            target = { target, features }: (target."unix" or false);
+          }
+          {
+            name = "rand_chacha";
+            packageId = "rand_chacha";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "rand_core";
+            packageId = "rand_core";
+          }
+        ];
+        features = {
+          "alloc" = [ "rand_core/alloc" ];
+          "default" = [ "std" "std_rng" ];
+          "getrandom" = [ "rand_core/getrandom" ];
+          "libc" = [ "dep:libc" ];
+          "log" = [ "dep:log" ];
+          "packed_simd" = [ "dep:packed_simd" ];
+          "rand_chacha" = [ "dep:rand_chacha" ];
+          "serde" = [ "dep:serde" ];
+          "serde1" = [ "serde" "rand_core/serde1" ];
+          "simd_support" = [ "packed_simd" ];
+          "std" = [ "rand_core/std" "rand_chacha/std" "alloc" "getrandom" "libc" ];
+          "std_rng" = [ "rand_chacha" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "getrandom" "libc" "rand_chacha" "small_rng" "std" "std_rng" ];
+      };
+      "rand_chacha" = rec {
+        crateName = "rand_chacha";
+        version = "0.3.1";
+        edition = "2018";
+        sha256 = "123x2adin558xbhvqb8w4f6syjsdkmqff8cxwhmjacpsl1ihmhg6";
+        authors = [
+          "The Rand Project Developers"
+          "The Rust Project Developers"
+          "The CryptoCorrosion Contributors"
+        ];
+        dependencies = [
+          {
+            name = "ppv-lite86";
+            packageId = "ppv-lite86";
+            usesDefaultFeatures = false;
+            features = [ "simd" ];
+          }
+          {
+            name = "rand_core";
+            packageId = "rand_core";
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "serde" = [ "dep:serde" ];
+          "serde1" = [ "serde" ];
+          "std" = [ "ppv-lite86/std" ];
+        };
+        resolvedDefaultFeatures = [ "std" ];
+      };
+      "rand_core" = rec {
+        crateName = "rand_core";
+        version = "0.6.4";
+        edition = "2018";
+        sha256 = "0b4j2v4cb5krak1pv6kakv4sz6xcwbrmy2zckc32hsigbrwy82zc";
+        authors = [
+          "The Rand Project Developers"
+          "The Rust Project Developers"
+        ];
+        dependencies = [
+          {
+            name = "getrandom";
+            packageId = "getrandom";
+            optional = true;
+          }
+        ];
+        features = {
+          "getrandom" = [ "dep:getrandom" ];
+          "serde" = [ "dep:serde" ];
+          "serde1" = [ "serde" ];
+          "std" = [ "alloc" "getrandom" "getrandom/std" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "getrandom" "std" ];
+      };
+      "rand_distr" = rec {
+        crateName = "rand_distr";
+        version = "0.4.3";
+        edition = "2018";
+        sha256 = "0cgfwg3z0pkqhrl0x90c77kx70r6g9z4m6fxq9v0h2ibr2dhpjrj";
+        authors = [
+          "The Rand Project Developers"
+        ];
+        dependencies = [
+          {
+            name = "num-traits";
+            packageId = "num-traits";
+            usesDefaultFeatures = false;
+            features = [ "libm" ];
+          }
+          {
+            name = "rand";
+            packageId = "rand";
+            usesDefaultFeatures = false;
+          }
+        ];
+        devDependencies = [
+          {
+            name = "rand";
+            packageId = "rand";
+            usesDefaultFeatures = false;
+            features = [ "std_rng" "std" "small_rng" ];
+          }
+        ];
+        features = {
+          "alloc" = [ "rand/alloc" ];
+          "default" = [ "std" ];
+          "serde" = [ "dep:serde" ];
+          "serde1" = [ "serde" "rand/serde1" ];
+          "std" = [ "alloc" "rand/std" ];
+          "std_math" = [ "num-traits/std" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "std" ];
+      };
+      "rayon" = rec {
+        crateName = "rayon";
+        version = "1.8.1";
+        edition = "2021";
+        sha256 = "0lg0488xwpj5jsfz2gfczcrpclbjl8221mj5vdrhg8bp3883fwps";
+        authors = [
+          "Niko Matsakis <niko@alum.mit.edu>"
+          "Josh Stone <cuviper@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "either";
+            packageId = "either";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "rayon-core";
+            packageId = "rayon-core";
+          }
+        ];
+        features = {
+          "web_spin_lock" = [ "dep:wasm_sync" "rayon-core/web_spin_lock" ];
+        };
+      };
+      "rayon-core" = rec {
+        crateName = "rayon-core";
+        version = "1.12.1";
+        edition = "2021";
+        links = "rayon-core";
+        sha256 = "1qpwim68ai5h0j7axa8ai8z0payaawv3id0lrgkqmapx7lx8fr8l";
+        authors = [
+          "Niko Matsakis <niko@alum.mit.edu>"
+          "Josh Stone <cuviper@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "crossbeam-deque";
+            packageId = "crossbeam-deque";
+          }
+          {
+            name = "crossbeam-utils";
+            packageId = "crossbeam-utils";
+          }
+        ];
+        features = {
+          "web_spin_lock" = [ "dep:wasm_sync" ];
+        };
+      };
+      "redox_syscall" = rec {
+        crateName = "redox_syscall";
+        version = "0.4.1";
+        edition = "2018";
+        sha256 = "1aiifyz5dnybfvkk4cdab9p2kmphag1yad6iknc7aszlxxldf8j7";
+        libName = "syscall";
+        authors = [
+          "Jeremy Soller <jackpot51@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "bitflags";
+            packageId = "bitflags 1.3.2";
+          }
+        ];
+        features = {
+          "core" = [ "dep:core" ];
+          "rustc-dep-of-std" = [ "core" "bitflags/rustc-dep-of-std" ];
+        };
+      };
+      "regex" = rec {
+        crateName = "regex";
+        version = "1.10.3";
+        edition = "2021";
+        sha256 = "05cvihqy0wgnh9i8a9y2n803n5azg2h0b7nlqy6rsvxhy00vwbdn";
+        authors = [
+          "The Rust Project Developers"
+          "Andrew Gallant <jamslam@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "aho-corasick";
+            packageId = "aho-corasick";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "memchr";
+            packageId = "memchr";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "regex-automata";
+            packageId = "regex-automata";
+            usesDefaultFeatures = false;
+            features = [ "alloc" "syntax" "meta" "nfa-pikevm" ];
+          }
+          {
+            name = "regex-syntax";
+            packageId = "regex-syntax";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "default" = [ "std" "perf" "unicode" "regex-syntax/default" ];
+          "logging" = [ "aho-corasick?/logging" "memchr?/logging" "regex-automata/logging" ];
+          "perf" = [ "perf-cache" "perf-dfa" "perf-onepass" "perf-backtrack" "perf-inline" "perf-literal" ];
+          "perf-backtrack" = [ "regex-automata/nfa-backtrack" ];
+          "perf-dfa" = [ "regex-automata/hybrid" ];
+          "perf-dfa-full" = [ "regex-automata/dfa-build" "regex-automata/dfa-search" ];
+          "perf-inline" = [ "regex-automata/perf-inline" ];
+          "perf-literal" = [ "dep:aho-corasick" "dep:memchr" "regex-automata/perf-literal" ];
+          "perf-onepass" = [ "regex-automata/dfa-onepass" ];
+          "std" = [ "aho-corasick?/std" "memchr?/std" "regex-automata/std" "regex-syntax/std" ];
+          "unicode" = [ "unicode-age" "unicode-bool" "unicode-case" "unicode-gencat" "unicode-perl" "unicode-script" "unicode-segment" "regex-automata/unicode" "regex-syntax/unicode" ];
+          "unicode-age" = [ "regex-automata/unicode-age" "regex-syntax/unicode-age" ];
+          "unicode-bool" = [ "regex-automata/unicode-bool" "regex-syntax/unicode-bool" ];
+          "unicode-case" = [ "regex-automata/unicode-case" "regex-syntax/unicode-case" ];
+          "unicode-gencat" = [ "regex-automata/unicode-gencat" "regex-syntax/unicode-gencat" ];
+          "unicode-perl" = [ "regex-automata/unicode-perl" "regex-automata/unicode-word-boundary" "regex-syntax/unicode-perl" ];
+          "unicode-script" = [ "regex-automata/unicode-script" "regex-syntax/unicode-script" ];
+          "unicode-segment" = [ "regex-automata/unicode-segment" "regex-syntax/unicode-segment" ];
+          "unstable" = [ "pattern" ];
+          "use_std" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "perf" "perf-backtrack" "perf-cache" "perf-dfa" "perf-inline" "perf-literal" "perf-onepass" "std" "unicode" "unicode-age" "unicode-bool" "unicode-case" "unicode-gencat" "unicode-perl" "unicode-script" "unicode-segment" ];
+      };
+      "regex-automata" = rec {
+        crateName = "regex-automata";
+        version = "0.4.5";
+        edition = "2021";
+        sha256 = "1karc80mx15z435rm1jg3sqylnc58nxi15gqypcd1inkzzpqgfav";
+        authors = [
+          "The Rust Project Developers"
+          "Andrew Gallant <jamslam@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "aho-corasick";
+            packageId = "aho-corasick";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "memchr";
+            packageId = "memchr";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "regex-syntax";
+            packageId = "regex-syntax";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "default" = [ "std" "syntax" "perf" "unicode" "meta" "nfa" "dfa" "hybrid" ];
+          "dfa" = [ "dfa-build" "dfa-search" "dfa-onepass" ];
+          "dfa-build" = [ "nfa-thompson" "dfa-search" ];
+          "dfa-onepass" = [ "nfa-thompson" ];
+          "hybrid" = [ "alloc" "nfa-thompson" ];
+          "internal-instrument" = [ "internal-instrument-pikevm" ];
+          "internal-instrument-pikevm" = [ "logging" "std" ];
+          "logging" = [ "dep:log" "aho-corasick?/logging" "memchr?/logging" ];
+          "meta" = [ "syntax" "nfa-pikevm" ];
+          "nfa" = [ "nfa-thompson" "nfa-pikevm" "nfa-backtrack" ];
+          "nfa-backtrack" = [ "nfa-thompson" ];
+          "nfa-pikevm" = [ "nfa-thompson" ];
+          "nfa-thompson" = [ "alloc" ];
+          "perf" = [ "perf-inline" "perf-literal" ];
+          "perf-literal" = [ "perf-literal-substring" "perf-literal-multisubstring" ];
+          "perf-literal-multisubstring" = [ "std" "dep:aho-corasick" ];
+          "perf-literal-substring" = [ "aho-corasick?/perf-literal" "dep:memchr" ];
+          "std" = [ "regex-syntax?/std" "memchr?/std" "aho-corasick?/std" "alloc" ];
+          "syntax" = [ "dep:regex-syntax" "alloc" ];
+          "unicode" = [ "unicode-age" "unicode-bool" "unicode-case" "unicode-gencat" "unicode-perl" "unicode-script" "unicode-segment" "unicode-word-boundary" "regex-syntax?/unicode" ];
+          "unicode-age" = [ "regex-syntax?/unicode-age" ];
+          "unicode-bool" = [ "regex-syntax?/unicode-bool" ];
+          "unicode-case" = [ "regex-syntax?/unicode-case" ];
+          "unicode-gencat" = [ "regex-syntax?/unicode-gencat" ];
+          "unicode-perl" = [ "regex-syntax?/unicode-perl" ];
+          "unicode-script" = [ "regex-syntax?/unicode-script" ];
+          "unicode-segment" = [ "regex-syntax?/unicode-segment" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "dfa-onepass" "dfa-search" "hybrid" "meta" "nfa-backtrack" "nfa-pikevm" "nfa-thompson" "perf-inline" "perf-literal" "perf-literal-multisubstring" "perf-literal-substring" "std" "syntax" "unicode" "unicode-age" "unicode-bool" "unicode-case" "unicode-gencat" "unicode-perl" "unicode-script" "unicode-segment" "unicode-word-boundary" ];
+      };
+      "regex-syntax" = rec {
+        crateName = "regex-syntax";
+        version = "0.8.2";
+        edition = "2021";
+        sha256 = "17rd2s8xbiyf6lb4aj2nfi44zqlj98g2ays8zzj2vfs743k79360";
+        authors = [
+          "The Rust Project Developers"
+          "Andrew Gallant <jamslam@gmail.com>"
+        ];
+        features = {
+          "arbitrary" = [ "dep:arbitrary" ];
+          "default" = [ "std" "unicode" ];
+          "unicode" = [ "unicode-age" "unicode-bool" "unicode-case" "unicode-gencat" "unicode-perl" "unicode-script" "unicode-segment" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" "unicode" "unicode-age" "unicode-bool" "unicode-case" "unicode-gencat" "unicode-perl" "unicode-script" "unicode-segment" ];
+      };
+      "rustc-demangle" = rec {
+        crateName = "rustc-demangle";
+        version = "0.1.23";
+        edition = "2015";
+        sha256 = "0xnbk2bmyzshacjm2g1kd4zzv2y2az14bw3sjccq5qkpmsfvn9nn";
+        authors = [
+          "Alex Crichton <alex@alexcrichton.com>"
+        ];
+        features = {
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "core" = [ "dep:core" ];
+          "rustc-dep-of-std" = [ "core" "compiler_builtins" ];
+        };
+      };
+      "rustc_version" = rec {
+        crateName = "rustc_version";
+        version = "0.4.0";
+        edition = "2018";
+        sha256 = "0rpk9rcdk405xhbmgclsh4pai0svn49x35aggl4nhbkd4a2zb85z";
+        authors = [
+          "Dirkjan Ochtman <dirkjan@ochtman.nl>"
+          "Marvin Löbel <loebel.marvin@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "semver";
+            packageId = "semver";
+          }
+        ];
+
+      };
+      "rustversion" = rec {
+        crateName = "rustversion";
+        version = "1.0.14";
+        edition = "2018";
+        sha256 = "1x1pz1yynk5xzzrazk2svmidj69jhz89dz5vrc28sixl20x1iz3z";
+        procMacro = true;
+        build = "build/build.rs";
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+
+      };
+      "ryu" = rec {
+        crateName = "ryu";
+        version = "1.0.16";
+        edition = "2018";
+        sha256 = "0k7b90xr48ag5bzmfjp82rljasw2fx28xr3bg1lrpx7b5sljm3gr";
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        features = {
+          "no-panic" = [ "dep:no-panic" ];
+        };
+      };
+      "scopeguard" = rec {
+        crateName = "scopeguard";
+        version = "1.2.0";
+        edition = "2015";
+        sha256 = "0jcz9sd47zlsgcnm1hdw0664krxwb5gczlif4qngj2aif8vky54l";
+        authors = [
+          "bluss"
+        ];
+        features = {
+          "default" = [ "use_std" ];
+        };
+      };
+      "semver" = rec {
+        crateName = "semver";
+        version = "1.0.21";
+        edition = "2018";
+        sha256 = "1c49snqlfcx93xym1cgwx8zcspmyyxm37xa2fyfgjx1vhalxfzmr";
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        features = {
+          "default" = [ "std" ];
+          "serde" = [ "dep:serde" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "seq-macro" = rec {
+        crateName = "seq-macro";
+        version = "0.3.5";
+        edition = "2018";
+        sha256 = "1d50kbaslrrd0374ivx15jg57f03y5xzil1wd2ajlvajzlkbzw53";
+        procMacro = true;
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+
+      };
+      "serde" = rec {
+        crateName = "serde";
+        version = "1.0.196";
+        edition = "2018";
+        sha256 = "0civrvhbwwk442xhlkfdkkdn478by486qxmackq6k3501zk2c047";
+        authors = [
+          "Erick Tryzelaar <erick.tryzelaar@gmail.com>"
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "serde_derive";
+            packageId = "serde_derive";
+            optional = true;
+          }
+          {
+            name = "serde_derive";
+            packageId = "serde_derive";
+            target = { target, features }: false;
+          }
+        ];
+        devDependencies = [
+          {
+            name = "serde_derive";
+            packageId = "serde_derive";
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "derive" = [ "serde_derive" ];
+          "serde_derive" = [ "dep:serde_derive" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "derive" "serde_derive" "std" ];
+      };
+      "serde_derive" = rec {
+        crateName = "serde_derive";
+        version = "1.0.196";
+        edition = "2015";
+        sha256 = "0rybziqrfaxkaxrybkhrps7zv3ibxnjdk0fwais16zayr5h57j1k";
+        procMacro = true;
+        authors = [
+          "Erick Tryzelaar <erick.tryzelaar@gmail.com>"
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+            usesDefaultFeatures = false;
+            features = [ "proc-macro" ];
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+            usesDefaultFeatures = false;
+            features = [ "proc-macro" ];
+          }
+          {
+            name = "syn";
+            packageId = "syn 2.0.48";
+            usesDefaultFeatures = false;
+            features = [ "clone-impls" "derive" "parsing" "printing" "proc-macro" ];
+          }
+        ];
+        features = { };
+        resolvedDefaultFeatures = [ "default" ];
+      };
+      "serde_json" = rec {
+        crateName = "serde_json";
+        version = "1.0.113";
+        edition = "2021";
+        sha256 = "0ycaiff7ar4qx5sy9kvi1kv9rnnfl15kcfmhxiiwknn3n5q1p039";
+        authors = [
+          "Erick Tryzelaar <erick.tryzelaar@gmail.com>"
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "itoa";
+            packageId = "itoa";
+          }
+          {
+            name = "ryu";
+            packageId = "ryu";
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+            usesDefaultFeatures = false;
+          }
+        ];
+        devDependencies = [
+          {
+            name = "serde";
+            packageId = "serde";
+            features = [ "derive" ];
+          }
+        ];
+        features = {
+          "alloc" = [ "serde/alloc" ];
+          "default" = [ "std" ];
+          "indexmap" = [ "dep:indexmap" ];
+          "preserve_order" = [ "indexmap" "std" ];
+          "std" = [ "serde/std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "sha2" = rec {
+        crateName = "sha2";
+        version = "0.10.8";
+        edition = "2018";
+        sha256 = "1j1x78zk9il95w9iv46dh9wm73r6xrgj32y6lzzw7bxws9dbfgbr";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        dependencies = [
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "cpufeatures";
+            packageId = "cpufeatures";
+            target = { target, features }: (("aarch64" == target."arch" or null) || ("x86_64" == target."arch" or null) || ("x86" == target."arch" or null));
+          }
+          {
+            name = "digest";
+            packageId = "digest";
+          }
+        ];
+        devDependencies = [
+          {
+            name = "digest";
+            packageId = "digest";
+            features = [ "dev" ];
+          }
+        ];
+        features = {
+          "asm" = [ "sha2-asm" ];
+          "asm-aarch64" = [ "asm" ];
+          "default" = [ "std" ];
+          "oid" = [ "digest/oid" ];
+          "sha2-asm" = [ "dep:sha2-asm" ];
+          "std" = [ "digest/std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "signal-hook-registry" = rec {
+        crateName = "signal-hook-registry";
+        version = "1.4.1";
+        edition = "2015";
+        sha256 = "18crkkw5k82bvcx088xlf5g4n3772m24qhzgfan80nda7d3rn8nq";
+        authors = [
+          "Michal 'vorner' Vaner <vorner@vorner.cz>"
+          "Masaki Hara <ackie.h.gmai@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+          }
+        ];
+
+      };
+      "signature" = rec {
+        crateName = "signature";
+        version = "2.2.0";
+        edition = "2021";
+        sha256 = "1pi9hd5vqfr3q3k49k37z06p7gs5si0in32qia4mmr1dancr6m3p";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        dependencies = [
+          {
+            name = "rand_core";
+            packageId = "rand_core";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "derive" = [ "dep:derive" ];
+          "digest" = [ "dep:digest" ];
+          "rand_core" = [ "dep:rand_core" ];
+          "std" = [ "alloc" "rand_core?/std" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "std" ];
+      };
+      "simdutf8" = rec {
+        crateName = "simdutf8";
+        version = "0.1.4";
+        edition = "2018";
+        sha256 = "0fi6zvnldaw7g726wnm9vvpv4s89s5jsk7fgp3rg2l99amw64zzj";
+        authors = [
+          "Hans Kratz <hans@appfour.com>"
+        ];
+        features = {
+          "default" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "slab" = rec {
+        crateName = "slab";
+        version = "0.4.9";
+        edition = "2018";
+        sha256 = "0rxvsgir0qw5lkycrqgb1cxsvxzjv9bmx73bk5y42svnzfba94lg";
+        authors = [
+          "Carl Lerche <me@carllerche.com>"
+        ];
+        buildDependencies = [
+          {
+            name = "autocfg";
+            packageId = "autocfg";
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "serde" = [ "dep:serde" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "smallvec" = rec {
+        crateName = "smallvec";
+        version = "1.13.1";
+        edition = "2018";
+        sha256 = "1mzk9j117pn3k1gabys0b7nz8cdjsx5xc6q7fwnm8r0an62d7v76";
+        authors = [
+          "The Servo Project Developers"
+        ];
+        features = {
+          "arbitrary" = [ "dep:arbitrary" ];
+          "const_new" = [ "const_generics" ];
+          "drain_keep_rest" = [ "drain_filter" ];
+          "serde" = [ "dep:serde" ];
+        };
+      };
+      "smartstring" = rec {
+        crateName = "smartstring";
+        version = "1.0.1";
+        edition = "2021";
+        sha256 = "0agf4x0jz79r30aqibyfjm1h9hrjdh0harcqcvb2vapv7rijrdrz";
+        authors = [
+          "Bodil Stokke <bodil@bodil.org>"
+        ];
+        dependencies = [
+          {
+            name = "static_assertions";
+            packageId = "static_assertions";
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "autocfg";
+            packageId = "autocfg";
+          }
+          {
+            name = "version_check";
+            packageId = "version_check";
+          }
+        ];
+        features = {
+          "arbitrary" = [ "dep:arbitrary" ];
+          "default" = [ "std" ];
+          "proptest" = [ "dep:proptest" ];
+          "serde" = [ "dep:serde" ];
+          "test" = [ "std" "arbitrary" "arbitrary/derive" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "snap" = rec {
+        crateName = "snap";
+        version = "1.1.1";
+        edition = "2018";
+        sha256 = "0fxw80m831l76a5zxcwmz2aq7mcwc1pp345pnljl4cv1kbxnfsqv";
+        authors = [
+          "Andrew Gallant <jamslam@gmail.com>"
+        ];
+
+      };
+      "socket2" = rec {
+        crateName = "socket2";
+        version = "0.5.5";
+        edition = "2021";
+        sha256 = "1sgq315f1njky114ip7wcy83qlphv9qclprfjwvxcpfblmcsqpvv";
+        authors = [
+          "Alex Crichton <alex@alexcrichton.com>"
+          "Thomas de Zeeuw <thomasdezeeuw@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (target."unix" or false);
+          }
+          {
+            name = "windows-sys";
+            packageId = "windows-sys 0.48.0";
+            target = { target, features }: (target."windows" or false);
+            features = [ "Win32_Foundation" "Win32_Networking_WinSock" "Win32_System_IO" "Win32_System_Threading" "Win32_System_WindowsProgramming" ];
+          }
+        ];
+        features = { };
+        resolvedDefaultFeatures = [ "all" ];
+      };
+      "spki" = rec {
+        crateName = "spki";
+        version = "0.7.3";
+        edition = "2021";
+        sha256 = "17fj8k5fmx4w9mp27l970clrh5qa7r5sjdvbsln987xhb34dc7nr";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        dependencies = [
+          {
+            name = "base64ct";
+            packageId = "base64ct";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "der";
+            packageId = "der";
+            features = [ "oid" ];
+          }
+        ];
+        features = {
+          "alloc" = [ "base64ct?/alloc" "der/alloc" ];
+          "arbitrary" = [ "std" "dep:arbitrary" "der/arbitrary" ];
+          "base64" = [ "dep:base64ct" ];
+          "fingerprint" = [ "sha2" ];
+          "pem" = [ "alloc" "der/pem" ];
+          "sha2" = [ "dep:sha2" ];
+          "std" = [ "der/std" "alloc" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "std" ];
+      };
+      "sqlparser" = rec {
+        crateName = "sqlparser";
+        version = "0.39.0";
+        edition = "2021";
+        sha256 = "1mrbqjdqr179qnhy43d0dnrl3yipsp4qyji5rc68j4fyrg14sfvl";
+        authors = [
+          "Andy Grove <andygrove73@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "log";
+            packageId = "log";
+          }
+        ];
+        features = {
+          "bigdecimal" = [ "dep:bigdecimal" ];
+          "default" = [ "std" ];
+          "json_example" = [ "serde_json" "serde" ];
+          "serde" = [ "dep:serde" ];
+          "serde_json" = [ "dep:serde_json" ];
+          "sqlparser_derive" = [ "dep:sqlparser_derive" ];
+          "visitor" = [ "sqlparser_derive" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "stable_deref_trait" = rec {
+        crateName = "stable_deref_trait";
+        version = "1.2.0";
+        edition = "2015";
+        sha256 = "1lxjr8q2n534b2lhkxd6l6wcddzjvnksi58zv11f9y0jjmr15wd8";
+        authors = [
+          "Robert Grosse <n210241048576@gmail.com>"
+        ];
+        features = {
+          "default" = [ "std" ];
+          "std" = [ "alloc" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "std" ];
+      };
+      "static_assertions" = rec {
+        crateName = "static_assertions";
+        version = "1.1.0";
+        edition = "2015";
+        sha256 = "0gsl6xmw10gvn3zs1rv99laj5ig7ylffnh71f9l34js4nr4r7sx2";
+        authors = [
+          "Nikolai Vazquez"
+        ];
+        features = { };
+      };
+      "streaming-decompression" = rec {
+        crateName = "streaming-decompression";
+        version = "0.1.2";
+        edition = "2018";
+        sha256 = "1wscqj3s30qknda778wf7z99mknk65p0h9hhs658l4pvkfqw6v5z";
+        dependencies = [
+          {
+            name = "fallible-streaming-iterator";
+            packageId = "fallible-streaming-iterator";
+          }
+        ];
+
+      };
+      "streaming-iterator" = rec {
+        crateName = "streaming-iterator";
+        version = "0.1.9";
+        edition = "2021";
+        sha256 = "0845zdv8qb7zwqzglpqc0830i43xh3fb6vqms155wz85qfvk28ib";
+        authors = [
+          "Steven Fackler <sfackler@gmail.com>"
+        ];
+        features = {
+          "std" = [ "alloc" ];
+        };
+      };
+      "strength_reduce" = rec {
+        crateName = "strength_reduce";
+        version = "0.2.4";
+        edition = "2015";
+        sha256 = "10jdq9dijjdkb20wg1dmwg447rnj37jbq0mwvbadvqi2gys5x2gy";
+        authors = [
+          "Elliott Mahler <join.together@gmail.com>"
+        ];
+
+      };
+      "strum" = rec {
+        crateName = "strum";
+        version = "0.25.0";
+        edition = "2018";
+        sha256 = "09g1q55ms8vax1z0mxlbva3vm8n2r1179kfvbccnkjcidzm58399";
+        authors = [
+          "Peter Glotfelty <peter.glotfelty@microsoft.com>"
+        ];
+        features = {
+          "default" = [ "std" ];
+          "derive" = [ "strum_macros" ];
+          "phf" = [ "dep:phf" ];
+          "strum_macros" = [ "dep:strum_macros" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "strum_macros" = rec {
+        crateName = "strum_macros";
+        version = "0.25.3";
+        edition = "2018";
+        sha256 = "184y62g474zqb2f7n16x3ghvlyjbh50viw32p9w9l5lwmjlizp13";
+        procMacro = true;
+        authors = [
+          "Peter Glotfelty <peter.glotfelty@microsoft.com>"
+        ];
+        dependencies = [
+          {
+            name = "heck";
+            packageId = "heck";
+          }
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "rustversion";
+            packageId = "rustversion";
+          }
+          {
+            name = "syn";
+            packageId = "syn 2.0.48";
+            features = [ "parsing" "extra-traits" ];
+          }
+        ];
+
+      };
+      "subtle" = rec {
+        crateName = "subtle";
+        version = "2.5.0";
+        edition = "2018";
+        sha256 = "1g2yjs7gffgmdvkkq0wrrh0pxds3q0dv6dhkw9cdpbib656xdkc1";
+        authors = [
+          "Isis Lovecruft <isis@patternsinthevoid.net>"
+          "Henry de Valence <hdevalence@hdevalence.ca>"
+        ];
+        features = {
+          "default" = [ "std" "i128" ];
+        };
+      };
+      "syn 1.0.109" = rec {
+        crateName = "syn";
+        version = "1.0.109";
+        edition = "2018";
+        sha256 = "0ds2if4600bd59wsv7jjgfkayfzy3hnazs394kz6zdkmna8l3dkj";
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "unicode-ident";
+            packageId = "unicode-ident";
+          }
+        ];
+        features = {
+          "default" = [ "derive" "parsing" "printing" "clone-impls" "proc-macro" ];
+          "printing" = [ "quote" ];
+          "proc-macro" = [ "proc-macro2/proc-macro" "quote/proc-macro" ];
+          "quote" = [ "dep:quote" ];
+          "test" = [ "syn-test-suite/all-features" ];
+        };
+        resolvedDefaultFeatures = [ "clone-impls" "default" "derive" "extra-traits" "full" "parsing" "printing" "proc-macro" "quote" "visit-mut" ];
+      };
+      "syn 2.0.48" = rec {
+        crateName = "syn";
+        version = "2.0.48";
+        edition = "2021";
+        sha256 = "0gqgfygmrxmp8q32lia9p294kdd501ybn6kn2h4gqza0irik2d8g";
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "unicode-ident";
+            packageId = "unicode-ident";
+          }
+        ];
+        features = {
+          "default" = [ "derive" "parsing" "printing" "clone-impls" "proc-macro" ];
+          "printing" = [ "quote" ];
+          "proc-macro" = [ "proc-macro2/proc-macro" "quote/proc-macro" ];
+          "quote" = [ "dep:quote" ];
+          "test" = [ "syn-test-suite/all-features" ];
+        };
+        resolvedDefaultFeatures = [ "clone-impls" "default" "derive" "extra-traits" "full" "parsing" "printing" "proc-macro" "quote" "visit" "visit-mut" ];
+      };
+      "sysinfo" = rec {
+        crateName = "sysinfo";
+        version = "0.30.5";
+        edition = "2018";
+        sha256 = "1clba87ndskvxddrmwysnjccm6vyr75j24p6ck48jqwgii1z7d0z";
+        authors = [
+          "Guillaume Gomez <guillaume1.gomez@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "core-foundation-sys";
+            packageId = "core-foundation-sys";
+            target = { target, features }: (("macos" == target."os" or null) || ("ios" == target."os" or null));
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (!(("unknown" == target."os" or null) || ("wasm32" == target."arch" or null)));
+          }
+          {
+            name = "ntapi";
+            packageId = "ntapi";
+            target = { target, features }: (target."windows" or false);
+          }
+          {
+            name = "once_cell";
+            packageId = "once_cell";
+            target = { target, features }: ((target."windows" or false) || ("linux" == target."os" or null) || ("android" == target."os" or null));
+          }
+          {
+            name = "windows";
+            packageId = "windows";
+            target = { target, features }: (target."windows" or false);
+            features = [ "Wdk_System_SystemInformation" "Wdk_System_SystemServices" "Wdk_System_Threading" "Win32_Foundation" "Win32_NetworkManagement_IpHelper" "Win32_NetworkManagement_NetManagement" "Win32_NetworkManagement_Ndis" "Win32_Networking_WinSock" "Win32_Security" "Win32_Security_Authentication_Identity" "Win32_Security_Authorization" "Win32_Storage_FileSystem" "Win32_System_Com" "Win32_System_Diagnostics_Debug" "Win32_System_IO" "Win32_System_Ioctl" "Win32_System_LibraryLoader" "Win32_System_Kernel" "Win32_System_Memory" "Win32_System_Ole" "Win32_System_Performance" "Win32_System_Power" "Win32_System_ProcessStatus" "Win32_System_Registry" "Win32_System_RemoteDesktop" "Win32_System_Rpc" "Win32_System_SystemInformation" "Win32_System_SystemServices" "Win32_System_Threading" "Win32_System_Variant" "Win32_System_WindowsProgramming" "Win32_System_Wmi" "Win32_UI_Shell" ];
+          }
+        ];
+        features = {
+          "apple-app-store" = [ "apple-sandbox" ];
+          "debug" = [ "libc/extra_traits" ];
+          "default" = [ "multithread" ];
+          "multithread" = [ "rayon" ];
+          "rayon" = [ "dep:rayon" ];
+          "serde" = [ "dep:serde" ];
+        };
+      };
+      "target-features" = rec {
+        crateName = "target-features";
+        version = "0.1.5";
+        edition = "2021";
+        sha256 = "1gb974chm9aj8ifkyibylxkyb5an4bf5y8dxb18pqmck698gmdfg";
+        authors = [
+          "Caleb Zulawski <caleb.zulawski@gmail.com>"
+        ];
+
+      };
+      "thiserror" = rec {
+        crateName = "thiserror";
+        version = "1.0.56";
+        edition = "2021";
+        sha256 = "1b9hnzngjan4d89zjs16i01bcpcnvdwklyh73lj16xk28p37hhym";
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "thiserror-impl";
+            packageId = "thiserror-impl";
+          }
+        ];
+
+      };
+      "thiserror-impl" = rec {
+        crateName = "thiserror-impl";
+        version = "1.0.56";
+        edition = "2021";
+        sha256 = "0w9ldp8fa574ilz4dn7y7scpcq66vdjy59qal8qdpwsh7faal3zs";
+        procMacro = true;
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn 2.0.48";
+          }
+        ];
+
+      };
+      "tokio" = rec {
+        crateName = "tokio";
+        version = "1.36.0";
+        edition = "2021";
+        sha256 = "0c89p36zbd4abr1z3l5mipp43x7z4c9b4vp4s6r8y0gs2mjmya31";
+        authors = [
+          "Tokio Contributors <team@tokio.rs>"
+        ];
+        dependencies = [
+          {
+            name = "backtrace";
+            packageId = "backtrace";
+            target = { target, features }: (target."tokio_taskdump" or false);
+          }
+          {
+            name = "bytes";
+            packageId = "bytes";
+            optional = true;
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            optional = true;
+            target = { target, features }: (target."unix" or false);
+          }
+          {
+            name = "mio";
+            packageId = "mio";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "num_cpus";
+            packageId = "num_cpus";
+            optional = true;
+          }
+          {
+            name = "parking_lot";
+            packageId = "parking_lot";
+            optional = true;
+          }
+          {
+            name = "pin-project-lite";
+            packageId = "pin-project-lite";
+          }
+          {
+            name = "signal-hook-registry";
+            packageId = "signal-hook-registry";
+            optional = true;
+            target = { target, features }: (target."unix" or false);
+          }
+          {
+            name = "socket2";
+            packageId = "socket2";
+            optional = true;
+            target = { target, features }: (!(builtins.elem "wasm" target."family"));
+            features = [ "all" ];
+          }
+          {
+            name = "tokio-macros";
+            packageId = "tokio-macros";
+            optional = true;
+          }
+          {
+            name = "windows-sys";
+            packageId = "windows-sys 0.48.0";
+            optional = true;
+            target = { target, features }: (target."windows" or false);
+          }
+        ];
+        devDependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (target."unix" or false);
+          }
+          {
+            name = "socket2";
+            packageId = "socket2";
+            target = { target, features }: (!(builtins.elem "wasm" target."family"));
+          }
+          {
+            name = "windows-sys";
+            packageId = "windows-sys 0.48.0";
+            target = { target, features }: (target."windows" or false);
+            features = [ "Win32_Foundation" "Win32_Security_Authorization" ];
+          }
+        ];
+        features = {
+          "bytes" = [ "dep:bytes" ];
+          "full" = [ "fs" "io-util" "io-std" "macros" "net" "parking_lot" "process" "rt" "rt-multi-thread" "signal" "sync" "time" ];
+          "io-util" = [ "bytes" ];
+          "libc" = [ "dep:libc" ];
+          "macros" = [ "tokio-macros" ];
+          "mio" = [ "dep:mio" ];
+          "net" = [ "libc" "mio/os-poll" "mio/os-ext" "mio/net" "socket2" "windows-sys/Win32_Foundation" "windows-sys/Win32_Security" "windows-sys/Win32_Storage_FileSystem" "windows-sys/Win32_System_Pipes" "windows-sys/Win32_System_SystemServices" ];
+          "num_cpus" = [ "dep:num_cpus" ];
+          "parking_lot" = [ "dep:parking_lot" ];
+          "process" = [ "bytes" "libc" "mio/os-poll" "mio/os-ext" "mio/net" "signal-hook-registry" "windows-sys/Win32_Foundation" "windows-sys/Win32_System_Threading" "windows-sys/Win32_System_WindowsProgramming" ];
+          "rt-multi-thread" = [ "num_cpus" "rt" ];
+          "signal" = [ "libc" "mio/os-poll" "mio/net" "mio/os-ext" "signal-hook-registry" "windows-sys/Win32_Foundation" "windows-sys/Win32_System_Console" ];
+          "signal-hook-registry" = [ "dep:signal-hook-registry" ];
+          "socket2" = [ "dep:socket2" ];
+          "test-util" = [ "rt" "sync" "time" ];
+          "tokio-macros" = [ "dep:tokio-macros" ];
+          "tracing" = [ "dep:tracing" ];
+          "windows-sys" = [ "dep:windows-sys" ];
+        };
+        resolvedDefaultFeatures = [ "bytes" "default" "fs" "full" "io-std" "io-util" "libc" "macros" "mio" "net" "num_cpus" "parking_lot" "process" "rt" "rt-multi-thread" "signal" "signal-hook-registry" "socket2" "sync" "time" "tokio-macros" "windows-sys" ];
+      };
+      "tokio-macros" = rec {
+        crateName = "tokio-macros";
+        version = "2.2.0";
+        edition = "2021";
+        sha256 = "0fwjy4vdx1h9pi4g2nml72wi0fr27b5m954p13ji9anyy8l1x2jv";
+        procMacro = true;
+        authors = [
+          "Tokio Contributors <team@tokio.rs>"
+        ];
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn 2.0.48";
+            features = [ "full" ];
+          }
+        ];
+
+      };
+      "tokio-util" = rec {
+        crateName = "tokio-util";
+        version = "0.7.10";
+        edition = "2021";
+        sha256 = "058y6x4mf0fsqji9rfyb77qbfyc50y4pk2spqgj6xsyr693z66al";
+        authors = [
+          "Tokio Contributors <team@tokio.rs>"
+        ];
+        dependencies = [
+          {
+            name = "bytes";
+            packageId = "bytes";
+          }
+          {
+            name = "futures-core";
+            packageId = "futures-core";
+          }
+          {
+            name = "futures-sink";
+            packageId = "futures-sink";
+          }
+          {
+            name = "pin-project-lite";
+            packageId = "pin-project-lite";
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "sync" ];
+          }
+        ];
+        devDependencies = [
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "full" ];
+          }
+        ];
+        features = {
+          "__docs_rs" = [ "futures-util" ];
+          "codec" = [ "tracing" ];
+          "compat" = [ "futures-io" ];
+          "full" = [ "codec" "compat" "io-util" "time" "net" "rt" ];
+          "futures-io" = [ "dep:futures-io" ];
+          "futures-util" = [ "dep:futures-util" ];
+          "hashbrown" = [ "dep:hashbrown" ];
+          "io-util" = [ "io" "tokio/rt" "tokio/io-util" ];
+          "net" = [ "tokio/net" ];
+          "rt" = [ "tokio/rt" "tokio/sync" "futures-util" "hashbrown" ];
+          "slab" = [ "dep:slab" ];
+          "time" = [ "tokio/time" "slab" ];
+          "tracing" = [ "dep:tracing" ];
+        };
+        resolvedDefaultFeatures = [ "default" "io" "io-util" ];
+      };
+      "typenum" = rec {
+        crateName = "typenum";
+        version = "1.17.0";
+        edition = "2018";
+        sha256 = "09dqxv69m9lj9zvv6xw5vxaqx15ps0vxyy5myg33i0kbqvq0pzs2";
+        build = "build/main.rs";
+        authors = [
+          "Paho Lurie-Gregg <paho@paholg.com>"
+          "Andre Bogus <bogusandre@gmail.com>"
+        ];
+        features = {
+          "scale-info" = [ "dep:scale-info" ];
+          "scale_info" = [ "scale-info/derive" ];
+        };
+      };
+      "unicode-ident" = rec {
+        crateName = "unicode-ident";
+        version = "1.0.12";
+        edition = "2018";
+        sha256 = "0jzf1znfpb2gx8nr8mvmyqs1crnv79l57nxnbiszc7xf7ynbjm1k";
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+
+      };
+      "unicode-width" = rec {
+        crateName = "unicode-width";
+        version = "0.1.11";
+        edition = "2015";
+        sha256 = "11ds4ydhg8g7l06rlmh712q41qsrd0j0h00n1jm74kww3kqk65z5";
+        authors = [
+          "kwantam <kwantam@gmail.com>"
+          "Manish Goregaokar <manishsmail@gmail.com>"
+        ];
+        features = {
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "core" = [ "dep:core" ];
+          "rustc-dep-of-std" = [ "std" "core" "compiler_builtins" ];
+          "std" = [ "dep:std" ];
+        };
+        resolvedDefaultFeatures = [ "default" ];
+      };
+      "version_check" = rec {
+        crateName = "version_check";
+        version = "0.9.4";
+        edition = "2015";
+        sha256 = "0gs8grwdlgh0xq660d7wr80x14vxbizmd8dbp29p2pdncx8lp1s9";
+        authors = [
+          "Sergio Benitez <sb@sergio.bz>"
+        ];
+
+      };
+      "wasi" = rec {
+        crateName = "wasi";
+        version = "0.11.0+wasi-snapshot-preview1";
+        edition = "2018";
+        sha256 = "08z4hxwkpdpalxjps1ai9y7ihin26y9f476i53dv98v45gkqg3cw";
+        authors = [
+          "The Cranelift Project Developers"
+        ];
+        features = {
+          "compiler_builtins" = [ "dep:compiler_builtins" ];
+          "core" = [ "dep:core" ];
+          "default" = [ "std" ];
+          "rustc-dep-of-std" = [ "compiler_builtins" "core" "rustc-std-workspace-alloc" ];
+          "rustc-std-workspace-alloc" = [ "dep:rustc-std-workspace-alloc" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "wasm-bindgen" = rec {
+        crateName = "wasm-bindgen";
+        version = "0.2.91";
+        edition = "2018";
+        sha256 = "0zwbb07ln4m5hh6axamc701nnj090nd66syxbf6bagzf189j9qf1";
+        authors = [
+          "The wasm-bindgen Developers"
+        ];
+        dependencies = [
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "wasm-bindgen-macro";
+            packageId = "wasm-bindgen-macro";
+          }
+        ];
+        features = {
+          "default" = [ "spans" "std" ];
+          "enable-interning" = [ "std" ];
+          "gg-alloc" = [ "wasm-bindgen-test/gg-alloc" ];
+          "serde" = [ "dep:serde" ];
+          "serde-serialize" = [ "serde" "serde_json" "std" ];
+          "serde_json" = [ "dep:serde_json" ];
+          "spans" = [ "wasm-bindgen-macro/spans" ];
+          "strict-macro" = [ "wasm-bindgen-macro/strict-macro" ];
+          "xxx_debug_only_print_generated_code" = [ "wasm-bindgen-macro/xxx_debug_only_print_generated_code" ];
+        };
+        resolvedDefaultFeatures = [ "default" "spans" "std" ];
+      };
+      "wasm-bindgen-backend" = rec {
+        crateName = "wasm-bindgen-backend";
+        version = "0.2.91";
+        edition = "2018";
+        sha256 = "02zpi9sjzhd8kfv1yj9m1bs4a41ik9ii5bc8hjf60arm1j8f3ry9";
+        authors = [
+          "The wasm-bindgen Developers"
+        ];
+        dependencies = [
+          {
+            name = "bumpalo";
+            packageId = "bumpalo";
+          }
+          {
+            name = "log";
+            packageId = "log";
+          }
+          {
+            name = "once_cell";
+            packageId = "once_cell";
+          }
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn 2.0.48";
+            features = [ "full" ];
+          }
+          {
+            name = "wasm-bindgen-shared";
+            packageId = "wasm-bindgen-shared";
+          }
+        ];
+        features = {
+          "extra-traits" = [ "syn/extra-traits" ];
+        };
+        resolvedDefaultFeatures = [ "spans" ];
+      };
+      "wasm-bindgen-macro" = rec {
+        crateName = "wasm-bindgen-macro";
+        version = "0.2.91";
+        edition = "2018";
+        sha256 = "1va6dilw9kcnvsg5043h5b9mwc5sgq0lyhj9fif2n62qsgigj2mk";
+        procMacro = true;
+        authors = [
+          "The wasm-bindgen Developers"
+        ];
+        dependencies = [
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "wasm-bindgen-macro-support";
+            packageId = "wasm-bindgen-macro-support";
+          }
+        ];
+        features = {
+          "spans" = [ "wasm-bindgen-macro-support/spans" ];
+          "strict-macro" = [ "wasm-bindgen-macro-support/strict-macro" ];
+        };
+        resolvedDefaultFeatures = [ "spans" ];
+      };
+      "wasm-bindgen-macro-support" = rec {
+        crateName = "wasm-bindgen-macro-support";
+        version = "0.2.91";
+        edition = "2018";
+        sha256 = "0rlyl3yzwbcnc691mvx78m1wbqf1qs52mlc3g88bh7ihwrdk4bv4";
+        authors = [
+          "The wasm-bindgen Developers"
+        ];
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn 2.0.48";
+            features = [ "visit" "full" ];
+          }
+          {
+            name = "wasm-bindgen-backend";
+            packageId = "wasm-bindgen-backend";
+          }
+          {
+            name = "wasm-bindgen-shared";
+            packageId = "wasm-bindgen-shared";
+          }
+        ];
+        features = {
+          "extra-traits" = [ "syn/extra-traits" ];
+          "spans" = [ "wasm-bindgen-backend/spans" ];
+        };
+        resolvedDefaultFeatures = [ "spans" ];
+      };
+      "wasm-bindgen-shared" = rec {
+        crateName = "wasm-bindgen-shared";
+        version = "0.2.91";
+        edition = "2018";
+        links = "wasm_bindgen";
+        sha256 = "0f4qmjv57ppwi4xpdxgcd77vz9vmvlrnybg8dj430hzhvk96n62g";
+        authors = [
+          "The wasm-bindgen Developers"
+        ];
+
+      };
+      "weave" = rec {
+        crateName = "weave";
+        version = "0.1.0";
+        edition = "2021";
+        crateBin = [
+          {
+            name = "swizzle";
+            path = "src/bin/swizzle.rs";
+            requiredFeatures = [ ];
+          }
+          {
+            name = "weave";
+            path = "src/main.rs";
+            requiredFeatures = [ ];
+          }
+        ];
+        # We can't filter paths with references in Nix 2.4
+        # See https://github.com/NixOS/nix/issues/5410
+        src =
+          if ((lib.versionOlder builtins.nixVersion "2.4pre20211007") || (lib.versionOlder "2.5" builtins.nixVersion))
+          then lib.cleanSourceWith { filter = sourceFilter; src = ./.; }
+          else ./.;
+        dependencies = [
+          {
+            name = "anyhow";
+            packageId = "anyhow";
+            features = [ "backtrace" ];
+          }
+          {
+            name = "hashbrown";
+            packageId = "hashbrown";
+          }
+          {
+            name = "nix-compat";
+            packageId = "nix-compat";
+          }
+          {
+            name = "owning_ref";
+            packageId = "owning_ref";
+          }
+          {
+            name = "polars";
+            packageId = "polars";
+            features = [ "parquet" ];
+          }
+          {
+            name = "rayon";
+            packageId = "rayon";
+          }
+          {
+            name = "tokio";
+            packageId = "tokio";
+            features = [ "full" ];
+          }
+        ];
+
+      };
+      "winapi" = rec {
+        crateName = "winapi";
+        version = "0.3.9";
+        edition = "2015";
+        sha256 = "06gl025x418lchw1wxj64ycr7gha83m44cjr5sarhynd9xkrm0sw";
+        authors = [
+          "Peter Atashian <retep998@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "winapi-i686-pc-windows-gnu";
+            packageId = "winapi-i686-pc-windows-gnu";
+            target = { target, features }: (pkgs.rust.lib.toRustTarget stdenv.hostPlatform == "i686-pc-windows-gnu");
+          }
+          {
+            name = "winapi-x86_64-pc-windows-gnu";
+            packageId = "winapi-x86_64-pc-windows-gnu";
+            target = { target, features }: (pkgs.rust.lib.toRustTarget stdenv.hostPlatform == "x86_64-pc-windows-gnu");
+          }
+        ];
+        features = {
+          "debug" = [ "impl-debug" ];
+        };
+        resolvedDefaultFeatures = [ "cfg" "consoleapi" "evntrace" "handleapi" "impl-default" "in6addr" "inaddr" "minwinbase" "ntsecapi" "processenv" "synchapi" "winbase" "windef" "winerror" "winioctl" "winuser" ];
+      };
+      "winapi-i686-pc-windows-gnu" = rec {
+        crateName = "winapi-i686-pc-windows-gnu";
+        version = "0.4.0";
+        edition = "2015";
+        sha256 = "1dmpa6mvcvzz16zg6d5vrfy4bxgg541wxrcip7cnshi06v38ffxc";
+        authors = [
+          "Peter Atashian <retep998@gmail.com>"
+        ];
+
+      };
+      "winapi-x86_64-pc-windows-gnu" = rec {
+        crateName = "winapi-x86_64-pc-windows-gnu";
+        version = "0.4.0";
+        edition = "2015";
+        sha256 = "0gqq64czqb64kskjryj8isp62m2sgvx25yyj3kpc2myh85w24bki";
+        authors = [
+          "Peter Atashian <retep998@gmail.com>"
+        ];
+
+      };
+      "windows" = rec {
+        crateName = "windows";
+        version = "0.52.0";
+        edition = "2021";
+        sha256 = "1gnh210qjlprpd1szaq04rjm1zqgdm9j7l9absg0kawi2rwm72p4";
+        authors = [
+          "Microsoft"
+        ];
+        dependencies = [
+          {
+            name = "windows-core";
+            packageId = "windows-core";
+          }
+          {
+            name = "windows-targets";
+            packageId = "windows-targets 0.52.0";
+          }
+        ];
+        features = {
+          "AI_MachineLearning" = [ "AI" ];
+          "ApplicationModel_Activation" = [ "ApplicationModel" ];
+          "ApplicationModel_AppExtensions" = [ "ApplicationModel" ];
+          "ApplicationModel_AppService" = [ "ApplicationModel" ];
+          "ApplicationModel_Appointments" = [ "ApplicationModel" ];
+          "ApplicationModel_Appointments_AppointmentsProvider" = [ "ApplicationModel_Appointments" ];
+          "ApplicationModel_Appointments_DataProvider" = [ "ApplicationModel_Appointments" ];
+          "ApplicationModel_Background" = [ "ApplicationModel" ];
+          "ApplicationModel_Calls" = [ "ApplicationModel" ];
+          "ApplicationModel_Calls_Background" = [ "ApplicationModel_Calls" ];
+          "ApplicationModel_Calls_Provider" = [ "ApplicationModel_Calls" ];
+          "ApplicationModel_Chat" = [ "ApplicationModel" ];
+          "ApplicationModel_CommunicationBlocking" = [ "ApplicationModel" ];
+          "ApplicationModel_Contacts" = [ "ApplicationModel" ];
+          "ApplicationModel_Contacts_DataProvider" = [ "ApplicationModel_Contacts" ];
+          "ApplicationModel_Contacts_Provider" = [ "ApplicationModel_Contacts" ];
+          "ApplicationModel_ConversationalAgent" = [ "ApplicationModel" ];
+          "ApplicationModel_Core" = [ "ApplicationModel" ];
+          "ApplicationModel_DataTransfer" = [ "ApplicationModel" ];
+          "ApplicationModel_DataTransfer_DragDrop" = [ "ApplicationModel_DataTransfer" ];
+          "ApplicationModel_DataTransfer_DragDrop_Core" = [ "ApplicationModel_DataTransfer_DragDrop" ];
+          "ApplicationModel_DataTransfer_ShareTarget" = [ "ApplicationModel_DataTransfer" ];
+          "ApplicationModel_Email" = [ "ApplicationModel" ];
+          "ApplicationModel_Email_DataProvider" = [ "ApplicationModel_Email" ];
+          "ApplicationModel_ExtendedExecution" = [ "ApplicationModel" ];
+          "ApplicationModel_ExtendedExecution_Foreground" = [ "ApplicationModel_ExtendedExecution" ];
+          "ApplicationModel_Holographic" = [ "ApplicationModel" ];
+          "ApplicationModel_LockScreen" = [ "ApplicationModel" ];
+          "ApplicationModel_Payments" = [ "ApplicationModel" ];
+          "ApplicationModel_Payments_Provider" = [ "ApplicationModel_Payments" ];
+          "ApplicationModel_Preview" = [ "ApplicationModel" ];
+          "ApplicationModel_Preview_Holographic" = [ "ApplicationModel_Preview" ];
+          "ApplicationModel_Preview_InkWorkspace" = [ "ApplicationModel_Preview" ];
+          "ApplicationModel_Preview_Notes" = [ "ApplicationModel_Preview" ];
+          "ApplicationModel_Resources" = [ "ApplicationModel" ];
+          "ApplicationModel_Resources_Core" = [ "ApplicationModel_Resources" ];
+          "ApplicationModel_Resources_Management" = [ "ApplicationModel_Resources" ];
+          "ApplicationModel_Search" = [ "ApplicationModel" ];
+          "ApplicationModel_Search_Core" = [ "ApplicationModel_Search" ];
+          "ApplicationModel_Store" = [ "ApplicationModel" ];
+          "ApplicationModel_Store_LicenseManagement" = [ "ApplicationModel_Store" ];
+          "ApplicationModel_Store_Preview" = [ "ApplicationModel_Store" ];
+          "ApplicationModel_Store_Preview_InstallControl" = [ "ApplicationModel_Store_Preview" ];
+          "ApplicationModel_UserActivities" = [ "ApplicationModel" ];
+          "ApplicationModel_UserActivities_Core" = [ "ApplicationModel_UserActivities" ];
+          "ApplicationModel_UserDataAccounts" = [ "ApplicationModel" ];
+          "ApplicationModel_UserDataAccounts_Provider" = [ "ApplicationModel_UserDataAccounts" ];
+          "ApplicationModel_UserDataAccounts_SystemAccess" = [ "ApplicationModel_UserDataAccounts" ];
+          "ApplicationModel_UserDataTasks" = [ "ApplicationModel" ];
+          "ApplicationModel_UserDataTasks_DataProvider" = [ "ApplicationModel_UserDataTasks" ];
+          "ApplicationModel_VoiceCommands" = [ "ApplicationModel" ];
+          "ApplicationModel_Wallet" = [ "ApplicationModel" ];
+          "ApplicationModel_Wallet_System" = [ "ApplicationModel_Wallet" ];
+          "Data_Html" = [ "Data" ];
+          "Data_Json" = [ "Data" ];
+          "Data_Pdf" = [ "Data" ];
+          "Data_Text" = [ "Data" ];
+          "Data_Xml" = [ "Data" ];
+          "Data_Xml_Dom" = [ "Data_Xml" ];
+          "Data_Xml_Xsl" = [ "Data_Xml" ];
+          "Devices_Adc" = [ "Devices" ];
+          "Devices_Adc_Provider" = [ "Devices_Adc" ];
+          "Devices_Background" = [ "Devices" ];
+          "Devices_Bluetooth" = [ "Devices" ];
+          "Devices_Bluetooth_Advertisement" = [ "Devices_Bluetooth" ];
+          "Devices_Bluetooth_Background" = [ "Devices_Bluetooth" ];
+          "Devices_Bluetooth_GenericAttributeProfile" = [ "Devices_Bluetooth" ];
+          "Devices_Bluetooth_Rfcomm" = [ "Devices_Bluetooth" ];
+          "Devices_Custom" = [ "Devices" ];
+          "Devices_Display" = [ "Devices" ];
+          "Devices_Display_Core" = [ "Devices_Display" ];
+          "Devices_Enumeration" = [ "Devices" ];
+          "Devices_Enumeration_Pnp" = [ "Devices_Enumeration" ];
+          "Devices_Geolocation" = [ "Devices" ];
+          "Devices_Geolocation_Geofencing" = [ "Devices_Geolocation" ];
+          "Devices_Geolocation_Provider" = [ "Devices_Geolocation" ];
+          "Devices_Gpio" = [ "Devices" ];
+          "Devices_Gpio_Provider" = [ "Devices_Gpio" ];
+          "Devices_Haptics" = [ "Devices" ];
+          "Devices_HumanInterfaceDevice" = [ "Devices" ];
+          "Devices_I2c" = [ "Devices" ];
+          "Devices_I2c_Provider" = [ "Devices_I2c" ];
+          "Devices_Input" = [ "Devices" ];
+          "Devices_Input_Preview" = [ "Devices_Input" ];
+          "Devices_Lights" = [ "Devices" ];
+          "Devices_Lights_Effects" = [ "Devices_Lights" ];
+          "Devices_Midi" = [ "Devices" ];
+          "Devices_PointOfService" = [ "Devices" ];
+          "Devices_PointOfService_Provider" = [ "Devices_PointOfService" ];
+          "Devices_Portable" = [ "Devices" ];
+          "Devices_Power" = [ "Devices" ];
+          "Devices_Printers" = [ "Devices" ];
+          "Devices_Printers_Extensions" = [ "Devices_Printers" ];
+          "Devices_Pwm" = [ "Devices" ];
+          "Devices_Pwm_Provider" = [ "Devices_Pwm" ];
+          "Devices_Radios" = [ "Devices" ];
+          "Devices_Scanners" = [ "Devices" ];
+          "Devices_Sensors" = [ "Devices" ];
+          "Devices_Sensors_Custom" = [ "Devices_Sensors" ];
+          "Devices_SerialCommunication" = [ "Devices" ];
+          "Devices_SmartCards" = [ "Devices" ];
+          "Devices_Sms" = [ "Devices" ];
+          "Devices_Spi" = [ "Devices" ];
+          "Devices_Spi_Provider" = [ "Devices_Spi" ];
+          "Devices_Usb" = [ "Devices" ];
+          "Devices_WiFi" = [ "Devices" ];
+          "Devices_WiFiDirect" = [ "Devices" ];
+          "Devices_WiFiDirect_Services" = [ "Devices_WiFiDirect" ];
+          "Embedded_DeviceLockdown" = [ "Embedded" ];
+          "Foundation_Collections" = [ "Foundation" ];
+          "Foundation_Diagnostics" = [ "Foundation" ];
+          "Foundation_Metadata" = [ "Foundation" ];
+          "Foundation_Numerics" = [ "Foundation" ];
+          "Gaming_Input" = [ "Gaming" ];
+          "Gaming_Input_Custom" = [ "Gaming_Input" ];
+          "Gaming_Input_ForceFeedback" = [ "Gaming_Input" ];
+          "Gaming_Input_Preview" = [ "Gaming_Input" ];
+          "Gaming_Preview" = [ "Gaming" ];
+          "Gaming_Preview_GamesEnumeration" = [ "Gaming_Preview" ];
+          "Gaming_UI" = [ "Gaming" ];
+          "Gaming_XboxLive" = [ "Gaming" ];
+          "Gaming_XboxLive_Storage" = [ "Gaming_XboxLive" ];
+          "Globalization_Collation" = [ "Globalization" ];
+          "Globalization_DateTimeFormatting" = [ "Globalization" ];
+          "Globalization_Fonts" = [ "Globalization" ];
+          "Globalization_NumberFormatting" = [ "Globalization" ];
+          "Globalization_PhoneNumberFormatting" = [ "Globalization" ];
+          "Graphics_Capture" = [ "Graphics" ];
+          "Graphics_DirectX" = [ "Graphics" ];
+          "Graphics_DirectX_Direct3D11" = [ "Graphics_DirectX" ];
+          "Graphics_Display" = [ "Graphics" ];
+          "Graphics_Display_Core" = [ "Graphics_Display" ];
+          "Graphics_Effects" = [ "Graphics" ];
+          "Graphics_Holographic" = [ "Graphics" ];
+          "Graphics_Imaging" = [ "Graphics" ];
+          "Graphics_Printing" = [ "Graphics" ];
+          "Graphics_Printing3D" = [ "Graphics" ];
+          "Graphics_Printing_OptionDetails" = [ "Graphics_Printing" ];
+          "Graphics_Printing_PrintSupport" = [ "Graphics_Printing" ];
+          "Graphics_Printing_PrintTicket" = [ "Graphics_Printing" ];
+          "Graphics_Printing_Workflow" = [ "Graphics_Printing" ];
+          "Management_Core" = [ "Management" ];
+          "Management_Deployment" = [ "Management" ];
+          "Management_Deployment_Preview" = [ "Management_Deployment" ];
+          "Management_Policies" = [ "Management" ];
+          "Management_Update" = [ "Management" ];
+          "Management_Workplace" = [ "Management" ];
+          "Media_AppBroadcasting" = [ "Media" ];
+          "Media_AppRecording" = [ "Media" ];
+          "Media_Audio" = [ "Media" ];
+          "Media_Capture" = [ "Media" ];
+          "Media_Capture_Core" = [ "Media_Capture" ];
+          "Media_Capture_Frames" = [ "Media_Capture" ];
+          "Media_Casting" = [ "Media" ];
+          "Media_ClosedCaptioning" = [ "Media" ];
+          "Media_ContentRestrictions" = [ "Media" ];
+          "Media_Control" = [ "Media" ];
+          "Media_Core" = [ "Media" ];
+          "Media_Core_Preview" = [ "Media_Core" ];
+          "Media_Devices" = [ "Media" ];
+          "Media_Devices_Core" = [ "Media_Devices" ];
+          "Media_DialProtocol" = [ "Media" ];
+          "Media_Editing" = [ "Media" ];
+          "Media_Effects" = [ "Media" ];
+          "Media_FaceAnalysis" = [ "Media" ];
+          "Media_Import" = [ "Media" ];
+          "Media_MediaProperties" = [ "Media" ];
+          "Media_Miracast" = [ "Media" ];
+          "Media_Ocr" = [ "Media" ];
+          "Media_PlayTo" = [ "Media" ];
+          "Media_Playback" = [ "Media" ];
+          "Media_Playlists" = [ "Media" ];
+          "Media_Protection" = [ "Media" ];
+          "Media_Protection_PlayReady" = [ "Media_Protection" ];
+          "Media_Render" = [ "Media" ];
+          "Media_SpeechRecognition" = [ "Media" ];
+          "Media_SpeechSynthesis" = [ "Media" ];
+          "Media_Streaming" = [ "Media" ];
+          "Media_Streaming_Adaptive" = [ "Media_Streaming" ];
+          "Media_Transcoding" = [ "Media" ];
+          "Networking_BackgroundTransfer" = [ "Networking" ];
+          "Networking_Connectivity" = [ "Networking" ];
+          "Networking_NetworkOperators" = [ "Networking" ];
+          "Networking_Proximity" = [ "Networking" ];
+          "Networking_PushNotifications" = [ "Networking" ];
+          "Networking_ServiceDiscovery" = [ "Networking" ];
+          "Networking_ServiceDiscovery_Dnssd" = [ "Networking_ServiceDiscovery" ];
+          "Networking_Sockets" = [ "Networking" ];
+          "Networking_Vpn" = [ "Networking" ];
+          "Networking_XboxLive" = [ "Networking" ];
+          "Perception_Automation" = [ "Perception" ];
+          "Perception_Automation_Core" = [ "Perception_Automation" ];
+          "Perception_People" = [ "Perception" ];
+          "Perception_Spatial" = [ "Perception" ];
+          "Perception_Spatial_Preview" = [ "Perception_Spatial" ];
+          "Perception_Spatial_Surfaces" = [ "Perception_Spatial" ];
+          "Phone_ApplicationModel" = [ "Phone" ];
+          "Phone_Devices" = [ "Phone" ];
+          "Phone_Devices_Notification" = [ "Phone_Devices" ];
+          "Phone_Devices_Power" = [ "Phone_Devices" ];
+          "Phone_Management" = [ "Phone" ];
+          "Phone_Management_Deployment" = [ "Phone_Management" ];
+          "Phone_Media" = [ "Phone" ];
+          "Phone_Media_Devices" = [ "Phone_Media" ];
+          "Phone_Notification" = [ "Phone" ];
+          "Phone_Notification_Management" = [ "Phone_Notification" ];
+          "Phone_PersonalInformation" = [ "Phone" ];
+          "Phone_PersonalInformation_Provisioning" = [ "Phone_PersonalInformation" ];
+          "Phone_Speech" = [ "Phone" ];
+          "Phone_Speech_Recognition" = [ "Phone_Speech" ];
+          "Phone_StartScreen" = [ "Phone" ];
+          "Phone_System" = [ "Phone" ];
+          "Phone_System_Power" = [ "Phone_System" ];
+          "Phone_System_Profile" = [ "Phone_System" ];
+          "Phone_System_UserProfile" = [ "Phone_System" ];
+          "Phone_System_UserProfile_GameServices" = [ "Phone_System_UserProfile" ];
+          "Phone_System_UserProfile_GameServices_Core" = [ "Phone_System_UserProfile_GameServices" ];
+          "Phone_UI" = [ "Phone" ];
+          "Phone_UI_Input" = [ "Phone_UI" ];
+          "Security_Authentication" = [ "Security" ];
+          "Security_Authentication_Identity" = [ "Security_Authentication" ];
+          "Security_Authentication_Identity_Core" = [ "Security_Authentication_Identity" ];
+          "Security_Authentication_OnlineId" = [ "Security_Authentication" ];
+          "Security_Authentication_Web" = [ "Security_Authentication" ];
+          "Security_Authentication_Web_Core" = [ "Security_Authentication_Web" ];
+          "Security_Authentication_Web_Provider" = [ "Security_Authentication_Web" ];
+          "Security_Authorization" = [ "Security" ];
+          "Security_Authorization_AppCapabilityAccess" = [ "Security_Authorization" ];
+          "Security_Credentials" = [ "Security" ];
+          "Security_Credentials_UI" = [ "Security_Credentials" ];
+          "Security_Cryptography" = [ "Security" ];
+          "Security_Cryptography_Certificates" = [ "Security_Cryptography" ];
+          "Security_Cryptography_Core" = [ "Security_Cryptography" ];
+          "Security_Cryptography_DataProtection" = [ "Security_Cryptography" ];
+          "Security_DataProtection" = [ "Security" ];
+          "Security_EnterpriseData" = [ "Security" ];
+          "Security_ExchangeActiveSyncProvisioning" = [ "Security" ];
+          "Security_Isolation" = [ "Security" ];
+          "Services_Maps" = [ "Services" ];
+          "Services_Maps_Guidance" = [ "Services_Maps" ];
+          "Services_Maps_LocalSearch" = [ "Services_Maps" ];
+          "Services_Maps_OfflineMaps" = [ "Services_Maps" ];
+          "Services_Store" = [ "Services" ];
+          "Services_TargetedContent" = [ "Services" ];
+          "Storage_AccessCache" = [ "Storage" ];
+          "Storage_BulkAccess" = [ "Storage" ];
+          "Storage_Compression" = [ "Storage" ];
+          "Storage_FileProperties" = [ "Storage" ];
+          "Storage_Pickers" = [ "Storage" ];
+          "Storage_Pickers_Provider" = [ "Storage_Pickers" ];
+          "Storage_Provider" = [ "Storage" ];
+          "Storage_Search" = [ "Storage" ];
+          "Storage_Streams" = [ "Storage" ];
+          "System_Diagnostics" = [ "System" ];
+          "System_Diagnostics_DevicePortal" = [ "System_Diagnostics" ];
+          "System_Diagnostics_Telemetry" = [ "System_Diagnostics" ];
+          "System_Diagnostics_TraceReporting" = [ "System_Diagnostics" ];
+          "System_Display" = [ "System" ];
+          "System_Implementation" = [ "System" ];
+          "System_Implementation_FileExplorer" = [ "System_Implementation" ];
+          "System_Inventory" = [ "System" ];
+          "System_Power" = [ "System" ];
+          "System_Profile" = [ "System" ];
+          "System_Profile_SystemManufacturers" = [ "System_Profile" ];
+          "System_RemoteDesktop" = [ "System" ];
+          "System_RemoteDesktop_Input" = [ "System_RemoteDesktop" ];
+          "System_RemoteSystems" = [ "System" ];
+          "System_Threading" = [ "System" ];
+          "System_Threading_Core" = [ "System_Threading" ];
+          "System_Update" = [ "System" ];
+          "System_UserProfile" = [ "System" ];
+          "UI_Accessibility" = [ "UI" ];
+          "UI_ApplicationSettings" = [ "UI" ];
+          "UI_Composition" = [ "UI" ];
+          "UI_Composition_Core" = [ "UI_Composition" ];
+          "UI_Composition_Desktop" = [ "UI_Composition" ];
+          "UI_Composition_Diagnostics" = [ "UI_Composition" ];
+          "UI_Composition_Effects" = [ "UI_Composition" ];
+          "UI_Composition_Interactions" = [ "UI_Composition" ];
+          "UI_Composition_Scenes" = [ "UI_Composition" ];
+          "UI_Core" = [ "UI" ];
+          "UI_Core_AnimationMetrics" = [ "UI_Core" ];
+          "UI_Core_Preview" = [ "UI_Core" ];
+          "UI_Input" = [ "UI" ];
+          "UI_Input_Core" = [ "UI_Input" ];
+          "UI_Input_Inking" = [ "UI_Input" ];
+          "UI_Input_Inking_Analysis" = [ "UI_Input_Inking" ];
+          "UI_Input_Inking_Core" = [ "UI_Input_Inking" ];
+          "UI_Input_Inking_Preview" = [ "UI_Input_Inking" ];
+          "UI_Input_Preview" = [ "UI_Input" ];
+          "UI_Input_Preview_Injection" = [ "UI_Input_Preview" ];
+          "UI_Input_Spatial" = [ "UI_Input" ];
+          "UI_Notifications" = [ "UI" ];
+          "UI_Notifications_Management" = [ "UI_Notifications" ];
+          "UI_Popups" = [ "UI" ];
+          "UI_Shell" = [ "UI" ];
+          "UI_StartScreen" = [ "UI" ];
+          "UI_Text" = [ "UI" ];
+          "UI_Text_Core" = [ "UI_Text" ];
+          "UI_UIAutomation" = [ "UI" ];
+          "UI_UIAutomation_Core" = [ "UI_UIAutomation" ];
+          "UI_ViewManagement" = [ "UI" ];
+          "UI_ViewManagement_Core" = [ "UI_ViewManagement" ];
+          "UI_WebUI" = [ "UI" ];
+          "UI_WebUI_Core" = [ "UI_WebUI" ];
+          "UI_WindowManagement" = [ "UI" ];
+          "UI_WindowManagement_Preview" = [ "UI_WindowManagement" ];
+          "Wdk_Foundation" = [ "Wdk" ];
+          "Wdk_Graphics" = [ "Wdk" ];
+          "Wdk_Graphics_Direct3D" = [ "Wdk_Graphics" ];
+          "Wdk_Storage" = [ "Wdk" ];
+          "Wdk_Storage_FileSystem" = [ "Wdk_Storage" ];
+          "Wdk_Storage_FileSystem_Minifilters" = [ "Wdk_Storage_FileSystem" ];
+          "Wdk_System" = [ "Wdk" ];
+          "Wdk_System_IO" = [ "Wdk_System" ];
+          "Wdk_System_OfflineRegistry" = [ "Wdk_System" ];
+          "Wdk_System_Registry" = [ "Wdk_System" ];
+          "Wdk_System_SystemInformation" = [ "Wdk_System" ];
+          "Wdk_System_SystemServices" = [ "Wdk_System" ];
+          "Wdk_System_Threading" = [ "Wdk_System" ];
+          "Web_AtomPub" = [ "Web" ];
+          "Web_Http" = [ "Web" ];
+          "Web_Http_Diagnostics" = [ "Web_Http" ];
+          "Web_Http_Filters" = [ "Web_Http" ];
+          "Web_Http_Headers" = [ "Web_Http" ];
+          "Web_Syndication" = [ "Web" ];
+          "Web_UI" = [ "Web" ];
+          "Web_UI_Interop" = [ "Web_UI" ];
+          "Win32_AI" = [ "Win32" ];
+          "Win32_AI_MachineLearning" = [ "Win32_AI" ];
+          "Win32_AI_MachineLearning_DirectML" = [ "Win32_AI_MachineLearning" ];
+          "Win32_AI_MachineLearning_WinML" = [ "Win32_AI_MachineLearning" ];
+          "Win32_Data" = [ "Win32" ];
+          "Win32_Data_HtmlHelp" = [ "Win32_Data" ];
+          "Win32_Data_RightsManagement" = [ "Win32_Data" ];
+          "Win32_Data_Xml" = [ "Win32_Data" ];
+          "Win32_Data_Xml_MsXml" = [ "Win32_Data_Xml" ];
+          "Win32_Data_Xml_XmlLite" = [ "Win32_Data_Xml" ];
+          "Win32_Devices" = [ "Win32" ];
+          "Win32_Devices_AllJoyn" = [ "Win32_Devices" ];
+          "Win32_Devices_BiometricFramework" = [ "Win32_Devices" ];
+          "Win32_Devices_Bluetooth" = [ "Win32_Devices" ];
+          "Win32_Devices_Communication" = [ "Win32_Devices" ];
+          "Win32_Devices_DeviceAccess" = [ "Win32_Devices" ];
+          "Win32_Devices_DeviceAndDriverInstallation" = [ "Win32_Devices" ];
+          "Win32_Devices_DeviceQuery" = [ "Win32_Devices" ];
+          "Win32_Devices_Display" = [ "Win32_Devices" ];
+          "Win32_Devices_Enumeration" = [ "Win32_Devices" ];
+          "Win32_Devices_Enumeration_Pnp" = [ "Win32_Devices_Enumeration" ];
+          "Win32_Devices_Fax" = [ "Win32_Devices" ];
+          "Win32_Devices_FunctionDiscovery" = [ "Win32_Devices" ];
+          "Win32_Devices_Geolocation" = [ "Win32_Devices" ];
+          "Win32_Devices_HumanInterfaceDevice" = [ "Win32_Devices" ];
+          "Win32_Devices_ImageAcquisition" = [ "Win32_Devices" ];
+          "Win32_Devices_PortableDevices" = [ "Win32_Devices" ];
+          "Win32_Devices_Properties" = [ "Win32_Devices" ];
+          "Win32_Devices_Pwm" = [ "Win32_Devices" ];
+          "Win32_Devices_Sensors" = [ "Win32_Devices" ];
+          "Win32_Devices_SerialCommunication" = [ "Win32_Devices" ];
+          "Win32_Devices_Tapi" = [ "Win32_Devices" ];
+          "Win32_Devices_Usb" = [ "Win32_Devices" ];
+          "Win32_Devices_WebServicesOnDevices" = [ "Win32_Devices" ];
+          "Win32_Foundation" = [ "Win32" ];
+          "Win32_Gaming" = [ "Win32" ];
+          "Win32_Globalization" = [ "Win32" ];
+          "Win32_Graphics" = [ "Win32" ];
+          "Win32_Graphics_CompositionSwapchain" = [ "Win32_Graphics" ];
+          "Win32_Graphics_DXCore" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Direct2D" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Direct2D_Common" = [ "Win32_Graphics_Direct2D" ];
+          "Win32_Graphics_Direct3D" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Direct3D10" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Direct3D11" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Direct3D11on12" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Direct3D12" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Direct3D9" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Direct3D9on12" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Direct3D_Dxc" = [ "Win32_Graphics_Direct3D" ];
+          "Win32_Graphics_Direct3D_Fxc" = [ "Win32_Graphics_Direct3D" ];
+          "Win32_Graphics_DirectComposition" = [ "Win32_Graphics" ];
+          "Win32_Graphics_DirectDraw" = [ "Win32_Graphics" ];
+          "Win32_Graphics_DirectManipulation" = [ "Win32_Graphics" ];
+          "Win32_Graphics_DirectWrite" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Dwm" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Dxgi" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Dxgi_Common" = [ "Win32_Graphics_Dxgi" ];
+          "Win32_Graphics_Gdi" = [ "Win32_Graphics" ];
+          "Win32_Graphics_GdiPlus" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Hlsl" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Imaging" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Imaging_D2D" = [ "Win32_Graphics_Imaging" ];
+          "Win32_Graphics_OpenGL" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Printing" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Printing_PrintTicket" = [ "Win32_Graphics_Printing" ];
+          "Win32_Management" = [ "Win32" ];
+          "Win32_Management_MobileDeviceManagementRegistration" = [ "Win32_Management" ];
+          "Win32_Media" = [ "Win32" ];
+          "Win32_Media_Audio" = [ "Win32_Media" ];
+          "Win32_Media_Audio_Apo" = [ "Win32_Media_Audio" ];
+          "Win32_Media_Audio_DirectMusic" = [ "Win32_Media_Audio" ];
+          "Win32_Media_Audio_DirectSound" = [ "Win32_Media_Audio" ];
+          "Win32_Media_Audio_Endpoints" = [ "Win32_Media_Audio" ];
+          "Win32_Media_Audio_XAudio2" = [ "Win32_Media_Audio" ];
+          "Win32_Media_DeviceManager" = [ "Win32_Media" ];
+          "Win32_Media_DirectShow" = [ "Win32_Media" ];
+          "Win32_Media_DirectShow_Tv" = [ "Win32_Media_DirectShow" ];
+          "Win32_Media_DirectShow_Xml" = [ "Win32_Media_DirectShow" ];
+          "Win32_Media_DxMediaObjects" = [ "Win32_Media" ];
+          "Win32_Media_KernelStreaming" = [ "Win32_Media" ];
+          "Win32_Media_LibrarySharingServices" = [ "Win32_Media" ];
+          "Win32_Media_MediaFoundation" = [ "Win32_Media" ];
+          "Win32_Media_MediaPlayer" = [ "Win32_Media" ];
+          "Win32_Media_Multimedia" = [ "Win32_Media" ];
+          "Win32_Media_PictureAcquisition" = [ "Win32_Media" ];
+          "Win32_Media_Speech" = [ "Win32_Media" ];
+          "Win32_Media_Streaming" = [ "Win32_Media" ];
+          "Win32_Media_WindowsMediaFormat" = [ "Win32_Media" ];
+          "Win32_NetworkManagement" = [ "Win32" ];
+          "Win32_NetworkManagement_Dhcp" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_Dns" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_InternetConnectionWizard" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_IpHelper" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_MobileBroadband" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_Multicast" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_Ndis" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_NetBios" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_NetManagement" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_NetShell" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_NetworkDiagnosticsFramework" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_NetworkPolicyServer" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_P2P" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_QoS" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_Rras" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_Snmp" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WNet" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WebDav" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WiFi" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WindowsConnectNow" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WindowsConnectionManager" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WindowsFilteringPlatform" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WindowsFirewall" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WindowsNetworkVirtualization" = [ "Win32_NetworkManagement" ];
+          "Win32_Networking" = [ "Win32" ];
+          "Win32_Networking_ActiveDirectory" = [ "Win32_Networking" ];
+          "Win32_Networking_BackgroundIntelligentTransferService" = [ "Win32_Networking" ];
+          "Win32_Networking_Clustering" = [ "Win32_Networking" ];
+          "Win32_Networking_HttpServer" = [ "Win32_Networking" ];
+          "Win32_Networking_Ldap" = [ "Win32_Networking" ];
+          "Win32_Networking_NetworkListManager" = [ "Win32_Networking" ];
+          "Win32_Networking_RemoteDifferentialCompression" = [ "Win32_Networking" ];
+          "Win32_Networking_WebSocket" = [ "Win32_Networking" ];
+          "Win32_Networking_WinHttp" = [ "Win32_Networking" ];
+          "Win32_Networking_WinInet" = [ "Win32_Networking" ];
+          "Win32_Networking_WinSock" = [ "Win32_Networking" ];
+          "Win32_Networking_WindowsWebServices" = [ "Win32_Networking" ];
+          "Win32_Security" = [ "Win32" ];
+          "Win32_Security_AppLocker" = [ "Win32_Security" ];
+          "Win32_Security_Authentication" = [ "Win32_Security" ];
+          "Win32_Security_Authentication_Identity" = [ "Win32_Security_Authentication" ];
+          "Win32_Security_Authentication_Identity_Provider" = [ "Win32_Security_Authentication_Identity" ];
+          "Win32_Security_Authorization" = [ "Win32_Security" ];
+          "Win32_Security_Authorization_UI" = [ "Win32_Security_Authorization" ];
+          "Win32_Security_ConfigurationSnapin" = [ "Win32_Security" ];
+          "Win32_Security_Credentials" = [ "Win32_Security" ];
+          "Win32_Security_Cryptography" = [ "Win32_Security" ];
+          "Win32_Security_Cryptography_Catalog" = [ "Win32_Security_Cryptography" ];
+          "Win32_Security_Cryptography_Certificates" = [ "Win32_Security_Cryptography" ];
+          "Win32_Security_Cryptography_Sip" = [ "Win32_Security_Cryptography" ];
+          "Win32_Security_Cryptography_UI" = [ "Win32_Security_Cryptography" ];
+          "Win32_Security_DiagnosticDataQuery" = [ "Win32_Security" ];
+          "Win32_Security_DirectoryServices" = [ "Win32_Security" ];
+          "Win32_Security_EnterpriseData" = [ "Win32_Security" ];
+          "Win32_Security_ExtensibleAuthenticationProtocol" = [ "Win32_Security" ];
+          "Win32_Security_Isolation" = [ "Win32_Security" ];
+          "Win32_Security_LicenseProtection" = [ "Win32_Security" ];
+          "Win32_Security_NetworkAccessProtection" = [ "Win32_Security" ];
+          "Win32_Security_Tpm" = [ "Win32_Security" ];
+          "Win32_Security_WinTrust" = [ "Win32_Security" ];
+          "Win32_Security_WinWlx" = [ "Win32_Security" ];
+          "Win32_Storage" = [ "Win32" ];
+          "Win32_Storage_Cabinets" = [ "Win32_Storage" ];
+          "Win32_Storage_CloudFilters" = [ "Win32_Storage" ];
+          "Win32_Storage_Compression" = [ "Win32_Storage" ];
+          "Win32_Storage_DataDeduplication" = [ "Win32_Storage" ];
+          "Win32_Storage_DistributedFileSystem" = [ "Win32_Storage" ];
+          "Win32_Storage_EnhancedStorage" = [ "Win32_Storage" ];
+          "Win32_Storage_FileHistory" = [ "Win32_Storage" ];
+          "Win32_Storage_FileServerResourceManager" = [ "Win32_Storage" ];
+          "Win32_Storage_FileSystem" = [ "Win32_Storage" ];
+          "Win32_Storage_Imapi" = [ "Win32_Storage" ];
+          "Win32_Storage_IndexServer" = [ "Win32_Storage" ];
+          "Win32_Storage_InstallableFileSystems" = [ "Win32_Storage" ];
+          "Win32_Storage_IscsiDisc" = [ "Win32_Storage" ];
+          "Win32_Storage_Jet" = [ "Win32_Storage" ];
+          "Win32_Storage_Nvme" = [ "Win32_Storage" ];
+          "Win32_Storage_OfflineFiles" = [ "Win32_Storage" ];
+          "Win32_Storage_OperationRecorder" = [ "Win32_Storage" ];
+          "Win32_Storage_Packaging" = [ "Win32_Storage" ];
+          "Win32_Storage_Packaging_Appx" = [ "Win32_Storage_Packaging" ];
+          "Win32_Storage_Packaging_Opc" = [ "Win32_Storage_Packaging" ];
+          "Win32_Storage_ProjectedFileSystem" = [ "Win32_Storage" ];
+          "Win32_Storage_StructuredStorage" = [ "Win32_Storage" ];
+          "Win32_Storage_Vhd" = [ "Win32_Storage" ];
+          "Win32_Storage_VirtualDiskService" = [ "Win32_Storage" ];
+          "Win32_Storage_Vss" = [ "Win32_Storage" ];
+          "Win32_Storage_Xps" = [ "Win32_Storage" ];
+          "Win32_Storage_Xps_Printing" = [ "Win32_Storage_Xps" ];
+          "Win32_System" = [ "Win32" ];
+          "Win32_System_AddressBook" = [ "Win32_System" ];
+          "Win32_System_Antimalware" = [ "Win32_System" ];
+          "Win32_System_ApplicationInstallationAndServicing" = [ "Win32_System" ];
+          "Win32_System_ApplicationVerifier" = [ "Win32_System" ];
+          "Win32_System_AssessmentTool" = [ "Win32_System" ];
+          "Win32_System_ClrHosting" = [ "Win32_System" ];
+          "Win32_System_Com" = [ "Win32_System" ];
+          "Win32_System_Com_CallObj" = [ "Win32_System_Com" ];
+          "Win32_System_Com_ChannelCredentials" = [ "Win32_System_Com" ];
+          "Win32_System_Com_Events" = [ "Win32_System_Com" ];
+          "Win32_System_Com_Marshal" = [ "Win32_System_Com" ];
+          "Win32_System_Com_StructuredStorage" = [ "Win32_System_Com" ];
+          "Win32_System_Com_UI" = [ "Win32_System_Com" ];
+          "Win32_System_Com_Urlmon" = [ "Win32_System_Com" ];
+          "Win32_System_ComponentServices" = [ "Win32_System" ];
+          "Win32_System_Console" = [ "Win32_System" ];
+          "Win32_System_Contacts" = [ "Win32_System" ];
+          "Win32_System_CorrelationVector" = [ "Win32_System" ];
+          "Win32_System_DataExchange" = [ "Win32_System" ];
+          "Win32_System_DeploymentServices" = [ "Win32_System" ];
+          "Win32_System_DesktopSharing" = [ "Win32_System" ];
+          "Win32_System_DeveloperLicensing" = [ "Win32_System" ];
+          "Win32_System_Diagnostics" = [ "Win32_System" ];
+          "Win32_System_Diagnostics_Ceip" = [ "Win32_System_Diagnostics" ];
+          "Win32_System_Diagnostics_ClrProfiling" = [ "Win32_System_Diagnostics" ];
+          "Win32_System_Diagnostics_Debug" = [ "Win32_System_Diagnostics" ];
+          "Win32_System_Diagnostics_Debug_ActiveScript" = [ "Win32_System_Diagnostics_Debug" ];
+          "Win32_System_Diagnostics_Debug_Extensions" = [ "Win32_System_Diagnostics_Debug" ];
+          "Win32_System_Diagnostics_Etw" = [ "Win32_System_Diagnostics" ];
+          "Win32_System_Diagnostics_ProcessSnapshotting" = [ "Win32_System_Diagnostics" ];
+          "Win32_System_Diagnostics_ToolHelp" = [ "Win32_System_Diagnostics" ];
+          "Win32_System_DistributedTransactionCoordinator" = [ "Win32_System" ];
+          "Win32_System_Environment" = [ "Win32_System" ];
+          "Win32_System_ErrorReporting" = [ "Win32_System" ];
+          "Win32_System_EventCollector" = [ "Win32_System" ];
+          "Win32_System_EventLog" = [ "Win32_System" ];
+          "Win32_System_EventNotificationService" = [ "Win32_System" ];
+          "Win32_System_GroupPolicy" = [ "Win32_System" ];
+          "Win32_System_HostCompute" = [ "Win32_System" ];
+          "Win32_System_HostComputeNetwork" = [ "Win32_System" ];
+          "Win32_System_HostComputeSystem" = [ "Win32_System" ];
+          "Win32_System_Hypervisor" = [ "Win32_System" ];
+          "Win32_System_IO" = [ "Win32_System" ];
+          "Win32_System_Iis" = [ "Win32_System" ];
+          "Win32_System_Ioctl" = [ "Win32_System" ];
+          "Win32_System_JobObjects" = [ "Win32_System" ];
+          "Win32_System_Js" = [ "Win32_System" ];
+          "Win32_System_Kernel" = [ "Win32_System" ];
+          "Win32_System_LibraryLoader" = [ "Win32_System" ];
+          "Win32_System_Mailslots" = [ "Win32_System" ];
+          "Win32_System_Mapi" = [ "Win32_System" ];
+          "Win32_System_Memory" = [ "Win32_System" ];
+          "Win32_System_Memory_NonVolatile" = [ "Win32_System_Memory" ];
+          "Win32_System_MessageQueuing" = [ "Win32_System" ];
+          "Win32_System_MixedReality" = [ "Win32_System" ];
+          "Win32_System_Mmc" = [ "Win32_System" ];
+          "Win32_System_Ole" = [ "Win32_System" ];
+          "Win32_System_ParentalControls" = [ "Win32_System" ];
+          "Win32_System_PasswordManagement" = [ "Win32_System" ];
+          "Win32_System_Performance" = [ "Win32_System" ];
+          "Win32_System_Performance_HardwareCounterProfiling" = [ "Win32_System_Performance" ];
+          "Win32_System_Pipes" = [ "Win32_System" ];
+          "Win32_System_Power" = [ "Win32_System" ];
+          "Win32_System_ProcessStatus" = [ "Win32_System" ];
+          "Win32_System_RealTimeCommunications" = [ "Win32_System" ];
+          "Win32_System_Recovery" = [ "Win32_System" ];
+          "Win32_System_Registry" = [ "Win32_System" ];
+          "Win32_System_RemoteAssistance" = [ "Win32_System" ];
+          "Win32_System_RemoteDesktop" = [ "Win32_System" ];
+          "Win32_System_RemoteManagement" = [ "Win32_System" ];
+          "Win32_System_RestartManager" = [ "Win32_System" ];
+          "Win32_System_Restore" = [ "Win32_System" ];
+          "Win32_System_Rpc" = [ "Win32_System" ];
+          "Win32_System_Search" = [ "Win32_System" ];
+          "Win32_System_Search_Common" = [ "Win32_System_Search" ];
+          "Win32_System_SecurityCenter" = [ "Win32_System" ];
+          "Win32_System_ServerBackup" = [ "Win32_System" ];
+          "Win32_System_Services" = [ "Win32_System" ];
+          "Win32_System_SettingsManagementInfrastructure" = [ "Win32_System" ];
+          "Win32_System_SetupAndMigration" = [ "Win32_System" ];
+          "Win32_System_Shutdown" = [ "Win32_System" ];
+          "Win32_System_SideShow" = [ "Win32_System" ];
+          "Win32_System_StationsAndDesktops" = [ "Win32_System" ];
+          "Win32_System_SubsystemForLinux" = [ "Win32_System" ];
+          "Win32_System_SystemInformation" = [ "Win32_System" ];
+          "Win32_System_SystemServices" = [ "Win32_System" ];
+          "Win32_System_TaskScheduler" = [ "Win32_System" ];
+          "Win32_System_Threading" = [ "Win32_System" ];
+          "Win32_System_Time" = [ "Win32_System" ];
+          "Win32_System_TpmBaseServices" = [ "Win32_System" ];
+          "Win32_System_TransactionServer" = [ "Win32_System" ];
+          "Win32_System_UpdateAgent" = [ "Win32_System" ];
+          "Win32_System_UpdateAssessment" = [ "Win32_System" ];
+          "Win32_System_UserAccessLogging" = [ "Win32_System" ];
+          "Win32_System_Variant" = [ "Win32_System" ];
+          "Win32_System_VirtualDosMachines" = [ "Win32_System" ];
+          "Win32_System_WinRT" = [ "Win32_System" ];
+          "Win32_System_WinRT_AllJoyn" = [ "Win32_System_WinRT" ];
+          "Win32_System_WinRT_Composition" = [ "Win32_System_WinRT" ];
+          "Win32_System_WinRT_CoreInputView" = [ "Win32_System_WinRT" ];
+          "Win32_System_WinRT_Direct3D11" = [ "Win32_System_WinRT" ];
+          "Win32_System_WinRT_Display" = [ "Win32_System_WinRT" ];
+          "Win32_System_WinRT_Graphics" = [ "Win32_System_WinRT" ];
+          "Win32_System_WinRT_Graphics_Capture" = [ "Win32_System_WinRT_Graphics" ];
+          "Win32_System_WinRT_Graphics_Direct2D" = [ "Win32_System_WinRT_Graphics" ];
+          "Win32_System_WinRT_Graphics_Imaging" = [ "Win32_System_WinRT_Graphics" ];
+          "Win32_System_WinRT_Holographic" = [ "Win32_System_WinRT" ];
+          "Win32_System_WinRT_Isolation" = [ "Win32_System_WinRT" ];
+          "Win32_System_WinRT_ML" = [ "Win32_System_WinRT" ];
+          "Win32_System_WinRT_Media" = [ "Win32_System_WinRT" ];
+          "Win32_System_WinRT_Metadata" = [ "Win32_System_WinRT" ];
+          "Win32_System_WinRT_Pdf" = [ "Win32_System_WinRT" ];
+          "Win32_System_WinRT_Printing" = [ "Win32_System_WinRT" ];
+          "Win32_System_WinRT_Shell" = [ "Win32_System_WinRT" ];
+          "Win32_System_WinRT_Storage" = [ "Win32_System_WinRT" ];
+          "Win32_System_WindowsProgramming" = [ "Win32_System" ];
+          "Win32_System_WindowsSync" = [ "Win32_System" ];
+          "Win32_System_Wmi" = [ "Win32_System" ];
+          "Win32_UI" = [ "Win32" ];
+          "Win32_UI_Accessibility" = [ "Win32_UI" ];
+          "Win32_UI_Animation" = [ "Win32_UI" ];
+          "Win32_UI_ColorSystem" = [ "Win32_UI" ];
+          "Win32_UI_Controls" = [ "Win32_UI" ];
+          "Win32_UI_Controls_Dialogs" = [ "Win32_UI_Controls" ];
+          "Win32_UI_Controls_RichEdit" = [ "Win32_UI_Controls" ];
+          "Win32_UI_HiDpi" = [ "Win32_UI" ];
+          "Win32_UI_Input" = [ "Win32_UI" ];
+          "Win32_UI_Input_Ime" = [ "Win32_UI_Input" ];
+          "Win32_UI_Input_Ink" = [ "Win32_UI_Input" ];
+          "Win32_UI_Input_KeyboardAndMouse" = [ "Win32_UI_Input" ];
+          "Win32_UI_Input_Pointer" = [ "Win32_UI_Input" ];
+          "Win32_UI_Input_Radial" = [ "Win32_UI_Input" ];
+          "Win32_UI_Input_Touch" = [ "Win32_UI_Input" ];
+          "Win32_UI_Input_XboxController" = [ "Win32_UI_Input" ];
+          "Win32_UI_InteractionContext" = [ "Win32_UI" ];
+          "Win32_UI_LegacyWindowsEnvironmentFeatures" = [ "Win32_UI" ];
+          "Win32_UI_Magnification" = [ "Win32_UI" ];
+          "Win32_UI_Notifications" = [ "Win32_UI" ];
+          "Win32_UI_Ribbon" = [ "Win32_UI" ];
+          "Win32_UI_Shell" = [ "Win32_UI" ];
+          "Win32_UI_Shell_Common" = [ "Win32_UI_Shell" ];
+          "Win32_UI_Shell_PropertiesSystem" = [ "Win32_UI_Shell" ];
+          "Win32_UI_TabletPC" = [ "Win32_UI" ];
+          "Win32_UI_TextServices" = [ "Win32_UI" ];
+          "Win32_UI_WindowsAndMessaging" = [ "Win32_UI" ];
+          "Win32_UI_Wpf" = [ "Win32_UI" ];
+          "Win32_Web" = [ "Win32" ];
+          "Win32_Web_InternetExplorer" = [ "Win32_Web" ];
+          "implement" = [ "windows-implement" "windows-interface" "windows-core/implement" ];
+          "windows-implement" = [ "dep:windows-implement" ];
+          "windows-interface" = [ "dep:windows-interface" ];
+        };
+        resolvedDefaultFeatures = [ "Wdk" "Wdk_System" "Wdk_System_SystemInformation" "Wdk_System_SystemServices" "Wdk_System_Threading" "Win32" "Win32_Foundation" "Win32_NetworkManagement" "Win32_NetworkManagement_IpHelper" "Win32_NetworkManagement_Ndis" "Win32_NetworkManagement_NetManagement" "Win32_Networking" "Win32_Networking_WinSock" "Win32_Security" "Win32_Security_Authentication" "Win32_Security_Authentication_Identity" "Win32_Security_Authorization" "Win32_Storage" "Win32_Storage_FileSystem" "Win32_System" "Win32_System_Com" "Win32_System_Diagnostics" "Win32_System_Diagnostics_Debug" "Win32_System_IO" "Win32_System_Ioctl" "Win32_System_Kernel" "Win32_System_LibraryLoader" "Win32_System_Memory" "Win32_System_Ole" "Win32_System_Performance" "Win32_System_Power" "Win32_System_ProcessStatus" "Win32_System_Registry" "Win32_System_RemoteDesktop" "Win32_System_Rpc" "Win32_System_SystemInformation" "Win32_System_SystemServices" "Win32_System_Threading" "Win32_System_Variant" "Win32_System_WindowsProgramming" "Win32_System_Wmi" "Win32_UI" "Win32_UI_Shell" "default" ];
+      };
+      "windows-core" = rec {
+        crateName = "windows-core";
+        version = "0.52.0";
+        edition = "2021";
+        sha256 = "1nc3qv7sy24x0nlnb32f7alzpd6f72l4p24vl65vydbyil669ark";
+        authors = [
+          "Microsoft"
+        ];
+        dependencies = [
+          {
+            name = "windows-targets";
+            packageId = "windows-targets 0.52.0";
+          }
+        ];
+        features = { };
+        resolvedDefaultFeatures = [ "default" ];
+      };
+      "windows-sys 0.48.0" = rec {
+        crateName = "windows-sys";
+        version = "0.48.0";
+        edition = "2018";
+        sha256 = "1aan23v5gs7gya1lc46hqn9mdh8yph3fhxmhxlw36pn6pqc28zb7";
+        authors = [
+          "Microsoft"
+        ];
+        dependencies = [
+          {
+            name = "windows-targets";
+            packageId = "windows-targets 0.48.5";
+          }
+        ];
+        features = {
+          "Wdk_System" = [ "Wdk" ];
+          "Wdk_System_OfflineRegistry" = [ "Wdk_System" ];
+          "Win32_Data" = [ "Win32" ];
+          "Win32_Data_HtmlHelp" = [ "Win32_Data" ];
+          "Win32_Data_RightsManagement" = [ "Win32_Data" ];
+          "Win32_Data_Xml" = [ "Win32_Data" ];
+          "Win32_Data_Xml_MsXml" = [ "Win32_Data_Xml" ];
+          "Win32_Data_Xml_XmlLite" = [ "Win32_Data_Xml" ];
+          "Win32_Devices" = [ "Win32" ];
+          "Win32_Devices_AllJoyn" = [ "Win32_Devices" ];
+          "Win32_Devices_BiometricFramework" = [ "Win32_Devices" ];
+          "Win32_Devices_Bluetooth" = [ "Win32_Devices" ];
+          "Win32_Devices_Communication" = [ "Win32_Devices" ];
+          "Win32_Devices_DeviceAccess" = [ "Win32_Devices" ];
+          "Win32_Devices_DeviceAndDriverInstallation" = [ "Win32_Devices" ];
+          "Win32_Devices_DeviceQuery" = [ "Win32_Devices" ];
+          "Win32_Devices_Display" = [ "Win32_Devices" ];
+          "Win32_Devices_Enumeration" = [ "Win32_Devices" ];
+          "Win32_Devices_Enumeration_Pnp" = [ "Win32_Devices_Enumeration" ];
+          "Win32_Devices_Fax" = [ "Win32_Devices" ];
+          "Win32_Devices_FunctionDiscovery" = [ "Win32_Devices" ];
+          "Win32_Devices_Geolocation" = [ "Win32_Devices" ];
+          "Win32_Devices_HumanInterfaceDevice" = [ "Win32_Devices" ];
+          "Win32_Devices_ImageAcquisition" = [ "Win32_Devices" ];
+          "Win32_Devices_PortableDevices" = [ "Win32_Devices" ];
+          "Win32_Devices_Properties" = [ "Win32_Devices" ];
+          "Win32_Devices_Pwm" = [ "Win32_Devices" ];
+          "Win32_Devices_Sensors" = [ "Win32_Devices" ];
+          "Win32_Devices_SerialCommunication" = [ "Win32_Devices" ];
+          "Win32_Devices_Tapi" = [ "Win32_Devices" ];
+          "Win32_Devices_Usb" = [ "Win32_Devices" ];
+          "Win32_Devices_WebServicesOnDevices" = [ "Win32_Devices" ];
+          "Win32_Foundation" = [ "Win32" ];
+          "Win32_Gaming" = [ "Win32" ];
+          "Win32_Globalization" = [ "Win32" ];
+          "Win32_Graphics" = [ "Win32" ];
+          "Win32_Graphics_Dwm" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Gdi" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Hlsl" = [ "Win32_Graphics" ];
+          "Win32_Graphics_OpenGL" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Printing" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Printing_PrintTicket" = [ "Win32_Graphics_Printing" ];
+          "Win32_Management" = [ "Win32" ];
+          "Win32_Management_MobileDeviceManagementRegistration" = [ "Win32_Management" ];
+          "Win32_Media" = [ "Win32" ];
+          "Win32_Media_Audio" = [ "Win32_Media" ];
+          "Win32_Media_Audio_Apo" = [ "Win32_Media_Audio" ];
+          "Win32_Media_Audio_DirectMusic" = [ "Win32_Media_Audio" ];
+          "Win32_Media_Audio_Endpoints" = [ "Win32_Media_Audio" ];
+          "Win32_Media_Audio_XAudio2" = [ "Win32_Media_Audio" ];
+          "Win32_Media_DeviceManager" = [ "Win32_Media" ];
+          "Win32_Media_DxMediaObjects" = [ "Win32_Media" ];
+          "Win32_Media_KernelStreaming" = [ "Win32_Media" ];
+          "Win32_Media_LibrarySharingServices" = [ "Win32_Media" ];
+          "Win32_Media_MediaPlayer" = [ "Win32_Media" ];
+          "Win32_Media_Multimedia" = [ "Win32_Media" ];
+          "Win32_Media_Speech" = [ "Win32_Media" ];
+          "Win32_Media_Streaming" = [ "Win32_Media" ];
+          "Win32_Media_WindowsMediaFormat" = [ "Win32_Media" ];
+          "Win32_NetworkManagement" = [ "Win32" ];
+          "Win32_NetworkManagement_Dhcp" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_Dns" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_InternetConnectionWizard" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_IpHelper" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_MobileBroadband" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_Multicast" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_Ndis" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_NetBios" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_NetManagement" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_NetShell" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_NetworkDiagnosticsFramework" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_NetworkPolicyServer" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_P2P" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_QoS" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_Rras" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_Snmp" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WNet" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WebDav" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WiFi" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WindowsConnectNow" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WindowsConnectionManager" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WindowsFilteringPlatform" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WindowsFirewall" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WindowsNetworkVirtualization" = [ "Win32_NetworkManagement" ];
+          "Win32_Networking" = [ "Win32" ];
+          "Win32_Networking_ActiveDirectory" = [ "Win32_Networking" ];
+          "Win32_Networking_BackgroundIntelligentTransferService" = [ "Win32_Networking" ];
+          "Win32_Networking_Clustering" = [ "Win32_Networking" ];
+          "Win32_Networking_HttpServer" = [ "Win32_Networking" ];
+          "Win32_Networking_Ldap" = [ "Win32_Networking" ];
+          "Win32_Networking_NetworkListManager" = [ "Win32_Networking" ];
+          "Win32_Networking_RemoteDifferentialCompression" = [ "Win32_Networking" ];
+          "Win32_Networking_WebSocket" = [ "Win32_Networking" ];
+          "Win32_Networking_WinHttp" = [ "Win32_Networking" ];
+          "Win32_Networking_WinInet" = [ "Win32_Networking" ];
+          "Win32_Networking_WinSock" = [ "Win32_Networking" ];
+          "Win32_Networking_WindowsWebServices" = [ "Win32_Networking" ];
+          "Win32_Security" = [ "Win32" ];
+          "Win32_Security_AppLocker" = [ "Win32_Security" ];
+          "Win32_Security_Authentication" = [ "Win32_Security" ];
+          "Win32_Security_Authentication_Identity" = [ "Win32_Security_Authentication" ];
+          "Win32_Security_Authentication_Identity_Provider" = [ "Win32_Security_Authentication_Identity" ];
+          "Win32_Security_Authorization" = [ "Win32_Security" ];
+          "Win32_Security_Authorization_UI" = [ "Win32_Security_Authorization" ];
+          "Win32_Security_ConfigurationSnapin" = [ "Win32_Security" ];
+          "Win32_Security_Credentials" = [ "Win32_Security" ];
+          "Win32_Security_Cryptography" = [ "Win32_Security" ];
+          "Win32_Security_Cryptography_Catalog" = [ "Win32_Security_Cryptography" ];
+          "Win32_Security_Cryptography_Certificates" = [ "Win32_Security_Cryptography" ];
+          "Win32_Security_Cryptography_Sip" = [ "Win32_Security_Cryptography" ];
+          "Win32_Security_Cryptography_UI" = [ "Win32_Security_Cryptography" ];
+          "Win32_Security_DiagnosticDataQuery" = [ "Win32_Security" ];
+          "Win32_Security_DirectoryServices" = [ "Win32_Security" ];
+          "Win32_Security_EnterpriseData" = [ "Win32_Security" ];
+          "Win32_Security_ExtensibleAuthenticationProtocol" = [ "Win32_Security" ];
+          "Win32_Security_Isolation" = [ "Win32_Security" ];
+          "Win32_Security_LicenseProtection" = [ "Win32_Security" ];
+          "Win32_Security_NetworkAccessProtection" = [ "Win32_Security" ];
+          "Win32_Security_Tpm" = [ "Win32_Security" ];
+          "Win32_Security_WinTrust" = [ "Win32_Security" ];
+          "Win32_Security_WinWlx" = [ "Win32_Security" ];
+          "Win32_Storage" = [ "Win32" ];
+          "Win32_Storage_Cabinets" = [ "Win32_Storage" ];
+          "Win32_Storage_CloudFilters" = [ "Win32_Storage" ];
+          "Win32_Storage_Compression" = [ "Win32_Storage" ];
+          "Win32_Storage_DataDeduplication" = [ "Win32_Storage" ];
+          "Win32_Storage_DistributedFileSystem" = [ "Win32_Storage" ];
+          "Win32_Storage_EnhancedStorage" = [ "Win32_Storage" ];
+          "Win32_Storage_FileHistory" = [ "Win32_Storage" ];
+          "Win32_Storage_FileServerResourceManager" = [ "Win32_Storage" ];
+          "Win32_Storage_FileSystem" = [ "Win32_Storage" ];
+          "Win32_Storage_Imapi" = [ "Win32_Storage" ];
+          "Win32_Storage_IndexServer" = [ "Win32_Storage" ];
+          "Win32_Storage_InstallableFileSystems" = [ "Win32_Storage" ];
+          "Win32_Storage_IscsiDisc" = [ "Win32_Storage" ];
+          "Win32_Storage_Jet" = [ "Win32_Storage" ];
+          "Win32_Storage_OfflineFiles" = [ "Win32_Storage" ];
+          "Win32_Storage_OperationRecorder" = [ "Win32_Storage" ];
+          "Win32_Storage_Packaging" = [ "Win32_Storage" ];
+          "Win32_Storage_Packaging_Appx" = [ "Win32_Storage_Packaging" ];
+          "Win32_Storage_Packaging_Opc" = [ "Win32_Storage_Packaging" ];
+          "Win32_Storage_ProjectedFileSystem" = [ "Win32_Storage" ];
+          "Win32_Storage_StructuredStorage" = [ "Win32_Storage" ];
+          "Win32_Storage_Vhd" = [ "Win32_Storage" ];
+          "Win32_Storage_VirtualDiskService" = [ "Win32_Storage" ];
+          "Win32_Storage_Vss" = [ "Win32_Storage" ];
+          "Win32_Storage_Xps" = [ "Win32_Storage" ];
+          "Win32_Storage_Xps_Printing" = [ "Win32_Storage_Xps" ];
+          "Win32_System" = [ "Win32" ];
+          "Win32_System_AddressBook" = [ "Win32_System" ];
+          "Win32_System_Antimalware" = [ "Win32_System" ];
+          "Win32_System_ApplicationInstallationAndServicing" = [ "Win32_System" ];
+          "Win32_System_ApplicationVerifier" = [ "Win32_System" ];
+          "Win32_System_AssessmentTool" = [ "Win32_System" ];
+          "Win32_System_ClrHosting" = [ "Win32_System" ];
+          "Win32_System_Com" = [ "Win32_System" ];
+          "Win32_System_Com_CallObj" = [ "Win32_System_Com" ];
+          "Win32_System_Com_ChannelCredentials" = [ "Win32_System_Com" ];
+          "Win32_System_Com_Events" = [ "Win32_System_Com" ];
+          "Win32_System_Com_Marshal" = [ "Win32_System_Com" ];
+          "Win32_System_Com_StructuredStorage" = [ "Win32_System_Com" ];
+          "Win32_System_Com_UI" = [ "Win32_System_Com" ];
+          "Win32_System_Com_Urlmon" = [ "Win32_System_Com" ];
+          "Win32_System_ComponentServices" = [ "Win32_System" ];
+          "Win32_System_Console" = [ "Win32_System" ];
+          "Win32_System_Contacts" = [ "Win32_System" ];
+          "Win32_System_CorrelationVector" = [ "Win32_System" ];
+          "Win32_System_DataExchange" = [ "Win32_System" ];
+          "Win32_System_DeploymentServices" = [ "Win32_System" ];
+          "Win32_System_DesktopSharing" = [ "Win32_System" ];
+          "Win32_System_DeveloperLicensing" = [ "Win32_System" ];
+          "Win32_System_Diagnostics" = [ "Win32_System" ];
+          "Win32_System_Diagnostics_Ceip" = [ "Win32_System_Diagnostics" ];
+          "Win32_System_Diagnostics_ClrProfiling" = [ "Win32_System_Diagnostics" ];
+          "Win32_System_Diagnostics_Debug" = [ "Win32_System_Diagnostics" ];
+          "Win32_System_Diagnostics_Debug_ActiveScript" = [ "Win32_System_Diagnostics_Debug" ];
+          "Win32_System_Diagnostics_Debug_Extensions" = [ "Win32_System_Diagnostics_Debug" ];
+          "Win32_System_Diagnostics_Etw" = [ "Win32_System_Diagnostics" ];
+          "Win32_System_Diagnostics_ProcessSnapshotting" = [ "Win32_System_Diagnostics" ];
+          "Win32_System_Diagnostics_ToolHelp" = [ "Win32_System_Diagnostics" ];
+          "Win32_System_DistributedTransactionCoordinator" = [ "Win32_System" ];
+          "Win32_System_Environment" = [ "Win32_System" ];
+          "Win32_System_ErrorReporting" = [ "Win32_System" ];
+          "Win32_System_EventCollector" = [ "Win32_System" ];
+          "Win32_System_EventLog" = [ "Win32_System" ];
+          "Win32_System_EventNotificationService" = [ "Win32_System" ];
+          "Win32_System_GroupPolicy" = [ "Win32_System" ];
+          "Win32_System_HostCompute" = [ "Win32_System" ];
+          "Win32_System_HostComputeNetwork" = [ "Win32_System" ];
+          "Win32_System_HostComputeSystem" = [ "Win32_System" ];
+          "Win32_System_Hypervisor" = [ "Win32_System" ];
+          "Win32_System_IO" = [ "Win32_System" ];
+          "Win32_System_Iis" = [ "Win32_System" ];
+          "Win32_System_Ioctl" = [ "Win32_System" ];
+          "Win32_System_JobObjects" = [ "Win32_System" ];
+          "Win32_System_Js" = [ "Win32_System" ];
+          "Win32_System_Kernel" = [ "Win32_System" ];
+          "Win32_System_LibraryLoader" = [ "Win32_System" ];
+          "Win32_System_Mailslots" = [ "Win32_System" ];
+          "Win32_System_Mapi" = [ "Win32_System" ];
+          "Win32_System_Memory" = [ "Win32_System" ];
+          "Win32_System_Memory_NonVolatile" = [ "Win32_System_Memory" ];
+          "Win32_System_MessageQueuing" = [ "Win32_System" ];
+          "Win32_System_MixedReality" = [ "Win32_System" ];
+          "Win32_System_Mmc" = [ "Win32_System" ];
+          "Win32_System_Ole" = [ "Win32_System" ];
+          "Win32_System_ParentalControls" = [ "Win32_System" ];
+          "Win32_System_PasswordManagement" = [ "Win32_System" ];
+          "Win32_System_Performance" = [ "Win32_System" ];
+          "Win32_System_Performance_HardwareCounterProfiling" = [ "Win32_System_Performance" ];
+          "Win32_System_Pipes" = [ "Win32_System" ];
+          "Win32_System_Power" = [ "Win32_System" ];
+          "Win32_System_ProcessStatus" = [ "Win32_System" ];
+          "Win32_System_RealTimeCommunications" = [ "Win32_System" ];
+          "Win32_System_Recovery" = [ "Win32_System" ];
+          "Win32_System_Registry" = [ "Win32_System" ];
+          "Win32_System_RemoteAssistance" = [ "Win32_System" ];
+          "Win32_System_RemoteDesktop" = [ "Win32_System" ];
+          "Win32_System_RemoteManagement" = [ "Win32_System" ];
+          "Win32_System_RestartManager" = [ "Win32_System" ];
+          "Win32_System_Restore" = [ "Win32_System" ];
+          "Win32_System_Rpc" = [ "Win32_System" ];
+          "Win32_System_Search" = [ "Win32_System" ];
+          "Win32_System_Search_Common" = [ "Win32_System_Search" ];
+          "Win32_System_SecurityCenter" = [ "Win32_System" ];
+          "Win32_System_ServerBackup" = [ "Win32_System" ];
+          "Win32_System_Services" = [ "Win32_System" ];
+          "Win32_System_SettingsManagementInfrastructure" = [ "Win32_System" ];
+          "Win32_System_SetupAndMigration" = [ "Win32_System" ];
+          "Win32_System_Shutdown" = [ "Win32_System" ];
+          "Win32_System_StationsAndDesktops" = [ "Win32_System" ];
+          "Win32_System_SubsystemForLinux" = [ "Win32_System" ];
+          "Win32_System_SystemInformation" = [ "Win32_System" ];
+          "Win32_System_SystemServices" = [ "Win32_System" ];
+          "Win32_System_TaskScheduler" = [ "Win32_System" ];
+          "Win32_System_Threading" = [ "Win32_System" ];
+          "Win32_System_Time" = [ "Win32_System" ];
+          "Win32_System_TpmBaseServices" = [ "Win32_System" ];
+          "Win32_System_UpdateAgent" = [ "Win32_System" ];
+          "Win32_System_UpdateAssessment" = [ "Win32_System" ];
+          "Win32_System_UserAccessLogging" = [ "Win32_System" ];
+          "Win32_System_VirtualDosMachines" = [ "Win32_System" ];
+          "Win32_System_WindowsProgramming" = [ "Win32_System" ];
+          "Win32_System_WindowsSync" = [ "Win32_System" ];
+          "Win32_System_Wmi" = [ "Win32_System" ];
+          "Win32_UI" = [ "Win32" ];
+          "Win32_UI_Accessibility" = [ "Win32_UI" ];
+          "Win32_UI_Animation" = [ "Win32_UI" ];
+          "Win32_UI_ColorSystem" = [ "Win32_UI" ];
+          "Win32_UI_Controls" = [ "Win32_UI" ];
+          "Win32_UI_Controls_Dialogs" = [ "Win32_UI_Controls" ];
+          "Win32_UI_Controls_RichEdit" = [ "Win32_UI_Controls" ];
+          "Win32_UI_HiDpi" = [ "Win32_UI" ];
+          "Win32_UI_Input" = [ "Win32_UI" ];
+          "Win32_UI_Input_Ime" = [ "Win32_UI_Input" ];
+          "Win32_UI_Input_Ink" = [ "Win32_UI_Input" ];
+          "Win32_UI_Input_KeyboardAndMouse" = [ "Win32_UI_Input" ];
+          "Win32_UI_Input_Pointer" = [ "Win32_UI_Input" ];
+          "Win32_UI_Input_Radial" = [ "Win32_UI_Input" ];
+          "Win32_UI_Input_Touch" = [ "Win32_UI_Input" ];
+          "Win32_UI_Input_XboxController" = [ "Win32_UI_Input" ];
+          "Win32_UI_InteractionContext" = [ "Win32_UI" ];
+          "Win32_UI_LegacyWindowsEnvironmentFeatures" = [ "Win32_UI" ];
+          "Win32_UI_Magnification" = [ "Win32_UI" ];
+          "Win32_UI_Notifications" = [ "Win32_UI" ];
+          "Win32_UI_Ribbon" = [ "Win32_UI" ];
+          "Win32_UI_Shell" = [ "Win32_UI" ];
+          "Win32_UI_Shell_Common" = [ "Win32_UI_Shell" ];
+          "Win32_UI_Shell_PropertiesSystem" = [ "Win32_UI_Shell" ];
+          "Win32_UI_TabletPC" = [ "Win32_UI" ];
+          "Win32_UI_TextServices" = [ "Win32_UI" ];
+          "Win32_UI_WindowsAndMessaging" = [ "Win32_UI" ];
+          "Win32_UI_Wpf" = [ "Win32_UI" ];
+          "Win32_Web" = [ "Win32" ];
+          "Win32_Web_InternetExplorer" = [ "Win32_Web" ];
+        };
+        resolvedDefaultFeatures = [ "Win32" "Win32_Foundation" "Win32_Networking" "Win32_Networking_WinSock" "Win32_Security" "Win32_Storage" "Win32_Storage_FileSystem" "Win32_System" "Win32_System_Console" "Win32_System_IO" "Win32_System_Pipes" "Win32_System_SystemServices" "Win32_System_Threading" "Win32_System_WindowsProgramming" "default" ];
+      };
+      "windows-sys 0.52.0" = rec {
+        crateName = "windows-sys";
+        version = "0.52.0";
+        edition = "2021";
+        sha256 = "0gd3v4ji88490zgb6b5mq5zgbvwv7zx1ibn8v3x83rwcdbryaar8";
+        authors = [
+          "Microsoft"
+        ];
+        dependencies = [
+          {
+            name = "windows-targets";
+            packageId = "windows-targets 0.52.0";
+          }
+        ];
+        features = {
+          "Wdk_Foundation" = [ "Wdk" ];
+          "Wdk_Graphics" = [ "Wdk" ];
+          "Wdk_Graphics_Direct3D" = [ "Wdk_Graphics" ];
+          "Wdk_Storage" = [ "Wdk" ];
+          "Wdk_Storage_FileSystem" = [ "Wdk_Storage" ];
+          "Wdk_Storage_FileSystem_Minifilters" = [ "Wdk_Storage_FileSystem" ];
+          "Wdk_System" = [ "Wdk" ];
+          "Wdk_System_IO" = [ "Wdk_System" ];
+          "Wdk_System_OfflineRegistry" = [ "Wdk_System" ];
+          "Wdk_System_Registry" = [ "Wdk_System" ];
+          "Wdk_System_SystemInformation" = [ "Wdk_System" ];
+          "Wdk_System_SystemServices" = [ "Wdk_System" ];
+          "Wdk_System_Threading" = [ "Wdk_System" ];
+          "Win32_Data" = [ "Win32" ];
+          "Win32_Data_HtmlHelp" = [ "Win32_Data" ];
+          "Win32_Data_RightsManagement" = [ "Win32_Data" ];
+          "Win32_Devices" = [ "Win32" ];
+          "Win32_Devices_AllJoyn" = [ "Win32_Devices" ];
+          "Win32_Devices_BiometricFramework" = [ "Win32_Devices" ];
+          "Win32_Devices_Bluetooth" = [ "Win32_Devices" ];
+          "Win32_Devices_Communication" = [ "Win32_Devices" ];
+          "Win32_Devices_DeviceAndDriverInstallation" = [ "Win32_Devices" ];
+          "Win32_Devices_DeviceQuery" = [ "Win32_Devices" ];
+          "Win32_Devices_Display" = [ "Win32_Devices" ];
+          "Win32_Devices_Enumeration" = [ "Win32_Devices" ];
+          "Win32_Devices_Enumeration_Pnp" = [ "Win32_Devices_Enumeration" ];
+          "Win32_Devices_Fax" = [ "Win32_Devices" ];
+          "Win32_Devices_HumanInterfaceDevice" = [ "Win32_Devices" ];
+          "Win32_Devices_PortableDevices" = [ "Win32_Devices" ];
+          "Win32_Devices_Properties" = [ "Win32_Devices" ];
+          "Win32_Devices_Pwm" = [ "Win32_Devices" ];
+          "Win32_Devices_Sensors" = [ "Win32_Devices" ];
+          "Win32_Devices_SerialCommunication" = [ "Win32_Devices" ];
+          "Win32_Devices_Tapi" = [ "Win32_Devices" ];
+          "Win32_Devices_Usb" = [ "Win32_Devices" ];
+          "Win32_Devices_WebServicesOnDevices" = [ "Win32_Devices" ];
+          "Win32_Foundation" = [ "Win32" ];
+          "Win32_Gaming" = [ "Win32" ];
+          "Win32_Globalization" = [ "Win32" ];
+          "Win32_Graphics" = [ "Win32" ];
+          "Win32_Graphics_Dwm" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Gdi" = [ "Win32_Graphics" ];
+          "Win32_Graphics_GdiPlus" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Hlsl" = [ "Win32_Graphics" ];
+          "Win32_Graphics_OpenGL" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Printing" = [ "Win32_Graphics" ];
+          "Win32_Graphics_Printing_PrintTicket" = [ "Win32_Graphics_Printing" ];
+          "Win32_Management" = [ "Win32" ];
+          "Win32_Management_MobileDeviceManagementRegistration" = [ "Win32_Management" ];
+          "Win32_Media" = [ "Win32" ];
+          "Win32_Media_Audio" = [ "Win32_Media" ];
+          "Win32_Media_DxMediaObjects" = [ "Win32_Media" ];
+          "Win32_Media_KernelStreaming" = [ "Win32_Media" ];
+          "Win32_Media_Multimedia" = [ "Win32_Media" ];
+          "Win32_Media_Streaming" = [ "Win32_Media" ];
+          "Win32_Media_WindowsMediaFormat" = [ "Win32_Media" ];
+          "Win32_NetworkManagement" = [ "Win32" ];
+          "Win32_NetworkManagement_Dhcp" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_Dns" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_InternetConnectionWizard" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_IpHelper" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_Multicast" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_Ndis" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_NetBios" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_NetManagement" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_NetShell" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_NetworkDiagnosticsFramework" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_P2P" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_QoS" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_Rras" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_Snmp" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WNet" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WebDav" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WiFi" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WindowsConnectionManager" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WindowsFilteringPlatform" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WindowsFirewall" = [ "Win32_NetworkManagement" ];
+          "Win32_NetworkManagement_WindowsNetworkVirtualization" = [ "Win32_NetworkManagement" ];
+          "Win32_Networking" = [ "Win32" ];
+          "Win32_Networking_ActiveDirectory" = [ "Win32_Networking" ];
+          "Win32_Networking_Clustering" = [ "Win32_Networking" ];
+          "Win32_Networking_HttpServer" = [ "Win32_Networking" ];
+          "Win32_Networking_Ldap" = [ "Win32_Networking" ];
+          "Win32_Networking_WebSocket" = [ "Win32_Networking" ];
+          "Win32_Networking_WinHttp" = [ "Win32_Networking" ];
+          "Win32_Networking_WinInet" = [ "Win32_Networking" ];
+          "Win32_Networking_WinSock" = [ "Win32_Networking" ];
+          "Win32_Networking_WindowsWebServices" = [ "Win32_Networking" ];
+          "Win32_Security" = [ "Win32" ];
+          "Win32_Security_AppLocker" = [ "Win32_Security" ];
+          "Win32_Security_Authentication" = [ "Win32_Security" ];
+          "Win32_Security_Authentication_Identity" = [ "Win32_Security_Authentication" ];
+          "Win32_Security_Authorization" = [ "Win32_Security" ];
+          "Win32_Security_Credentials" = [ "Win32_Security" ];
+          "Win32_Security_Cryptography" = [ "Win32_Security" ];
+          "Win32_Security_Cryptography_Catalog" = [ "Win32_Security_Cryptography" ];
+          "Win32_Security_Cryptography_Certificates" = [ "Win32_Security_Cryptography" ];
+          "Win32_Security_Cryptography_Sip" = [ "Win32_Security_Cryptography" ];
+          "Win32_Security_Cryptography_UI" = [ "Win32_Security_Cryptography" ];
+          "Win32_Security_DiagnosticDataQuery" = [ "Win32_Security" ];
+          "Win32_Security_DirectoryServices" = [ "Win32_Security" ];
+          "Win32_Security_EnterpriseData" = [ "Win32_Security" ];
+          "Win32_Security_ExtensibleAuthenticationProtocol" = [ "Win32_Security" ];
+          "Win32_Security_Isolation" = [ "Win32_Security" ];
+          "Win32_Security_LicenseProtection" = [ "Win32_Security" ];
+          "Win32_Security_NetworkAccessProtection" = [ "Win32_Security" ];
+          "Win32_Security_WinTrust" = [ "Win32_Security" ];
+          "Win32_Security_WinWlx" = [ "Win32_Security" ];
+          "Win32_Storage" = [ "Win32" ];
+          "Win32_Storage_Cabinets" = [ "Win32_Storage" ];
+          "Win32_Storage_CloudFilters" = [ "Win32_Storage" ];
+          "Win32_Storage_Compression" = [ "Win32_Storage" ];
+          "Win32_Storage_DistributedFileSystem" = [ "Win32_Storage" ];
+          "Win32_Storage_FileHistory" = [ "Win32_Storage" ];
+          "Win32_Storage_FileSystem" = [ "Win32_Storage" ];
+          "Win32_Storage_Imapi" = [ "Win32_Storage" ];
+          "Win32_Storage_IndexServer" = [ "Win32_Storage" ];
+          "Win32_Storage_InstallableFileSystems" = [ "Win32_Storage" ];
+          "Win32_Storage_IscsiDisc" = [ "Win32_Storage" ];
+          "Win32_Storage_Jet" = [ "Win32_Storage" ];
+          "Win32_Storage_Nvme" = [ "Win32_Storage" ];
+          "Win32_Storage_OfflineFiles" = [ "Win32_Storage" ];
+          "Win32_Storage_OperationRecorder" = [ "Win32_Storage" ];
+          "Win32_Storage_Packaging" = [ "Win32_Storage" ];
+          "Win32_Storage_Packaging_Appx" = [ "Win32_Storage_Packaging" ];
+          "Win32_Storage_ProjectedFileSystem" = [ "Win32_Storage" ];
+          "Win32_Storage_StructuredStorage" = [ "Win32_Storage" ];
+          "Win32_Storage_Vhd" = [ "Win32_Storage" ];
+          "Win32_Storage_Xps" = [ "Win32_Storage" ];
+          "Win32_System" = [ "Win32" ];
+          "Win32_System_AddressBook" = [ "Win32_System" ];
+          "Win32_System_Antimalware" = [ "Win32_System" ];
+          "Win32_System_ApplicationInstallationAndServicing" = [ "Win32_System" ];
+          "Win32_System_ApplicationVerifier" = [ "Win32_System" ];
+          "Win32_System_ClrHosting" = [ "Win32_System" ];
+          "Win32_System_Com" = [ "Win32_System" ];
+          "Win32_System_Com_Marshal" = [ "Win32_System_Com" ];
+          "Win32_System_Com_StructuredStorage" = [ "Win32_System_Com" ];
+          "Win32_System_Com_Urlmon" = [ "Win32_System_Com" ];
+          "Win32_System_ComponentServices" = [ "Win32_System" ];
+          "Win32_System_Console" = [ "Win32_System" ];
+          "Win32_System_CorrelationVector" = [ "Win32_System" ];
+          "Win32_System_DataExchange" = [ "Win32_System" ];
+          "Win32_System_DeploymentServices" = [ "Win32_System" ];
+          "Win32_System_DeveloperLicensing" = [ "Win32_System" ];
+          "Win32_System_Diagnostics" = [ "Win32_System" ];
+          "Win32_System_Diagnostics_Ceip" = [ "Win32_System_Diagnostics" ];
+          "Win32_System_Diagnostics_Debug" = [ "Win32_System_Diagnostics" ];
+          "Win32_System_Diagnostics_Debug_Extensions" = [ "Win32_System_Diagnostics_Debug" ];
+          "Win32_System_Diagnostics_Etw" = [ "Win32_System_Diagnostics" ];
+          "Win32_System_Diagnostics_ProcessSnapshotting" = [ "Win32_System_Diagnostics" ];
+          "Win32_System_Diagnostics_ToolHelp" = [ "Win32_System_Diagnostics" ];
+          "Win32_System_DistributedTransactionCoordinator" = [ "Win32_System" ];
+          "Win32_System_Environment" = [ "Win32_System" ];
+          "Win32_System_ErrorReporting" = [ "Win32_System" ];
+          "Win32_System_EventCollector" = [ "Win32_System" ];
+          "Win32_System_EventLog" = [ "Win32_System" ];
+          "Win32_System_EventNotificationService" = [ "Win32_System" ];
+          "Win32_System_GroupPolicy" = [ "Win32_System" ];
+          "Win32_System_HostCompute" = [ "Win32_System" ];
+          "Win32_System_HostComputeNetwork" = [ "Win32_System" ];
+          "Win32_System_HostComputeSystem" = [ "Win32_System" ];
+          "Win32_System_Hypervisor" = [ "Win32_System" ];
+          "Win32_System_IO" = [ "Win32_System" ];
+          "Win32_System_Iis" = [ "Win32_System" ];
+          "Win32_System_Ioctl" = [ "Win32_System" ];
+          "Win32_System_JobObjects" = [ "Win32_System" ];
+          "Win32_System_Js" = [ "Win32_System" ];
+          "Win32_System_Kernel" = [ "Win32_System" ];
+          "Win32_System_LibraryLoader" = [ "Win32_System" ];
+          "Win32_System_Mailslots" = [ "Win32_System" ];
+          "Win32_System_Mapi" = [ "Win32_System" ];
+          "Win32_System_Memory" = [ "Win32_System" ];
+          "Win32_System_Memory_NonVolatile" = [ "Win32_System_Memory" ];
+          "Win32_System_MessageQueuing" = [ "Win32_System" ];
+          "Win32_System_MixedReality" = [ "Win32_System" ];
+          "Win32_System_Ole" = [ "Win32_System" ];
+          "Win32_System_PasswordManagement" = [ "Win32_System" ];
+          "Win32_System_Performance" = [ "Win32_System" ];
+          "Win32_System_Performance_HardwareCounterProfiling" = [ "Win32_System_Performance" ];
+          "Win32_System_Pipes" = [ "Win32_System" ];
+          "Win32_System_Power" = [ "Win32_System" ];
+          "Win32_System_ProcessStatus" = [ "Win32_System" ];
+          "Win32_System_Recovery" = [ "Win32_System" ];
+          "Win32_System_Registry" = [ "Win32_System" ];
+          "Win32_System_RemoteDesktop" = [ "Win32_System" ];
+          "Win32_System_RemoteManagement" = [ "Win32_System" ];
+          "Win32_System_RestartManager" = [ "Win32_System" ];
+          "Win32_System_Restore" = [ "Win32_System" ];
+          "Win32_System_Rpc" = [ "Win32_System" ];
+          "Win32_System_Search" = [ "Win32_System" ];
+          "Win32_System_Search_Common" = [ "Win32_System_Search" ];
+          "Win32_System_SecurityCenter" = [ "Win32_System" ];
+          "Win32_System_Services" = [ "Win32_System" ];
+          "Win32_System_SetupAndMigration" = [ "Win32_System" ];
+          "Win32_System_Shutdown" = [ "Win32_System" ];
+          "Win32_System_StationsAndDesktops" = [ "Win32_System" ];
+          "Win32_System_SubsystemForLinux" = [ "Win32_System" ];
+          "Win32_System_SystemInformation" = [ "Win32_System" ];
+          "Win32_System_SystemServices" = [ "Win32_System" ];
+          "Win32_System_Threading" = [ "Win32_System" ];
+          "Win32_System_Time" = [ "Win32_System" ];
+          "Win32_System_TpmBaseServices" = [ "Win32_System" ];
+          "Win32_System_UserAccessLogging" = [ "Win32_System" ];
+          "Win32_System_Variant" = [ "Win32_System" ];
+          "Win32_System_VirtualDosMachines" = [ "Win32_System" ];
+          "Win32_System_WindowsProgramming" = [ "Win32_System" ];
+          "Win32_System_Wmi" = [ "Win32_System" ];
+          "Win32_UI" = [ "Win32" ];
+          "Win32_UI_Accessibility" = [ "Win32_UI" ];
+          "Win32_UI_ColorSystem" = [ "Win32_UI" ];
+          "Win32_UI_Controls" = [ "Win32_UI" ];
+          "Win32_UI_Controls_Dialogs" = [ "Win32_UI_Controls" ];
+          "Win32_UI_HiDpi" = [ "Win32_UI" ];
+          "Win32_UI_Input" = [ "Win32_UI" ];
+          "Win32_UI_Input_Ime" = [ "Win32_UI_Input" ];
+          "Win32_UI_Input_KeyboardAndMouse" = [ "Win32_UI_Input" ];
+          "Win32_UI_Input_Pointer" = [ "Win32_UI_Input" ];
+          "Win32_UI_Input_Touch" = [ "Win32_UI_Input" ];
+          "Win32_UI_Input_XboxController" = [ "Win32_UI_Input" ];
+          "Win32_UI_InteractionContext" = [ "Win32_UI" ];
+          "Win32_UI_Magnification" = [ "Win32_UI" ];
+          "Win32_UI_Shell" = [ "Win32_UI" ];
+          "Win32_UI_Shell_PropertiesSystem" = [ "Win32_UI_Shell" ];
+          "Win32_UI_TabletPC" = [ "Win32_UI" ];
+          "Win32_UI_TextServices" = [ "Win32_UI" ];
+          "Win32_UI_WindowsAndMessaging" = [ "Win32_UI" ];
+          "Win32_Web" = [ "Win32" ];
+          "Win32_Web_InternetExplorer" = [ "Win32_Web" ];
+        };
+        resolvedDefaultFeatures = [ "Win32" "Win32_Foundation" "Win32_System" "Win32_System_Com" "Win32_UI" "Win32_UI_Shell" "default" ];
+      };
+      "windows-targets 0.48.5" = rec {
+        crateName = "windows-targets";
+        version = "0.48.5";
+        edition = "2018";
+        sha256 = "034ljxqshifs1lan89xwpcy1hp0lhdh4b5n0d2z4fwjx2piacbws";
+        authors = [
+          "Microsoft"
+        ];
+        dependencies = [
+          {
+            name = "windows_aarch64_gnullvm";
+            packageId = "windows_aarch64_gnullvm 0.48.5";
+            target = { target, features }: (pkgs.rust.lib.toRustTarget stdenv.hostPlatform == "aarch64-pc-windows-gnullvm");
+          }
+          {
+            name = "windows_aarch64_msvc";
+            packageId = "windows_aarch64_msvc 0.48.5";
+            target = { target, features }: (("aarch64" == target."arch" or null) && ("msvc" == target."env" or null) && (!(target."windows_raw_dylib" or false)));
+          }
+          {
+            name = "windows_i686_gnu";
+            packageId = "windows_i686_gnu 0.48.5";
+            target = { target, features }: (("x86" == target."arch" or null) && ("gnu" == target."env" or null) && (!(target."windows_raw_dylib" or false)));
+          }
+          {
+            name = "windows_i686_msvc";
+            packageId = "windows_i686_msvc 0.48.5";
+            target = { target, features }: (("x86" == target."arch" or null) && ("msvc" == target."env" or null) && (!(target."windows_raw_dylib" or false)));
+          }
+          {
+            name = "windows_x86_64_gnu";
+            packageId = "windows_x86_64_gnu 0.48.5";
+            target = { target, features }: (("x86_64" == target."arch" or null) && ("gnu" == target."env" or null) && (!("llvm" == target."abi" or null)) && (!(target."windows_raw_dylib" or false)));
+          }
+          {
+            name = "windows_x86_64_gnullvm";
+            packageId = "windows_x86_64_gnullvm 0.48.5";
+            target = { target, features }: (pkgs.rust.lib.toRustTarget stdenv.hostPlatform == "x86_64-pc-windows-gnullvm");
+          }
+          {
+            name = "windows_x86_64_msvc";
+            packageId = "windows_x86_64_msvc 0.48.5";
+            target = { target, features }: (("x86_64" == target."arch" or null) && ("msvc" == target."env" or null) && (!(target."windows_raw_dylib" or false)));
+          }
+        ];
+
+      };
+      "windows-targets 0.52.0" = rec {
+        crateName = "windows-targets";
+        version = "0.52.0";
+        edition = "2021";
+        sha256 = "1kg7a27ynzw8zz3krdgy6w5gbqcji27j1sz4p7xk2j5j8082064a";
+        authors = [
+          "Microsoft"
+        ];
+        dependencies = [
+          {
+            name = "windows_aarch64_gnullvm";
+            packageId = "windows_aarch64_gnullvm 0.52.0";
+            target = { target, features }: (pkgs.rust.lib.toRustTarget stdenv.hostPlatform == "aarch64-pc-windows-gnullvm");
+          }
+          {
+            name = "windows_aarch64_msvc";
+            packageId = "windows_aarch64_msvc 0.52.0";
+            target = { target, features }: (("aarch64" == target."arch" or null) && ("msvc" == target."env" or null) && (!(target."windows_raw_dylib" or false)));
+          }
+          {
+            name = "windows_i686_gnu";
+            packageId = "windows_i686_gnu 0.52.0";
+            target = { target, features }: (("x86" == target."arch" or null) && ("gnu" == target."env" or null) && (!(target."windows_raw_dylib" or false)));
+          }
+          {
+            name = "windows_i686_msvc";
+            packageId = "windows_i686_msvc 0.52.0";
+            target = { target, features }: (("x86" == target."arch" or null) && ("msvc" == target."env" or null) && (!(target."windows_raw_dylib" or false)));
+          }
+          {
+            name = "windows_x86_64_gnu";
+            packageId = "windows_x86_64_gnu 0.52.0";
+            target = { target, features }: (("x86_64" == target."arch" or null) && ("gnu" == target."env" or null) && (!("llvm" == target."abi" or null)) && (!(target."windows_raw_dylib" or false)));
+          }
+          {
+            name = "windows_x86_64_gnullvm";
+            packageId = "windows_x86_64_gnullvm 0.52.0";
+            target = { target, features }: (pkgs.rust.lib.toRustTarget stdenv.hostPlatform == "x86_64-pc-windows-gnullvm");
+          }
+          {
+            name = "windows_x86_64_msvc";
+            packageId = "windows_x86_64_msvc 0.52.0";
+            target = { target, features }: (("x86_64" == target."arch" or null) && ("msvc" == target."env" or null) && (!(target."windows_raw_dylib" or false)));
+          }
+        ];
+
+      };
+      "windows_aarch64_gnullvm 0.48.5" = rec {
+        crateName = "windows_aarch64_gnullvm";
+        version = "0.48.5";
+        edition = "2018";
+        sha256 = "1n05v7qblg1ci3i567inc7xrkmywczxrs1z3lj3rkkxw18py6f1b";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "windows_aarch64_gnullvm 0.52.0" = rec {
+        crateName = "windows_aarch64_gnullvm";
+        version = "0.52.0";
+        edition = "2021";
+        sha256 = "1shmn1kbdc0bpphcxz0vlph96bxz0h1jlmh93s9agf2dbpin8xyb";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "windows_aarch64_msvc 0.48.5" = rec {
+        crateName = "windows_aarch64_msvc";
+        version = "0.48.5";
+        edition = "2018";
+        sha256 = "1g5l4ry968p73g6bg6jgyvy9lb8fyhcs54067yzxpcpkf44k2dfw";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "windows_aarch64_msvc 0.52.0" = rec {
+        crateName = "windows_aarch64_msvc";
+        version = "0.52.0";
+        edition = "2021";
+        sha256 = "1vvmy1ypvzdvxn9yf0b8ygfl85gl2gpcyvsvqppsmlpisil07amv";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "windows_i686_gnu 0.48.5" = rec {
+        crateName = "windows_i686_gnu";
+        version = "0.48.5";
+        edition = "2018";
+        sha256 = "0gklnglwd9ilqx7ac3cn8hbhkraqisd0n83jxzf9837nvvkiand7";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "windows_i686_gnu 0.52.0" = rec {
+        crateName = "windows_i686_gnu";
+        version = "0.52.0";
+        edition = "2021";
+        sha256 = "04zkglz4p3pjsns5gbz85v4s5aw102raz4spj4b0lmm33z5kg1m2";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "windows_i686_msvc 0.48.5" = rec {
+        crateName = "windows_i686_msvc";
+        version = "0.48.5";
+        edition = "2018";
+        sha256 = "01m4rik437dl9rdf0ndnm2syh10hizvq0dajdkv2fjqcywrw4mcg";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "windows_i686_msvc 0.52.0" = rec {
+        crateName = "windows_i686_msvc";
+        version = "0.52.0";
+        edition = "2021";
+        sha256 = "16kvmbvx0vr0zbgnaz6nsks9ycvfh5xp05bjrhq65kj623iyirgz";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "windows_x86_64_gnu 0.48.5" = rec {
+        crateName = "windows_x86_64_gnu";
+        version = "0.48.5";
+        edition = "2018";
+        sha256 = "13kiqqcvz2vnyxzydjh73hwgigsdr2z1xpzx313kxll34nyhmm2k";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "windows_x86_64_gnu 0.52.0" = rec {
+        crateName = "windows_x86_64_gnu";
+        version = "0.52.0";
+        edition = "2021";
+        sha256 = "1zdy4qn178sil5sdm63lm7f0kkcjg6gvdwmcprd2yjmwn8ns6vrx";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "windows_x86_64_gnullvm 0.48.5" = rec {
+        crateName = "windows_x86_64_gnullvm";
+        version = "0.48.5";
+        edition = "2018";
+        sha256 = "1k24810wfbgz8k48c2yknqjmiigmql6kk3knmddkv8k8g1v54yqb";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "windows_x86_64_gnullvm 0.52.0" = rec {
+        crateName = "windows_x86_64_gnullvm";
+        version = "0.52.0";
+        edition = "2021";
+        sha256 = "17lllq4l2k1lqgcnw1cccphxp9vs7inq99kjlm2lfl9zklg7wr8s";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "windows_x86_64_msvc 0.48.5" = rec {
+        crateName = "windows_x86_64_msvc";
+        version = "0.48.5";
+        edition = "2018";
+        sha256 = "0f4mdp895kkjh9zv8dxvn4pc10xr7839lf5pa9l0193i2pkgr57d";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "windows_x86_64_msvc 0.52.0" = rec {
+        crateName = "windows_x86_64_msvc";
+        version = "0.52.0";
+        edition = "2021";
+        sha256 = "012wfq37f18c09ij5m6rniw7xxn5fcvrxbqd0wd8vgnl3hfn9yfz";
+        authors = [
+          "Microsoft"
+        ];
+
+      };
+      "xxhash-rust" = rec {
+        crateName = "xxhash-rust";
+        version = "0.8.8";
+        edition = "2018";
+        sha256 = "0q9xl4kxibh61631lw9m7if7pkdvq3pp5ss52zdkxs6rirkhdgjk";
+        authors = [
+          "Douman <douman@gmx.se>"
+        ];
+        features = { };
+        resolvedDefaultFeatures = [ "xxh3" ];
+      };
+      "zerocopy" = rec {
+        crateName = "zerocopy";
+        version = "0.7.32";
+        edition = "2018";
+        sha256 = "1ghnfxw69kx5d1aqfd5fsfrra9dgpz17yqx84nd4ryjk3sbd7m3l";
+        authors = [
+          "Joshua Liebow-Feeser <joshlf@google.com>"
+        ];
+        dependencies = [
+          {
+            name = "zerocopy-derive";
+            packageId = "zerocopy-derive";
+            optional = true;
+          }
+          {
+            name = "zerocopy-derive";
+            packageId = "zerocopy-derive";
+            target = { target, features }: false;
+          }
+        ];
+        devDependencies = [
+          {
+            name = "zerocopy-derive";
+            packageId = "zerocopy-derive";
+          }
+        ];
+        features = {
+          "__internal_use_only_features_that_work_on_stable" = [ "alloc" "derive" "simd" ];
+          "byteorder" = [ "dep:byteorder" ];
+          "default" = [ "byteorder" ];
+          "derive" = [ "zerocopy-derive" ];
+          "simd-nightly" = [ "simd" ];
+          "zerocopy-derive" = [ "dep:zerocopy-derive" ];
+        };
+        resolvedDefaultFeatures = [ "simd" ];
+      };
+      "zerocopy-derive" = rec {
+        crateName = "zerocopy-derive";
+        version = "0.7.32";
+        edition = "2018";
+        sha256 = "19nj11md42aijyqnfx8pa647fjzhz537xyc624rajwwfrn6b3qcw";
+        procMacro = true;
+        authors = [
+          "Joshua Liebow-Feeser <joshlf@google.com>"
+        ];
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn 2.0.48";
+          }
+        ];
+
+      };
+      "zeroize" = rec {
+        crateName = "zeroize";
+        version = "1.7.0";
+        edition = "2021";
+        sha256 = "0bfvby7k9pdp6623p98yz2irqnamcyzpn7zh20nqmdn68b0lwnsj";
+        authors = [
+          "The RustCrypto Project Developers"
+        ];
+        features = {
+          "default" = [ "alloc" ];
+          "derive" = [ "zeroize_derive" ];
+          "serde" = [ "dep:serde" ];
+          "std" = [ "alloc" ];
+          "zeroize_derive" = [ "dep:zeroize_derive" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" ];
+      };
+      "zstd" = rec {
+        crateName = "zstd";
+        version = "0.13.0";
+        edition = "2018";
+        sha256 = "0401q54s9r35x2i7m1kwppgkj79g0pb6xz3xpby7qlkdb44k7yxz";
+        authors = [
+          "Alexandre Bury <alexandre.bury@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "zstd-safe";
+            packageId = "zstd-safe";
+            usesDefaultFeatures = false;
+            features = [ "std" ];
+          }
+        ];
+        features = {
+          "arrays" = [ "zstd-safe/arrays" ];
+          "bindgen" = [ "zstd-safe/bindgen" ];
+          "debug" = [ "zstd-safe/debug" ];
+          "default" = [ "legacy" "arrays" "zdict_builder" ];
+          "experimental" = [ "zstd-safe/experimental" ];
+          "fat-lto" = [ "zstd-safe/fat-lto" ];
+          "legacy" = [ "zstd-safe/legacy" ];
+          "no_asm" = [ "zstd-safe/no_asm" ];
+          "pkg-config" = [ "zstd-safe/pkg-config" ];
+          "thin" = [ "zstd-safe/thin" ];
+          "thin-lto" = [ "zstd-safe/thin-lto" ];
+          "zdict_builder" = [ "zstd-safe/zdict_builder" ];
+          "zstdmt" = [ "zstd-safe/zstdmt" ];
+        };
+        resolvedDefaultFeatures = [ "arrays" "default" "legacy" "zdict_builder" ];
+      };
+      "zstd-safe" = rec {
+        crateName = "zstd-safe";
+        version = "7.0.0";
+        edition = "2018";
+        sha256 = "0gpav2lcibrpmyslmjkcn3w0w64qif3jjljd2h8lr4p249s7qx23";
+        authors = [
+          "Alexandre Bury <alexandre.bury@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "zstd-sys";
+            packageId = "zstd-sys";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "bindgen" = [ "zstd-sys/bindgen" ];
+          "debug" = [ "zstd-sys/debug" ];
+          "default" = [ "legacy" "arrays" "zdict_builder" ];
+          "experimental" = [ "zstd-sys/experimental" ];
+          "fat-lto" = [ "zstd-sys/fat-lto" ];
+          "legacy" = [ "zstd-sys/legacy" ];
+          "no_asm" = [ "zstd-sys/no_asm" ];
+          "pkg-config" = [ "zstd-sys/pkg-config" ];
+          "std" = [ "zstd-sys/std" ];
+          "thin" = [ "zstd-sys/thin" ];
+          "thin-lto" = [ "zstd-sys/thin-lto" ];
+          "zdict_builder" = [ "zstd-sys/zdict_builder" ];
+          "zstdmt" = [ "zstd-sys/zstdmt" ];
+        };
+        resolvedDefaultFeatures = [ "arrays" "legacy" "std" "zdict_builder" ];
+      };
+      "zstd-sys" = rec {
+        crateName = "zstd-sys";
+        version = "2.0.9+zstd.1.5.5";
+        edition = "2018";
+        links = "zstd";
+        sha256 = "0mk6a2367swdi22zg03lcackpnvgq96d7120awd4i83lm2lfy5ly";
+        authors = [
+          "Alexandre Bury <alexandre.bury@gmail.com>"
+        ];
+        buildDependencies = [
+          {
+            name = "cc";
+            packageId = "cc";
+            features = [ "parallel" ];
+          }
+          {
+            name = "pkg-config";
+            packageId = "pkg-config";
+          }
+        ];
+        features = {
+          "bindgen" = [ "dep:bindgen" ];
+          "default" = [ "legacy" "zdict_builder" ];
+        };
+        resolvedDefaultFeatures = [ "legacy" "std" "zdict_builder" ];
+      };
+    };
+
+    #
+    # crate2nix/default.nix (excerpt start)
+    #
+
+    /* Target (platform) data for conditional dependencies.
+      This corresponds roughly to what buildRustCrate is setting.
+    */
+    makeDefaultTarget = platform: {
+      unix = platform.isUnix;
+      windows = platform.isWindows;
+      fuchsia = true;
+      test = false;
+
+      /* We are choosing an arbitrary rust version to grab `lib` from,
+      which is unfortunate, but `lib` has been version-agnostic the
+      whole time so this is good enough for now.
+      */
+      os = pkgs.rust.lib.toTargetOs platform;
+      arch = pkgs.rust.lib.toTargetArch platform;
+      family = pkgs.rust.lib.toTargetFamily platform;
+      vendor = pkgs.rust.lib.toTargetVendor platform;
+      env = "gnu";
+      endian =
+        if platform.parsed.cpu.significantByte.name == "littleEndian"
+        then "little" else "big";
+      pointer_width = toString platform.parsed.cpu.bits;
+      debug_assertions = false;
+    };
+
+    /* Filters common temp files and build files. */
+    # TODO(pkolloch): Substitute with gitignore filter
+    sourceFilter = name: type:
+      let
+        baseName = builtins.baseNameOf (builtins.toString name);
+      in
+        ! (
+          # Filter out git
+          baseName == ".gitignore"
+          || (type == "directory" && baseName == ".git")
+
+          # Filter out build results
+          || (
+            type == "directory" && (
+              baseName == "target"
+              || baseName == "_site"
+              || baseName == ".sass-cache"
+              || baseName == ".jekyll-metadata"
+              || baseName == "build-artifacts"
+            )
+          )
+
+          # Filter out nix-build result symlinks
+          || (
+            type == "symlink" && lib.hasPrefix "result" baseName
+          )
+
+          # Filter out IDE config
+          || (
+            type == "directory" && (
+              baseName == ".idea" || baseName == ".vscode"
+            )
+          ) || lib.hasSuffix ".iml" baseName
+
+          # Filter out nix build files
+          || baseName == "Cargo.nix"
+
+          # Filter out editor backup / swap files.
+          || lib.hasSuffix "~" baseName
+          || builtins.match "^\\.sw[a-z]$$" baseName != null
+          || builtins.match "^\\..*\\.sw[a-z]$$" baseName != null
+          || lib.hasSuffix ".tmp" baseName
+          || lib.hasSuffix ".bak" baseName
+          || baseName == "tests.nix"
+        );
+
+    /* Returns a crate which depends on successful test execution
+      of crate given as the second argument.
+
+      testCrateFlags: list of flags to pass to the test exectuable
+      testInputs: list of packages that should be available during test execution
+    */
+    crateWithTest = { crate, testCrate, testCrateFlags, testInputs, testPreRun, testPostRun }:
+      assert builtins.typeOf testCrateFlags == "list";
+      assert builtins.typeOf testInputs == "list";
+      assert builtins.typeOf testPreRun == "string";
+      assert builtins.typeOf testPostRun == "string";
+      let
+        # override the `crate` so that it will build and execute tests instead of
+        # building the actual lib and bin targets We just have to pass `--test`
+        # to rustc and it will do the right thing.  We execute the tests and copy
+        # their log and the test executables to $out for later inspection.
+        test =
+          let
+            drv = testCrate.override
+              (
+                _: {
+                  buildTests = true;
+                }
+              );
+            # If the user hasn't set any pre/post commands, we don't want to
+            # insert empty lines. This means that any existing users of crate2nix
+            # don't get a spurious rebuild unless they set these explicitly.
+            testCommand = pkgs.lib.concatStringsSep "\n"
+              (pkgs.lib.filter (s: s != "") [
+                testPreRun
+                "$f $testCrateFlags 2>&1 | tee -a $out"
+                testPostRun
+              ]);
+          in
+          pkgs.runCommand "run-tests-${testCrate.name}"
+            {
+              inherit testCrateFlags;
+              buildInputs = testInputs;
+            } ''
+            set -e
+
+            export RUST_BACKTRACE=1
+
+            # recreate a file hierarchy as when running tests with cargo
+
+            # the source for test data
+            # It's necessary to locate the source in $NIX_BUILD_TOP/source/
+            # instead of $NIX_BUILD_TOP/
+            # because we compiled those test binaries in the former and not the latter.
+            # So all paths will expect source tree to be there and not in the build top directly.
+            # For example: $NIX_BUILD_TOP := /build in general, if you ask yourself.
+            # TODO(raitobezarius): I believe there could be more edge cases if `crate.sourceRoot`
+            # do exist but it's very hard to reason about them, so let's wait until the first bug report.
+            mkdir -p source/
+            cd source/
+
+            ${pkgs.buildPackages.xorg.lndir}/bin/lndir ${crate.src}
+
+            # build outputs
+            testRoot=target/debug
+            mkdir -p $testRoot
+
+            # executables of the crate
+            # we copy to prevent std::env::current_exe() to resolve to a store location
+            for i in ${crate}/bin/*; do
+              cp "$i" "$testRoot"
+            done
+            chmod +w -R .
+
+            # test harness executables are suffixed with a hash, like cargo does
+            # this allows to prevent name collision with the main
+            # executables of the crate
+            hash=$(basename $out)
+            for file in ${drv}/tests/*; do
+              f=$testRoot/$(basename $file)-$hash
+              cp $file $f
+              ${testCommand}
+            done
+          '';
+      in
+      pkgs.runCommand "${crate.name}-linked"
+        {
+          inherit (crate) outputs crateName;
+          passthru = (crate.passthru or { }) // {
+            inherit test;
+          };
+        }
+        (lib.optionalString (stdenv.buildPlatform.canExecute stdenv.hostPlatform) ''
+          echo tested by ${test}
+        '' + ''
+          ${lib.concatMapStringsSep "\n" (output: "ln -s ${crate.${output}} ${"$"}${output}") crate.outputs}
+        '');
+
+    /* A restricted overridable version of builtRustCratesWithFeatures. */
+    buildRustCrateWithFeatures =
+      { packageId
+      , features ? rootFeatures
+      , crateOverrides ? defaultCrateOverrides
+      , buildRustCrateForPkgsFunc ? null
+      , runTests ? false
+      , testCrateFlags ? [ ]
+      , testInputs ? [ ]
+        # Any command to run immediatelly before a test is executed.
+      , testPreRun ? ""
+        # Any command run immediatelly after a test is executed.
+      , testPostRun ? ""
+      }:
+      lib.makeOverridable
+        (
+          { features
+          , crateOverrides
+          , runTests
+          , testCrateFlags
+          , testInputs
+          , testPreRun
+          , testPostRun
+          }:
+          let
+            buildRustCrateForPkgsFuncOverriden =
+              if buildRustCrateForPkgsFunc != null
+              then buildRustCrateForPkgsFunc
+              else
+                (
+                  if crateOverrides == pkgs.defaultCrateOverrides
+                  then buildRustCrateForPkgs
+                  else
+                    pkgs: (buildRustCrateForPkgs pkgs).override {
+                      defaultCrateOverrides = crateOverrides;
+                    }
+                );
+            builtRustCrates = builtRustCratesWithFeatures {
+              inherit packageId features;
+              buildRustCrateForPkgsFunc = buildRustCrateForPkgsFuncOverriden;
+              runTests = false;
+            };
+            builtTestRustCrates = builtRustCratesWithFeatures {
+              inherit packageId features;
+              buildRustCrateForPkgsFunc = buildRustCrateForPkgsFuncOverriden;
+              runTests = true;
+            };
+            drv = builtRustCrates.crates.${packageId};
+            testDrv = builtTestRustCrates.crates.${packageId};
+            derivation =
+              if runTests then
+                crateWithTest
+                  {
+                    crate = drv;
+                    testCrate = testDrv;
+                    inherit testCrateFlags testInputs testPreRun testPostRun;
+                  }
+              else drv;
+          in
+          derivation
+        )
+        { inherit features crateOverrides runTests testCrateFlags testInputs testPreRun testPostRun; };
+
+    /* Returns an attr set with packageId mapped to the result of buildRustCrateForPkgsFunc
+      for the corresponding crate.
+    */
+    builtRustCratesWithFeatures =
+      { packageId
+      , features
+      , crateConfigs ? crates
+      , buildRustCrateForPkgsFunc
+      , runTests
+      , makeTarget ? makeDefaultTarget
+      } @ args:
+        assert (builtins.isAttrs crateConfigs);
+        assert (builtins.isString packageId);
+        assert (builtins.isList features);
+        assert (builtins.isAttrs (makeTarget stdenv.hostPlatform));
+        assert (builtins.isBool runTests);
+        let
+          rootPackageId = packageId;
+          mergedFeatures = mergePackageFeatures
+            (
+              args // {
+                inherit rootPackageId;
+                target = makeTarget stdenv.hostPlatform // { test = runTests; };
+              }
+            );
+          # Memoize built packages so that reappearing packages are only built once.
+          builtByPackageIdByPkgs = mkBuiltByPackageIdByPkgs pkgs;
+          mkBuiltByPackageIdByPkgs = pkgs:
+            let
+              self = {
+                crates = lib.mapAttrs (packageId: value: buildByPackageIdForPkgsImpl self pkgs packageId) crateConfigs;
+                target = makeTarget pkgs.stdenv.hostPlatform;
+                build = mkBuiltByPackageIdByPkgs pkgs.buildPackages;
+              };
+            in
+            self;
+          buildByPackageIdForPkgsImpl = self: pkgs: packageId:
+            let
+              features = mergedFeatures."${packageId}" or [ ];
+              crateConfig' = crateConfigs."${packageId}";
+              crateConfig =
+                builtins.removeAttrs crateConfig' [ "resolvedDefaultFeatures" "devDependencies" ];
+              devDependencies =
+                lib.optionals
+                  (runTests && packageId == rootPackageId)
+                  (crateConfig'.devDependencies or [ ]);
+              dependencies =
+                dependencyDerivations {
+                  inherit features;
+                  inherit (self) target;
+                  buildByPackageId = depPackageId:
+                    # proc_macro crates must be compiled for the build architecture
+                    if crateConfigs.${depPackageId}.procMacro or false
+                    then self.build.crates.${depPackageId}
+                    else self.crates.${depPackageId};
+                  dependencies =
+                    (crateConfig.dependencies or [ ])
+                    ++ devDependencies;
+                };
+              buildDependencies =
+                dependencyDerivations {
+                  inherit features;
+                  inherit (self.build) target;
+                  buildByPackageId = depPackageId:
+                    self.build.crates.${depPackageId};
+                  dependencies = crateConfig.buildDependencies or [ ];
+                };
+              dependenciesWithRenames =
+                let
+                  buildDeps = filterEnabledDependencies {
+                    inherit features;
+                    inherit (self) target;
+                    dependencies = crateConfig.dependencies or [ ] ++ devDependencies;
+                  };
+                  hostDeps = filterEnabledDependencies {
+                    inherit features;
+                    inherit (self.build) target;
+                    dependencies = crateConfig.buildDependencies or [ ];
+                  };
+                in
+                lib.filter (d: d ? "rename") (hostDeps ++ buildDeps);
+              # Crate renames have the form:
+              #
+              # {
+              #    crate_name = [
+              #       { version = "1.2.3"; rename = "crate_name01"; }
+              #    ];
+              #    # ...
+              # }
+              crateRenames =
+                let
+                  grouped =
+                    lib.groupBy
+                      (dependency: dependency.name)
+                      dependenciesWithRenames;
+                  versionAndRename = dep:
+                    let
+                      package = crateConfigs."${dep.packageId}";
+                    in
+                    { inherit (dep) rename; inherit (package) version; };
+                in
+                lib.mapAttrs (name: builtins.map versionAndRename) grouped;
+            in
+            buildRustCrateForPkgsFunc pkgs
+              (
+                crateConfig // {
+                  # https://github.com/NixOS/nixpkgs/issues/218712
+                  dontStrip = stdenv.hostPlatform.isDarwin;
+                  src = crateConfig.src or (
+                    pkgs.fetchurl rec {
+                      name = "${crateConfig.crateName}-${crateConfig.version}.tar.gz";
+                      # https://www.pietroalbini.org/blog/downloading-crates-io/
+                      # Not rate-limited, CDN URL.
+                      url = "https://static.crates.io/crates/${crateConfig.crateName}/${crateConfig.crateName}-${crateConfig.version}.crate";
+                      sha256 =
+                        assert (lib.assertMsg (crateConfig ? sha256) "Missing sha256 for ${name}");
+                        crateConfig.sha256;
+                    }
+                  );
+                  extraRustcOpts = lib.lists.optional (targetFeatures != [ ]) "-C target-feature=${lib.concatMapStringsSep "," (x: "+${x}") targetFeatures}";
+                  inherit features dependencies buildDependencies crateRenames release;
+                }
+              );
+        in
+        builtByPackageIdByPkgs;
+
+    /* Returns the actual derivations for the given dependencies. */
+    dependencyDerivations =
+      { buildByPackageId
+      , features
+      , dependencies
+      , target
+      }:
+        assert (builtins.isList features);
+        assert (builtins.isList dependencies);
+        assert (builtins.isAttrs target);
+        let
+          enabledDependencies = filterEnabledDependencies {
+            inherit dependencies features target;
+          };
+          depDerivation = dependency: buildByPackageId dependency.packageId;
+        in
+        map depDerivation enabledDependencies;
+
+    /* Returns a sanitized version of val with all values substituted that cannot
+      be serialized as JSON.
+    */
+    sanitizeForJson = val:
+      if builtins.isAttrs val
+      then lib.mapAttrs (n: sanitizeForJson) val
+      else if builtins.isList val
+      then builtins.map sanitizeForJson val
+      else if builtins.isFunction val
+      then "function"
+      else val;
+
+    /* Returns various tools to debug a crate. */
+    debugCrate = { packageId, target ? makeDefaultTarget stdenv.hostPlatform }:
+      assert (builtins.isString packageId);
+      let
+        debug = rec {
+          # The built tree as passed to buildRustCrate.
+          buildTree = buildRustCrateWithFeatures {
+            buildRustCrateForPkgsFunc = _: lib.id;
+            inherit packageId;
+          };
+          sanitizedBuildTree = sanitizeForJson buildTree;
+          dependencyTree = sanitizeForJson
+            (
+              buildRustCrateWithFeatures {
+                buildRustCrateForPkgsFunc = _: crate: {
+                  "01_crateName" = crate.crateName or false;
+                  "02_features" = crate.features or [ ];
+                  "03_dependencies" = crate.dependencies or [ ];
+                };
+                inherit packageId;
+              }
+            );
+          mergedPackageFeatures = mergePackageFeatures {
+            features = rootFeatures;
+            inherit packageId target;
+          };
+          diffedDefaultPackageFeatures = diffDefaultPackageFeatures {
+            inherit packageId target;
+          };
+        };
+      in
+      { internal = debug; };
+
+    /* Returns differences between cargo default features and crate2nix default
+      features.
+
+      This is useful for verifying the feature resolution in crate2nix.
+    */
+    diffDefaultPackageFeatures =
+      { crateConfigs ? crates
+      , packageId
+      , target
+      }:
+        assert (builtins.isAttrs crateConfigs);
+        let
+          prefixValues = prefix: lib.mapAttrs (n: v: { "${prefix}" = v; });
+          mergedFeatures =
+            prefixValues
+              "crate2nix"
+              (mergePackageFeatures { inherit crateConfigs packageId target; features = [ "default" ]; });
+          configs = prefixValues "cargo" crateConfigs;
+          combined = lib.foldAttrs (a: b: a // b) { } [ mergedFeatures configs ];
+          onlyInCargo =
+            builtins.attrNames
+              (lib.filterAttrs (n: v: !(v ? "crate2nix") && (v ? "cargo")) combined);
+          onlyInCrate2Nix =
+            builtins.attrNames
+              (lib.filterAttrs (n: v: (v ? "crate2nix") && !(v ? "cargo")) combined);
+          differentFeatures = lib.filterAttrs
+            (
+              n: v:
+                (v ? "crate2nix")
+                && (v ? "cargo")
+                && (v.crate2nix.features or [ ]) != (v."cargo".resolved_default_features or [ ])
+            )
+            combined;
+        in
+        builtins.toJSON {
+          inherit onlyInCargo onlyInCrate2Nix differentFeatures;
+        };
+
+    /* Returns an attrset mapping packageId to the list of enabled features.
+
+      If multiple paths to a dependency enable different features, the
+      corresponding feature sets are merged. Features in rust are additive.
+    */
+    mergePackageFeatures =
+      { crateConfigs ? crates
+      , packageId
+      , rootPackageId ? packageId
+      , features ? rootFeatures
+      , dependencyPath ? [ crates.${packageId}.crateName ]
+      , featuresByPackageId ? { }
+      , target
+        # Adds devDependencies to the crate with rootPackageId.
+      , runTests ? false
+      , ...
+      } @ args:
+        assert (builtins.isAttrs crateConfigs);
+        assert (builtins.isString packageId);
+        assert (builtins.isString rootPackageId);
+        assert (builtins.isList features);
+        assert (builtins.isList dependencyPath);
+        assert (builtins.isAttrs featuresByPackageId);
+        assert (builtins.isAttrs target);
+        assert (builtins.isBool runTests);
+        let
+          crateConfig = crateConfigs."${packageId}" or (builtins.throw "Package not found: ${packageId}");
+          expandedFeatures = expandFeatures (crateConfig.features or { }) features;
+          enabledFeatures = enableFeatures (crateConfig.dependencies or [ ]) expandedFeatures;
+          depWithResolvedFeatures = dependency:
+            let
+              inherit (dependency) packageId;
+              features = dependencyFeatures enabledFeatures dependency;
+            in
+            { inherit packageId features; };
+          resolveDependencies = cache: path: dependencies:
+            assert (builtins.isAttrs cache);
+            assert (builtins.isList dependencies);
+            let
+              enabledDependencies = filterEnabledDependencies {
+                inherit dependencies target;
+                features = enabledFeatures;
+              };
+              directDependencies = map depWithResolvedFeatures enabledDependencies;
+              foldOverCache = op: lib.foldl op cache directDependencies;
+            in
+            foldOverCache
+              (
+                cache: { packageId, features }:
+                  let
+                    cacheFeatures = cache.${packageId} or [ ];
+                    combinedFeatures = sortedUnique (cacheFeatures ++ features);
+                  in
+                  if cache ? ${packageId} && cache.${packageId} == combinedFeatures
+                  then cache
+                  else
+                    mergePackageFeatures {
+                      features = combinedFeatures;
+                      featuresByPackageId = cache;
+                      inherit crateConfigs packageId target runTests rootPackageId;
+                    }
+              );
+          cacheWithSelf =
+            let
+              cacheFeatures = featuresByPackageId.${packageId} or [ ];
+              combinedFeatures = sortedUnique (cacheFeatures ++ enabledFeatures);
+            in
+            featuresByPackageId // {
+              "${packageId}" = combinedFeatures;
+            };
+          cacheWithDependencies =
+            resolveDependencies cacheWithSelf "dep"
+              (
+                crateConfig.dependencies or [ ]
+                ++ lib.optionals
+                  (runTests && packageId == rootPackageId)
+                  (crateConfig.devDependencies or [ ])
+              );
+          cacheWithAll =
+            resolveDependencies
+              cacheWithDependencies "build"
+              (crateConfig.buildDependencies or [ ]);
+        in
+        cacheWithAll;
+
+    /* Returns the enabled dependencies given the enabled features. */
+    filterEnabledDependencies = { dependencies, features, target }:
+      assert (builtins.isList dependencies);
+      assert (builtins.isList features);
+      assert (builtins.isAttrs target);
+
+      lib.filter
+        (
+          dep:
+          let
+            targetFunc = dep.target or (features: true);
+          in
+          targetFunc { inherit features target; }
+          && (
+            !(dep.optional or false)
+            || builtins.any (doesFeatureEnableDependency dep) features
+          )
+        )
+        dependencies;
+
+    /* Returns whether the given feature should enable the given dependency. */
+    doesFeatureEnableDependency = dependency: feature:
+      let
+        name = dependency.rename or dependency.name;
+        prefix = "${name}/";
+        len = builtins.stringLength prefix;
+        startsWithPrefix = builtins.substring 0 len feature == prefix;
+      in
+      feature == name || feature == "dep:" + name || startsWithPrefix;
+
+    /* Returns the expanded features for the given inputFeatures by applying the
+      rules in featureMap.
+
+      featureMap is an attribute set which maps feature names to lists of further
+      feature names to enable in case this feature is selected.
+    */
+    expandFeatures = featureMap: inputFeatures:
+      assert (builtins.isAttrs featureMap);
+      assert (builtins.isList inputFeatures);
+      let
+        expandFeaturesNoCycle = oldSeen: inputFeatures:
+          if inputFeatures != [ ]
+          then
+            let
+              # The feature we're currently expanding.
+              feature = builtins.head inputFeatures;
+              # All the features we've seen/expanded so far, including the one
+              # we're currently processing.
+              seen = oldSeen // { ${feature} = 1; };
+              # Expand the feature but be careful to not re-introduce a feature
+              # that we've already seen: this can easily cause a cycle, see issue
+              # #209.
+              enables = builtins.filter (f: !(seen ? "${f}")) (featureMap."${feature}" or [ ]);
+            in
+            [ feature ] ++ (expandFeaturesNoCycle seen (builtins.tail inputFeatures ++ enables))
+          # No more features left, nothing to expand to.
+          else [ ];
+        outFeatures = expandFeaturesNoCycle { } inputFeatures;
+      in
+      sortedUnique outFeatures;
+
+    /* This function adds optional dependencies as features if they are enabled
+      indirectly by dependency features. This function mimics Cargo's behavior
+      described in a note at:
+      https://doc.rust-lang.org/nightly/cargo/reference/features.html#dependency-features
+    */
+    enableFeatures = dependencies: features:
+      assert (builtins.isList features);
+      assert (builtins.isList dependencies);
+      let
+        additionalFeatures = lib.concatMap
+          (
+            dependency:
+              assert (builtins.isAttrs dependency);
+              let
+                enabled = builtins.any (doesFeatureEnableDependency dependency) features;
+              in
+              if (dependency.optional or false) && enabled
+              then [ (dependency.rename or dependency.name) ]
+              else [ ]
+          )
+          dependencies;
+      in
+      sortedUnique (features ++ additionalFeatures);
+
+    /*
+      Returns the actual features for the given dependency.
+
+      features: The features of the crate that refers this dependency.
+    */
+    dependencyFeatures = features: dependency:
+      assert (builtins.isList features);
+      assert (builtins.isAttrs dependency);
+      let
+        defaultOrNil =
+          if dependency.usesDefaultFeatures or true
+          then [ "default" ]
+          else [ ];
+        explicitFeatures = dependency.features or [ ];
+        additionalDependencyFeatures =
+          let
+            name = dependency.rename or dependency.name;
+            stripPrefixMatch = prefix: s:
+              if lib.hasPrefix prefix s
+              then lib.removePrefix prefix s
+              else null;
+            extractFeature = feature: lib.findFirst
+              (f: f != null)
+              null
+              (map (prefix: stripPrefixMatch prefix feature) [
+                (name + "/")
+                (name + "?/")
+              ]);
+            dependencyFeatures = lib.filter (f: f != null) (map extractFeature features);
+          in
+          dependencyFeatures;
+      in
+      defaultOrNil ++ explicitFeatures ++ additionalDependencyFeatures;
+
+    /* Sorts and removes duplicates from a list of strings. */
+    sortedUnique = features:
+      assert (builtins.isList features);
+      assert (builtins.all builtins.isString features);
+      let
+        outFeaturesSet = lib.foldl (set: feature: set // { "${feature}" = 1; }) { } features;
+        outFeaturesUnique = builtins.attrNames outFeaturesSet;
+      in
+      builtins.sort (a: b: a < b) outFeaturesUnique;
+
+    deprecationWarning = message: value:
+      if strictDeprecation
+      then builtins.throw "strictDeprecation enabled, aborting: ${message}"
+      else builtins.trace message value;
+
+    #
+    # crate2nix/default.nix (excerpt end)
+    #
+  };
+}
+
diff --git a/tvix/tools/weave/Cargo.toml b/tvix/tools/weave/Cargo.toml
new file mode 100644
index 0000000000..7c6c2d2f0c
--- /dev/null
+++ b/tvix/tools/weave/Cargo.toml
@@ -0,0 +1,20 @@
+[package]
+name = "weave"
+version = "0.1.0"
+edition = "2021"
+
+[workspace]
+members = ["."]
+
+# TODO(edef): cut down on required features, this is kind of a grab bag right now
+[dependencies]
+anyhow = { version = "1.0.79", features = ["backtrace"] }
+hashbrown = "0.14.3"
+nix-compat = { version = "0.1.0", path = "../../nix-compat" }
+owning_ref = "0.4.1"
+rayon = "1.8.1"
+tokio = { version = "1.36.0", features = ["full"] }
+
+[dependencies.polars]
+version = "0.36.2"
+features = ["parquet"]
diff --git a/tvix/tools/weave/OWNERS b/tvix/tools/weave/OWNERS
new file mode 100644
index 0000000000..b9bc074a80
--- /dev/null
+++ b/tvix/tools/weave/OWNERS
@@ -0,0 +1 @@
+edef
diff --git a/tvix/tools/weave/default.nix b/tvix/tools/weave/default.nix
new file mode 100644
index 0000000000..5a84d7f642
--- /dev/null
+++ b/tvix/tools/weave/default.nix
@@ -0,0 +1,3 @@
+{ pkgs, ... }:
+
+(pkgs.callPackage ./Cargo.nix { }).rootCrate.build
diff --git a/tvix/tools/weave/src/bin/swizzle.rs b/tvix/tools/weave/src/bin/swizzle.rs
new file mode 100644
index 0000000000..68c1858126
--- /dev/null
+++ b/tvix/tools/weave/src/bin/swizzle.rs
@@ -0,0 +1,114 @@
+//! Swizzle reads a `narinfo.parquet` file, usually produced by `narinfo2parquet`.
+//!
+//! It swizzles the reference list, ie it converts the references from absolute,
+//! global identifiers (store path hashes) to indices into the `store_path_hash`
+//! column (ie, row numbers), so that we can later walk the reference graph
+//! efficiently.
+//!
+//! Path hashes are represented as non-null, 20-byte `Binary` values.
+//! The indices are represented as 32-bit unsigned integers, with in-band nulls
+//! represented by [INDEX_NULL] (the all-1 bit pattern), to permit swizzling
+//! partial datasets.
+//!
+//! In essence, it converts from names to pointers, so that `weave` can simply
+//! chase pointers to trace the live set. This replaces an `O(log(n))` lookup
+//! with `O(1)` indexing, and produces a much denser representation that actually
+//! fits in memory.
+//!
+//! The in-memory representation is at least 80% smaller, and the indices compress
+//! well in Parquet due to both temporal locality of reference and the power law
+//! distribution of reference "popularity".
+//!
+//! Only two columns are read from `narinfo.parquet`:
+//!
+//!  * `store_path_hash :: PathHash`
+//!  * `references :: List[PathHash]`
+//!
+//! Output is written to `narinfo-references.parquet` in the form of a single
+//! `List[u32]` column, `reference_idxs`.
+//!
+//! This file is inherently bound to the corresponding `narinfo.parquet`,
+//! since it essentially contains pointers into this file.
+
+use anyhow::Result;
+use hashbrown::HashTable;
+use polars::prelude::*;
+use rayon::prelude::*;
+use std::fs::File;
+use tokio::runtime::Runtime;
+
+use weave::{as_fixed_binary, hash64, load_ph_array, DONE, INDEX_NULL};
+
+fn main() -> Result<()> {
+    let ph_array = load_ph_array()?;
+
+    // TODO(edef): re-parallelise this
+    // We originally parallelised on chunks, but ph_array is only a single chunk, due to how Parquet loading works.
+    // TODO(edef): outline the 64-bit hash prefix? it's an indirection, but it saves ~2G of memory
+    eprint!("… build index\r");
+    let ph_map: HashTable<(u64, u32)> = {
+        let mut ph_map = HashTable::with_capacity(ph_array.len());
+
+        for (offset, item) in ph_array.iter().enumerate() {
+            let offset = offset as u32;
+            let hash = hash64(item);
+            ph_map.insert_unique(hash, (hash, offset), |&(hash, _)| hash);
+        }
+
+        ph_map
+    };
+    eprintln!("{DONE}");
+
+    eprint!("… swizzle references\r");
+    let mut pq = ParquetReader::new(File::open("narinfo.parquet")?)
+        .with_columns(Some(vec!["references".into()]))
+        .batched(1 << 16)?;
+
+    let mut reference_idxs =
+        Series::new_empty("reference_idxs", &DataType::List(DataType::UInt32.into()));
+
+    let mut bounce = vec![];
+    let runtime = Runtime::new()?;
+    while let Some(batches) = runtime.block_on(pq.next_batches(48))? {
+        batches
+            .into_par_iter()
+            .map(|df| -> ListChunked {
+                df.column("references")
+                    .unwrap()
+                    .list()
+                    .unwrap()
+                    .apply_to_inner(&|series: Series| -> PolarsResult<Series> {
+                        let series = series.binary()?;
+                        let mut out: Vec<u32> = Vec::with_capacity(series.len());
+
+                        out.extend(as_fixed_binary::<20>(series).flat_map(|xs| xs).map(|key| {
+                            let hash = hash64(&key);
+                            ph_map
+                                .find(hash, |&(candidate_hash, candidate_index)| {
+                                    candidate_hash == hash
+                                        && &ph_array[candidate_index as usize] == key
+                                })
+                                .map(|&(_, index)| index)
+                                .unwrap_or(INDEX_NULL)
+                        }));
+
+                        Ok(Series::from_vec("reference_idxs", out))
+                    })
+                    .unwrap()
+            })
+            .collect_into_vec(&mut bounce);
+
+        for batch in bounce.drain(..) {
+            reference_idxs.append(&batch.into_series())?;
+        }
+    }
+    eprintln!("{DONE}");
+
+    eprint!("… writing output\r");
+    ParquetWriter::new(File::create("narinfo-references.parquet")?).finish(&mut df! {
+        "reference_idxs" => reference_idxs,
+    }?)?;
+    eprintln!("{DONE}");
+
+    Ok(())
+}
diff --git a/tvix/tools/weave/src/bytes.rs b/tvix/tools/weave/src/bytes.rs
new file mode 100644
index 0000000000..c6dc2ebb44
--- /dev/null
+++ b/tvix/tools/weave/src/bytes.rs
@@ -0,0 +1,27 @@
+use owning_ref::{OwningRef, StableAddress};
+use polars::export::arrow::buffer::Buffer;
+use std::ops::Deref;
+
+/// An shared `[[u8; N]]` backed by a Polars [Buffer].
+pub type FixedBytes<const N: usize> = OwningRef<Bytes, [[u8; N]]>;
+
+/// Wrapper struct to make [Buffer] implement [StableAddress].
+/// TODO(edef): upstream the `impl`
+pub struct Bytes(pub Buffer<u8>);
+
+/// SAFETY: [Buffer] is always an Arc+Vec indirection.
+unsafe impl StableAddress for Bytes {}
+
+impl Bytes {
+    pub fn map<U: ?Sized>(self, f: impl FnOnce(&[u8]) -> &U) -> OwningRef<Self, U> {
+        OwningRef::new(self).map(f)
+    }
+}
+
+impl Deref for Bytes {
+    type Target = [u8];
+
+    fn deref(&self) -> &Self::Target {
+        &*self.0
+    }
+}
diff --git a/tvix/tools/weave/src/lib.rs b/tvix/tools/weave/src/lib.rs
new file mode 100644
index 0000000000..bc2221bf5c
--- /dev/null
+++ b/tvix/tools/weave/src/lib.rs
@@ -0,0 +1,106 @@
+use anyhow::Result;
+use rayon::prelude::*;
+use std::{fs::File, ops::Range, slice};
+
+use polars::{
+    datatypes::BinaryChunked,
+    export::arrow::array::BinaryArray,
+    prelude::{ParquetReader, SerReader},
+};
+
+pub use crate::bytes::*;
+mod bytes;
+
+pub const INDEX_NULL: u32 = !0;
+pub const DONE: &str = "\u{2714}";
+
+/// A terrific hash function, turning 20 bytes of cryptographic hash
+/// into 8 bytes of cryptographic hash.
+pub fn hash64(h: &[u8; 20]) -> u64 {
+    let mut buf = [0; 8];
+    buf.copy_from_slice(&h[..8]);
+    u64::from_ne_bytes(buf)
+}
+
+/// Read a dense `store_path_hash` array from `narinfo.parquet`,
+/// returning it as an owned [FixedBytes].
+pub fn load_ph_array() -> Result<FixedBytes<20>> {
+    eprint!("… load store_path_hash\r");
+    // TODO(edef): this could use a further pushdown, since polars is more hindrance than help here
+    // We know this has to fit in memory (we can't mmap it without further encoding constraints),
+    // and we want a single `Vec<[u8; 20]>` of the data.
+    let ph_array = into_fixed_binary_rechunk::<20>(
+        ParquetReader::new(File::open("narinfo.parquet").unwrap())
+            .with_columns(Some(vec!["store_path_hash".into()]))
+            .set_rechunk(true)
+            .finish()?
+            .column("store_path_hash")?
+            .binary()?,
+    );
+
+    u32::try_from(ph_array.len()).expect("dataset exceeds 2^32");
+    eprintln!("{DONE}");
+
+    Ok(ph_array)
+}
+
+/// Iterator over `&[[u8; N]]` from a dense [BinaryChunked].
+pub fn as_fixed_binary<const N: usize>(
+    chunked: &BinaryChunked,
+) -> impl Iterator<Item = &[[u8; N]]> + DoubleEndedIterator {
+    chunked.downcast_iter().map(|array| {
+        let range = assert_fixed_dense::<N>(array);
+        exact_chunks(&array.values()[range]).unwrap()
+    })
+}
+
+/// Convert a dense [BinaryChunked] into a single chunk as [FixedBytes],
+/// without taking a reference to the offsets array and validity bitmap.
+fn into_fixed_binary_rechunk<const N: usize>(chunked: &BinaryChunked) -> FixedBytes<N> {
+    let chunked = chunked.rechunk();
+    let mut iter = chunked.downcast_iter();
+    let array = iter.next().unwrap();
+
+    let range = assert_fixed_dense::<N>(array);
+    Bytes(array.values().clone().sliced(range.start, range.len()))
+        .map(|buf| exact_chunks(buf).unwrap())
+}
+
+/// Ensures that the supplied Arrow array consists of densely packed bytestrings of length `N`.
+/// In other words, ensure that it is free of nulls, and that the offsets have a fixed stride of `N`.
+#[must_use = "only the range returned is guaranteed to be conformant"]
+fn assert_fixed_dense<const N: usize>(array: &BinaryArray<i64>) -> Range<usize> {
+    let null_count = array.validity().map_or(0, |bits| bits.unset_bits());
+    if null_count > 0 {
+        panic!("null values present");
+    }
+
+    let offsets = array.offsets();
+    let length_check = offsets
+        .as_slice()
+        .par_windows(2)
+        .all(|w| (w[1] - w[0]) == N as i64);
+
+    if !length_check {
+        panic!("lengths are inconsistent");
+    }
+
+    (*offsets.first() as usize)..(*offsets.last() as usize)
+}
+
+fn exact_chunks<const K: usize>(buf: &[u8]) -> Option<&[[u8; K]]> {
+    // SAFETY: We ensure that `buf.len()` is a multiple of K, and there are no alignment requirements.
+    unsafe {
+        let ptr = buf.as_ptr();
+        let len = buf.len();
+
+        if len % K != 0 {
+            return None;
+        }
+
+        let ptr = ptr as *mut [u8; K];
+        let len = len / K;
+
+        Some(slice::from_raw_parts(ptr, len))
+    }
+}
diff --git a/tvix/tools/weave/src/main.rs b/tvix/tools/weave/src/main.rs
new file mode 100644
index 0000000000..e8a1990a0d
--- /dev/null
+++ b/tvix/tools/weave/src/main.rs
@@ -0,0 +1,216 @@
+//! Weave resolves a list of roots from `nixpkgs.roots` against `narinfo.parquet`,
+//! and then uses the reference graph from the accompanying `narinfo-references.parquet`
+//! produced by `swizzle` to collect the closure of the roots.
+//!
+//! They are written to `live_idxs.parquet`, which only has one column, representing
+//! the row numbers in `narinfo.parquet` corresponding to live paths.
+
+use anyhow::Result;
+use hashbrown::{hash_table, HashTable};
+use nix_compat::nixbase32;
+use rayon::prelude::*;
+use std::{
+    collections::{BTreeMap, HashSet},
+    fs::{self, File},
+    ops::Index,
+    sync::atomic::{AtomicU32, Ordering},
+};
+
+use polars::{
+    datatypes::StaticArray,
+    export::arrow::{array::UInt32Array, offset::OffsetsBuffer},
+    prelude::*,
+};
+
+use weave::{hash64, DONE, INDEX_NULL};
+
+fn main() -> Result<()> {
+    eprint!("… parse roots\r");
+    let roots: PathSet32 = {
+        let mut roots = Vec::new();
+        fs::read("nixpkgs.roots")?
+            .par_chunks_exact(32 + 1)
+            .map(|e| nixbase32::decode_fixed::<20>(&e[0..32]).unwrap())
+            .collect_into_vec(&mut roots);
+
+        roots.iter().collect()
+    };
+    eprintln!("{DONE}");
+
+    {
+        let ph_array = weave::load_ph_array()?;
+
+        eprint!("… resolve roots\r");
+        ph_array.par_iter().enumerate().for_each(|(idx, h)| {
+            if let Some(idx_slot) = roots.find(h) {
+                idx_slot
+                    .compare_exchange(INDEX_NULL, idx as u32, Ordering::SeqCst, Ordering::SeqCst)
+                    .expect("duplicate entry");
+            }
+        });
+        eprintln!("{DONE}");
+    }
+
+    let mut todo = HashSet::with_capacity(roots.len());
+    {
+        let mut unknown_roots = 0usize;
+        for (_, idx) in roots.table {
+            let idx = idx.into_inner();
+            if idx == INDEX_NULL {
+                unknown_roots += 1;
+                continue;
+            }
+            todo.insert(idx);
+        }
+        println!("skipping {unknown_roots} unknown roots");
+    }
+
+    eprint!("… load reference_idxs\r");
+    let ri_array = ParquetReader::new(File::open("narinfo-references.parquet")?)
+        .finish()?
+        .column("reference_idxs")?
+        .list()?
+        .clone();
+
+    let ri_array = {
+        ChunkedList::new(ri_array.downcast_iter().map(|chunk| {
+            (
+                chunk.offsets(),
+                chunk
+                    .values()
+                    .as_any()
+                    .downcast_ref::<UInt32Array>()
+                    .unwrap()
+                    .as_slice()
+                    .unwrap(),
+            )
+        }))
+    };
+    eprintln!("{DONE}");
+
+    let mut seen = todo.clone();
+    while !todo.is_empty() {
+        println!("todo: {} seen: {}", todo.len(), seen.len());
+
+        todo = todo
+            .par_iter()
+            .flat_map(|&parent| {
+                if parent == INDEX_NULL {
+                    return vec![];
+                }
+
+                ri_array[parent as usize]
+                    .iter()
+                    .cloned()
+                    .filter(|child| !seen.contains(child))
+                    .collect::<Vec<u32>>()
+            })
+            .collect();
+
+        for &index in &todo {
+            seen.insert(index);
+        }
+    }
+
+    println!("done: {} paths", seen.len());
+
+    if seen.remove(&INDEX_NULL) {
+        println!("WARNING: missing edges");
+    }
+
+    eprint!("… gathering live set\r");
+    let mut seen: Vec<u32> = seen.into_iter().collect();
+    seen.par_sort();
+    eprintln!("{DONE}");
+
+    eprint!("… writing output\r");
+    ParquetWriter::new(File::create("live_idxs.parquet")?).finish(&mut df! {
+        "live_idx" => seen,
+    }?)?;
+    eprintln!("{DONE}");
+
+    Ok(())
+}
+
+struct PathSet32 {
+    table: HashTable<([u8; 20], AtomicU32)>,
+}
+
+impl PathSet32 {
+    fn with_capacity(capacity: usize) -> Self {
+        Self {
+            table: HashTable::with_capacity(capacity),
+        }
+    }
+
+    fn insert(&mut self, value: &[u8; 20]) -> bool {
+        let hash = hash64(value);
+
+        match self
+            .table
+            .entry(hash, |(x, _)| x == value, |(x, _)| hash64(x))
+        {
+            hash_table::Entry::Occupied(_) => false,
+            hash_table::Entry::Vacant(entry) => {
+                entry.insert((*value, AtomicU32::new(INDEX_NULL)));
+                true
+            }
+        }
+    }
+
+    fn find(&self, value: &[u8; 20]) -> Option<&AtomicU32> {
+        let hash = hash64(value);
+        self.table
+            .find(hash, |(x, _)| x == value)
+            .as_ref()
+            .map(|(_, x)| x)
+    }
+
+    fn len(&self) -> usize {
+        self.table.len()
+    }
+}
+
+impl<'a> FromIterator<&'a [u8; 20]> for PathSet32 {
+    fn from_iter<T: IntoIterator<Item = &'a [u8; 20]>>(iter: T) -> Self {
+        let iter = iter.into_iter();
+        let mut this = Self::with_capacity(iter.size_hint().0);
+
+        for item in iter {
+            this.insert(item);
+        }
+
+        this
+    }
+}
+
+struct ChunkedList<'a, T> {
+    by_offset: BTreeMap<usize, (&'a OffsetsBuffer<i64>, &'a [T])>,
+}
+
+impl<'a, T> ChunkedList<'a, T> {
+    fn new(chunks: impl IntoIterator<Item = (&'a OffsetsBuffer<i64>, &'a [T])>) -> Self {
+        let mut next_offset = 0usize;
+        ChunkedList {
+            by_offset: chunks
+                .into_iter()
+                .map(|(offsets, values)| {
+                    let offset = next_offset;
+                    next_offset = next_offset.checked_add(offsets.len_proxy()).unwrap();
+
+                    (offset, (offsets, values))
+                })
+                .collect(),
+        }
+    }
+}
+
+impl<'a, T> Index<usize> for ChunkedList<'a, T> {
+    type Output = [T];
+
+    fn index(&self, index: usize) -> &Self::Output {
+        let (&base, &(offsets, values)) = self.by_offset.range(..=index).next_back().unwrap();
+        let (start, end) = offsets.start_end(index - base);
+        &values[start..end]
+    }
+}
diff --git a/tvix/verify-lang-tests/default.nix b/tvix/verify-lang-tests/default.nix
new file mode 100644
index 0000000000..772c1c5320
--- /dev/null
+++ b/tvix/verify-lang-tests/default.nix
@@ -0,0 +1,226 @@
+# SPDX-License-Identifier: LGPL-2.1-only
+# SPDX-FileCopyrightText: © 2022 The TVL Contributors
+# SPDX-FileCopyrightText: © 2004-2022 The Nix Contributors
+#
+# Execute language tests found in tvix_tests and nix_tests
+# using the C++ Nix implementation. Based on NixOS/nix:tests/lang.sh.
+{ depot, pkgs, lib, ... }:
+
+let
+  testRoot = ../eval/src/tests;
+
+  inherit (pkgs.buildPackages) nix nix_latest;
+
+  parseTest = dir: baseName:
+    let
+      tokens = builtins.match "(eval|parse)-(okay|fail).+\\.nix" baseName;
+    in
+    if tokens == null
+    then null
+    else {
+      type = builtins.elemAt tokens 0;
+      expectedSuccess = (builtins.elemAt tokens 1) == "okay";
+      fileName = "${dir}/${baseName}";
+    };
+
+  allLangTests =
+    lib.concatMap
+      (
+        dir:
+        lib.pipe
+          (builtins.readDir (testRoot + "/${dir}"))
+          [
+            builtins.attrNames
+            (builtins.map (parseTest dir))
+            (builtins.filter (t: t != null))
+          ]
+      ) [ "nix_tests" "nix_tests/notyetpassing" "tvix_tests" "tvix_tests/notyetpassing" ];
+
+  skippedLangTests = {
+    # TODO(sterni): set up NIX_PATH in sandbox
+    "eval-okay-search-path.nix" = true;
+    # Floating point precision differs between tvix and Nix
+    "eval-okay-fromjson.nix" = true;
+    # C++ Nix can't TCO
+    "eval-okay-tail-call-1.nix" = true;
+    # Ordering change after 2.3
+    "eval-okay-xml.nix" = [ nix ];
+    # Missing builtins in Nix 2.3
+    "eval-okay-ceil.nix" = [ nix ];
+    "eval-okay-floor-ceil.nix" = [ nix ];
+    "eval-okay-floor.nix" = [ nix ];
+    "eval-okay-groupBy.nix" = [ nix ];
+    "eval-okay-zipAttrsWith.nix" = [ nix ];
+    "eval-okay-builtins-group-by-propagate-catchable.nix" = [ nix ];
+    # Comparable lists are not in Nix 2.3
+    "eval-okay-sort.nix" = [ nix ];
+    "eval-okay-compare-lists.nix" = [ nix ];
+    "eval-okay-value-pointer-compare.nix" = [ nix ];
+    "eval-okay-builtins-genericClosure-pointer-equality.nix" = [ nix ];
+    "eval-okay-list-comparison.nix" = [ nix ];
+    # getAttrPos gains support for functionArgs-returned sets after 2.3
+    "eval-okay-getattrpos-functionargs.nix" = [ nix ];
+    # groupBy appeared (long) after 2.3
+    "eval-okay-builtins-groupby-thunk.nix" = [ nix ];
+    # import is no longer considered a curried primop in Nix > 2.3
+    "eval-okay-import-display.nix" = [ nix ];
+    # Cycle detection and formatting changed sometime after Nix 2.3
+    "eval-okay-cycle-display-cpp-nix-2.13.nix" = [ nix ];
+    # builtins.replaceStrings becomes lazier in Nix 2.16
+    "eval-okay-replacestrings.nix" = [ nix ];
+    # builtins.readFileType is added in Nix 2.15
+    "eval-okay-readFileType.nix" = [ nix ];
+    # builtins.fromTOML gains support for timestamps in Nix 2.16
+    "eval-okay-fromTOML-timestamps.nix" = [ nix ];
+    # identifier formatting changed in Nix 2.17 due to cppnix commit
+    # b72bc4a972fe568744d98b89d63adcd504cb586c
+    "eval-okay-identifier-formatting.nix" = [ nix ];
+
+    # Differing strictness in the function argument for some builtins in Nix 2.18
+    # https://github.com/NixOS/nix/issues/9779
+    "eval-okay-builtins-map-propagate-catchable.nix" = [ nix_latest ];
+    "eval-okay-builtins-gen-list-propagate-catchable.nix" = [ nix_latest ];
+    "eval-okay-builtins-replace-strings-propagate-catchable.nix" =
+      [ nix_latest ];
+    "eval-okay-builtins-map-function-strictness.nix" = [ nix_latest ];
+    "eval-okay-builtins-genList-function-strictness.nix" = [ nix_latest ];
+
+    # TODO(sterni): support diffing working directory and home relative paths
+    # like C++ Nix test suite (using string replacement).
+    "eval-okay-path-antiquotation.nix" = true;
+  };
+
+  runCppNixLangTests = cpp-nix:
+    let
+      testCommand = { fileName, type, expectedSuccess, ... }:
+        let
+          testBase = lib.removeSuffix ".nix" fileName;
+          expFile =
+            let
+              possibleFiles =
+                builtins.filter
+                  (path: builtins.pathExists (testRoot + "/${path}"))
+                  (builtins.map
+                    (ext: "${testBase}.${ext}")
+                    [ "exp" "exp.xml" ]);
+            in
+            if possibleFiles == [ ] then null else builtins.head possibleFiles;
+          outFile = "${testBase}.out";
+
+          # Skip if skippedLangTests prescribes it (possibly just for the current nix)
+          # or if we are missing an exp file for an eval-okay test.
+          skip =
+            let
+              doSkip = skippedLangTests.${builtins.baseNameOf fileName} or false;
+            in
+            if type == "eval" && expectedSuccess && (expFile == null) then true
+            else if builtins.isBool doSkip then doSkip
+            else builtins.any (drv: cpp-nix == drv) doSkip;
+
+          flagsFile = "${testBase}.flags";
+
+          instantiateFlags =
+            lib.escapeShellArgs
+              (
+                [ "--${type}" fileName ]
+                ++ lib.optionals (type == "eval") [ "--strict" ]
+                ++ lib.optionals (expFile != null && lib.hasSuffix "xml" expFile)
+                  [
+                    "--no-location"
+                    "--xml"
+                  ]
+              )
+            + lib.optionalString (builtins.pathExists (testRoot + "/${flagsFile}"))
+              " $(cat '${flagsFile}')";
+        in
+
+        if skip
+        then "echo \"SKIP ${type} ${fileName}\"\n"
+        else ''
+          thisTestPassed=true
+
+          echo "RUN  ${type} ${fileName} ${
+            lib.optionalString (!expectedSuccess) "(expecting failure)"
+          }"
+
+          if ! expect ${if expectedSuccess then "0" else "1"} \
+                 nix-instantiate ${instantiateFlags} \
+                 ${if expectedSuccess then "1" else "2"}> \
+                 ${if expFile != null then outFile else "/dev/null"};
+          then
+            echo -n "FAIL"
+            thisTestPassed=false
+          fi
+        '' + lib.optionalString (expFile != null) ''
+          if ! diff --color=always -u '${outFile}' '${expFile}'; then
+            thisTestPassed=false
+          fi
+        '' + ''
+          if $thisTestPassed; then
+            echo -n "PASS"
+          else
+            echo -n "FAIL"
+            passed=false
+          fi
+
+          echo " ${type} ${fileName}"
+
+          unset thisTestPassed
+        '';
+    in
+
+    pkgs.stdenv.mkDerivation {
+      name = "cpp-${cpp-nix.name}-run-lang-tests";
+
+      nativeBuildInputs = [ cpp-nix ];
+
+      # Obtain tests via the unpackPhase
+      src = testRoot;
+      dontConfigure = true;
+
+      # Environment expected by the test suite
+      TEST_VAR = "foo";
+
+      buildPhase = ''
+        # Make nix-instantiate happy in the sandbox
+        export NIX_STORE_DIR="$(realpath "$(mktemp -d store.XXXXXXXXXX)")"
+        export NIX_STATE_DIR="$(realpath "$(mktemp -d state.XXXXXXXXXX)")"
+
+        # Helper function to check expected exit code
+        expect() {
+          local expected res
+          expected="$1"
+          shift
+          set +e
+          "$@"
+          res="$?"
+          set -e
+          [[ $res -eq $expected ]]
+        }
+
+        # Track test results so far
+        passed=true
+
+        source "$testCommandsPath"
+      '';
+
+      # Actually runs into the argv limit
+      passAsFile = [ "testCommands" ];
+      testCommands = lib.concatMapStrings testCommand allLangTests;
+
+      installPhase = ''
+        if $passed; then
+          touch $out
+        else
+          echo "Some test(s) failed!"
+          exit 1
+        fi
+      '';
+    };
+
+in
+
+depot.nix.readTree.drvTargets {
+  "nix-2.3" = runCppNixLangTests nix;
+  "nix-${lib.versions.majorMinor nix_latest.version}" = runCppNixLangTests nix_latest;
+}
diff --git a/tvix/website/default.nix b/tvix/website/default.nix
new file mode 100644
index 0000000000..a2fc247e4a
--- /dev/null
+++ b/tvix/website/default.nix
@@ -0,0 +1,46 @@
+{ depot, lib, pkgs, ... }:
+
+let
+  description = "Rust implementation of the purely-functional Nix package manager";
+
+  # https://developers.google.com/search/docs/advanced/structured-data/
+  # https://schema.org/SoftwareApplication
+  structuredData = {
+    "@context" = "https://schema.org";
+    "@type" = "SoftwareApplication";
+    name = "Tvix";
+    url = "https://tvix.dev";
+    abstract = description;
+    applicationCategory = "DeveloperApplication";
+    contributor = "https://tvl.fyi";
+    image = "https://tvix.dev/logo.webp";
+  };
+
+  # All Tvix-related blog posts from the main TVL website
+  tvixPosts = builtins.filter
+    (post: !(post.draft or false) && (lib.hasInfix "Tvix" post.title))
+    depot.web.tvl.blog.posts;
+
+  postListEntries = map (p: "* [${p.title}](https://tvl.fyi/blog/${p.key})") tvixPosts;
+
+  landing = depot.web.tvl.template {
+    title = "Tvix - A new implementation of Nix";
+    content = ''
+      ${builtins.readFile ./landing-en.md}
+      ${builtins.concatStringsSep "\n" postListEntries}
+    '';
+
+    extraHead = ''
+      <meta name="description" content="${description}">
+      <script type="application/ld+json">
+        ${builtins.toJSON structuredData}
+      </script>
+    '';
+  };
+
+in
+pkgs.runCommand "tvix-website" { } ''
+  mkdir $out
+  cp ${landing} $out/index.html
+  cp ${depot.tvix.logo}/logo.webp $out/
+''
diff --git a/tvix/website/landing-en.md b/tvix/website/landing-en.md
new file mode 100644
index 0000000000..61a011dee9
--- /dev/null
+++ b/tvix/website/landing-en.md
@@ -0,0 +1,42 @@
+<img class="tvl-logo" src="./logo.webp"
+     alt="A candy bar in different shades of blue that says 'Tvix by TVL' on it">
+
+------------------
+
+Tvix is a new implementation of Nix, a purely-functional package manager. It
+aims to have a modular implementation, in which different components can be
+reused or replaced based on the use-case.
+
+Tvix is developed as a GPLv3-licensed open-source project by
+[TVL][], with source code available in the [TVL monorepo][].
+
+There are several projects within Tvix, such as:
+
+* `//tvix/castore` - subtree storage/transfer in a content-addressed fashion
+* `//tvix/cli` - preliminary REPL & CLI implementation for Tvix
+* `//tvix/eval` - an implementation of the Nix programming language
+* `//tvix/nar-bridge` - a HTTP webserver providing a Nix HTTP Binary Cache interface in front of a tvix-store
+* `//tvix/nix-compat` - a Rust library for compatibility with C++ Nix, features like encodings and hashing schemes and formats
+* `//tvix/serde` - a Rust library for using the Nix language for app configuration
+* `//tvix/store` - a "filesystem" linking Nix store paths and metadata with the content-addressed layer
+* ... and a handful others!
+
+The language evaluator can be toyed with in [Tvixbolt][], and you can check out
+the [Tvix README][] ([GitHub mirror][gh]) for additional information on the
+project and development workflows.
+
+Developer documentation for some parts of Tvix is [available online][docs].
+
+Benchmarks are run nightly on new commits by [windtunnel][wt].
+
+[TVL]: https://tvl.fyi
+[TVL monorepo]: https://cs.tvl.fyi/depot/-/tree/tvix
+[Tvixbolt]: https://bolt.tvix.dev
+[Tvix README]: https://code.tvl.fyi/about/tvix
+[gh]: https://github.com/tvlfyi/tvix/
+[docs]: https://docs.tvix.dev
+[wt]: https://staging.windtunnel.ci/tvl/tvix
+
+-------------------
+
+Check out the latest Tvix-related blog posts from TVL's website: